@umituz/web-canvas 1.1.1 → 1.2.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.
@@ -998,7 +998,76 @@ ${project.appMetadata.whatsNew}`;
998
998
  }
999
999
  };
1000
1000
 
1001
+ // src/infrastructure/services/minimalXlsx.ts
1002
+ var XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
1003
+ function escapeXml(value) {
1004
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1005
+ }
1006
+ function safeSheetName(name, index) {
1007
+ const cleaned = name.replace(/[:\\/?*[\]]/g, " ").slice(0, 31).trim();
1008
+ return cleaned.length > 0 ? cleaned : `Sheet${index + 1}`;
1009
+ }
1010
+ function columnName(index) {
1011
+ let name = "";
1012
+ let n = index;
1013
+ while (n >= 0) {
1014
+ name = String.fromCharCode(65 + n % 26) + name;
1015
+ n = Math.floor(n / 26) - 1;
1016
+ }
1017
+ return name;
1018
+ }
1019
+ function cellXml(value, row, column) {
1020
+ const ref = `${columnName(column)}${row}`;
1021
+ if (typeof value === "number" && Number.isFinite(value)) {
1022
+ return `<c r="${ref}"><v>${value}</v></c>`;
1023
+ }
1024
+ const text = value === null || value === void 0 ? "" : typeof value === "boolean" ? value ? "TRUE" : "FALSE" : String(value);
1025
+ return `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${escapeXml(text)}</t></is></c>`;
1026
+ }
1027
+ function sheetXml(rows) {
1028
+ const body = rows.map((row, rowIndex) => {
1029
+ const cells = row.map((value, columnIndex) => cellXml(value, rowIndex + 1, columnIndex)).join("");
1030
+ return `<row r="${rowIndex + 1}">${cells}</row>`;
1031
+ }).join("");
1032
+ return `${XML_HEADER}<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>${body}</sheetData></worksheet>`;
1033
+ }
1034
+ async function writeXlsxBlob(sheets, JSZip) {
1035
+ const zip = new JSZip();
1036
+ const safeNames = sheets.map((sheet, index) => safeSheetName(sheet.name, index));
1037
+ zip.file(
1038
+ "[Content_Types].xml",
1039
+ `${XML_HEADER}<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>` + safeNames.map(
1040
+ (_, index) => `<Override PartName="/xl/worksheets/sheet${index + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`
1041
+ ).join("") + "</Types>"
1042
+ );
1043
+ zip.file(
1044
+ "_rels/.rels",
1045
+ `${XML_HEADER}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`
1046
+ );
1047
+ zip.file(
1048
+ "xl/workbook.xml",
1049
+ `${XML_HEADER}<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets>` + safeNames.map((name, index) => `<sheet name="${escapeXml(name)}" sheetId="${index + 1}" r:id="rId${index + 1}"/>`).join("") + "</sheets></workbook>"
1050
+ );
1051
+ zip.file(
1052
+ "xl/_rels/workbook.xml.rels",
1053
+ `${XML_HEADER}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` + safeNames.map(
1054
+ (_, index) => `<Relationship Id="rId${index + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${index + 1}.xml"/>`
1055
+ ).join("") + "</Relationships>"
1056
+ );
1057
+ sheets.forEach((sheet, index) => {
1058
+ zip.file(`xl/worksheets/sheet${index + 1}.xml`, sheetXml(sheet.rows));
1059
+ });
1060
+ return zip.generateAsync({ type: "blob" });
1061
+ }
1062
+
1001
1063
  // src/application/services/ReportService.ts
1064
+ function recordsToSheet(name, records) {
1065
+ const headers = [...new Set(records.flatMap((record) => Object.keys(record)))];
1066
+ return {
1067
+ name,
1068
+ rows: [headers, ...records.map((record) => headers.map((header) => record[header]))]
1069
+ };
1070
+ }
1002
1071
  var ReportService = class _ReportService {
1003
1072
  static getInstance() {
1004
1073
  if (!_ReportService.instance) {
@@ -1044,31 +1113,19 @@ var ReportService = class _ReportService {
1044
1113
  * Export data to Excel
1045
1114
  */
1046
1115
  async exportToExcel(data, options = {}) {
1047
- const XLSX = await import("xlsx");
1048
- const wb = XLSX.utils.book_new();
1116
+ const JSZip = (await import("jszip")).default;
1049
1117
  const apps = data.items.filter((i) => i.type === "app").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
1050
1118
  const campaigns = data.items.filter((i) => i.type === "campaign").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
1051
1119
  const posts = data.items.filter((i) => i.type === "post").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
1120
+ const sheets = [];
1052
1121
  if (apps.length > 0) {
1053
- XLSX.utils.book_append_sheet(
1054
- wb,
1055
- XLSX.utils.json_to_sheet(apps),
1056
- options.sheetName || "Apps"
1057
- );
1122
+ sheets.push(recordsToSheet(options.sheetName || "Apps", apps));
1058
1123
  }
1059
1124
  if (campaigns.length > 0) {
1060
- XLSX.utils.book_append_sheet(
1061
- wb,
1062
- XLSX.utils.json_to_sheet(campaigns),
1063
- "Campaigns"
1064
- );
1125
+ sheets.push(recordsToSheet("Campaigns", campaigns));
1065
1126
  }
1066
1127
  if (posts.length > 0) {
1067
- XLSX.utils.book_append_sheet(
1068
- wb,
1069
- XLSX.utils.json_to_sheet(posts),
1070
- "Posts"
1071
- );
1128
+ sheets.push(recordsToSheet("Posts", posts));
1072
1129
  }
1073
1130
  if (options.includeSummary !== false) {
1074
1131
  const summary = [
@@ -1086,13 +1143,18 @@ var ReportService = class _ReportService {
1086
1143
  Value: (/* @__PURE__ */ new Date()).toLocaleDateString()
1087
1144
  }
1088
1145
  ];
1089
- XLSX.utils.book_append_sheet(
1090
- wb,
1091
- XLSX.utils.json_to_sheet(summary),
1092
- "Summary"
1093
- );
1146
+ sheets.push(recordsToSheet("Summary", summary));
1147
+ }
1148
+ if (sheets.length === 0) {
1149
+ sheets.push({ name: options.sheetName || "Sheet1", rows: [] });
1094
1150
  }
1095
- XLSX.writeFile(wb, options.filename || "report.xlsx");
1151
+ const blob = await writeXlsxBlob(sheets, JSZip);
1152
+ const url = URL.createObjectURL(blob);
1153
+ const link = document.createElement("a");
1154
+ link.href = url;
1155
+ link.download = options.filename || "report.xlsx";
1156
+ link.click();
1157
+ URL.revokeObjectURL(url);
1096
1158
  }
1097
1159
  /**
1098
1160
  * Export data to CSV
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ReportService,
3
3
  ScreenshotStudioService
4
- } from "../chunk-DNKRSL6D.mjs";
4
+ } from "../chunk-P6RJ75US.mjs";
5
5
  import "../chunk-UOGTEMIJ.mjs";
6
6
  import "../chunk-Z6IB4BQV.mjs";
7
7
  import "../chunk-MC6PNV2F.mjs";
@@ -210,7 +210,76 @@ ${project.appMetadata.whatsNew}`;
210
210
  }
211
211
  };
212
212
 
213
+ // src/infrastructure/services/minimalXlsx.ts
214
+ var XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
215
+ function escapeXml(value) {
216
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
217
+ }
218
+ function safeSheetName(name, index) {
219
+ const cleaned = name.replace(/[:\\/?*[\]]/g, " ").slice(0, 31).trim();
220
+ return cleaned.length > 0 ? cleaned : `Sheet${index + 1}`;
221
+ }
222
+ function columnName(index) {
223
+ let name = "";
224
+ let n = index;
225
+ while (n >= 0) {
226
+ name = String.fromCharCode(65 + n % 26) + name;
227
+ n = Math.floor(n / 26) - 1;
228
+ }
229
+ return name;
230
+ }
231
+ function cellXml(value, row, column) {
232
+ const ref = `${columnName(column)}${row}`;
233
+ if (typeof value === "number" && Number.isFinite(value)) {
234
+ return `<c r="${ref}"><v>${value}</v></c>`;
235
+ }
236
+ const text = value === null || value === void 0 ? "" : typeof value === "boolean" ? value ? "TRUE" : "FALSE" : String(value);
237
+ return `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${escapeXml(text)}</t></is></c>`;
238
+ }
239
+ function sheetXml(rows) {
240
+ const body = rows.map((row, rowIndex) => {
241
+ const cells = row.map((value, columnIndex) => cellXml(value, rowIndex + 1, columnIndex)).join("");
242
+ return `<row r="${rowIndex + 1}">${cells}</row>`;
243
+ }).join("");
244
+ return `${XML_HEADER}<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>${body}</sheetData></worksheet>`;
245
+ }
246
+ async function writeXlsxBlob(sheets, JSZip) {
247
+ const zip = new JSZip();
248
+ const safeNames = sheets.map((sheet, index) => safeSheetName(sheet.name, index));
249
+ zip.file(
250
+ "[Content_Types].xml",
251
+ `${XML_HEADER}<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>` + safeNames.map(
252
+ (_, index) => `<Override PartName="/xl/worksheets/sheet${index + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`
253
+ ).join("") + "</Types>"
254
+ );
255
+ zip.file(
256
+ "_rels/.rels",
257
+ `${XML_HEADER}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`
258
+ );
259
+ zip.file(
260
+ "xl/workbook.xml",
261
+ `${XML_HEADER}<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets>` + safeNames.map((name, index) => `<sheet name="${escapeXml(name)}" sheetId="${index + 1}" r:id="rId${index + 1}"/>`).join("") + "</sheets></workbook>"
262
+ );
263
+ zip.file(
264
+ "xl/_rels/workbook.xml.rels",
265
+ `${XML_HEADER}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` + safeNames.map(
266
+ (_, index) => `<Relationship Id="rId${index + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${index + 1}.xml"/>`
267
+ ).join("") + "</Relationships>"
268
+ );
269
+ sheets.forEach((sheet, index) => {
270
+ zip.file(`xl/worksheets/sheet${index + 1}.xml`, sheetXml(sheet.rows));
271
+ });
272
+ return zip.generateAsync({ type: "blob" });
273
+ }
274
+
213
275
  // src/application/services/ReportService.ts
276
+ function recordsToSheet(name, records) {
277
+ const headers = [...new Set(records.flatMap((record) => Object.keys(record)))];
278
+ return {
279
+ name,
280
+ rows: [headers, ...records.map((record) => headers.map((header) => record[header]))]
281
+ };
282
+ }
214
283
  var ReportService = class _ReportService {
215
284
  static getInstance() {
216
285
  if (!_ReportService.instance) {
@@ -256,31 +325,19 @@ var ReportService = class _ReportService {
256
325
  * Export data to Excel
257
326
  */
258
327
  async exportToExcel(data, options = {}) {
259
- const XLSX = await import("xlsx");
260
- const wb = XLSX.utils.book_new();
328
+ const JSZip = (await import("jszip")).default;
261
329
  const apps = data.items.filter((i) => i.type === "app").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
262
330
  const campaigns = data.items.filter((i) => i.type === "campaign").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
263
331
  const posts = data.items.filter((i) => i.type === "post").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
332
+ const sheets = [];
264
333
  if (apps.length > 0) {
265
- XLSX.utils.book_append_sheet(
266
- wb,
267
- XLSX.utils.json_to_sheet(apps),
268
- options.sheetName || "Apps"
269
- );
334
+ sheets.push(recordsToSheet(options.sheetName || "Apps", apps));
270
335
  }
271
336
  if (campaigns.length > 0) {
272
- XLSX.utils.book_append_sheet(
273
- wb,
274
- XLSX.utils.json_to_sheet(campaigns),
275
- "Campaigns"
276
- );
337
+ sheets.push(recordsToSheet("Campaigns", campaigns));
277
338
  }
278
339
  if (posts.length > 0) {
279
- XLSX.utils.book_append_sheet(
280
- wb,
281
- XLSX.utils.json_to_sheet(posts),
282
- "Posts"
283
- );
340
+ sheets.push(recordsToSheet("Posts", posts));
284
341
  }
285
342
  if (options.includeSummary !== false) {
286
343
  const summary = [
@@ -298,13 +355,18 @@ var ReportService = class _ReportService {
298
355
  Value: (/* @__PURE__ */ new Date()).toLocaleDateString()
299
356
  }
300
357
  ];
301
- XLSX.utils.book_append_sheet(
302
- wb,
303
- XLSX.utils.json_to_sheet(summary),
304
- "Summary"
305
- );
358
+ sheets.push(recordsToSheet("Summary", summary));
359
+ }
360
+ if (sheets.length === 0) {
361
+ sheets.push({ name: options.sheetName || "Sheet1", rows: [] });
306
362
  }
307
- XLSX.writeFile(wb, options.filename || "report.xlsx");
363
+ const blob = await writeXlsxBlob(sheets, JSZip);
364
+ const url = URL.createObjectURL(blob);
365
+ const link = document.createElement("a");
366
+ link.href = url;
367
+ link.download = options.filename || "report.xlsx";
368
+ link.click();
369
+ URL.revokeObjectURL(url);
308
370
  }
309
371
  /**
310
372
  * Export data to CSV
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ReportService,
3
3
  ScreenshotStudioService
4
- } from "./chunk-DNKRSL6D.mjs";
4
+ } from "./chunk-P6RJ75US.mjs";
5
5
  import {
6
6
  CanvasRenderer
7
7
  } from "./chunk-UOGTEMIJ.mjs";
package/dist/index.js CHANGED
@@ -1010,7 +1010,76 @@ ${project.appMetadata.whatsNew}`;
1010
1010
  }
1011
1011
  };
1012
1012
 
1013
+ // src/infrastructure/services/minimalXlsx.ts
1014
+ var XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
1015
+ function escapeXml(value) {
1016
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1017
+ }
1018
+ function safeSheetName(name, index) {
1019
+ const cleaned = name.replace(/[:\\/?*[\]]/g, " ").slice(0, 31).trim();
1020
+ return cleaned.length > 0 ? cleaned : `Sheet${index + 1}`;
1021
+ }
1022
+ function columnName(index) {
1023
+ let name = "";
1024
+ let n = index;
1025
+ while (n >= 0) {
1026
+ name = String.fromCharCode(65 + n % 26) + name;
1027
+ n = Math.floor(n / 26) - 1;
1028
+ }
1029
+ return name;
1030
+ }
1031
+ function cellXml(value, row, column) {
1032
+ const ref = `${columnName(column)}${row}`;
1033
+ if (typeof value === "number" && Number.isFinite(value)) {
1034
+ return `<c r="${ref}"><v>${value}</v></c>`;
1035
+ }
1036
+ const text = value === null || value === void 0 ? "" : typeof value === "boolean" ? value ? "TRUE" : "FALSE" : String(value);
1037
+ return `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${escapeXml(text)}</t></is></c>`;
1038
+ }
1039
+ function sheetXml(rows) {
1040
+ const body = rows.map((row, rowIndex) => {
1041
+ const cells = row.map((value, columnIndex) => cellXml(value, rowIndex + 1, columnIndex)).join("");
1042
+ return `<row r="${rowIndex + 1}">${cells}</row>`;
1043
+ }).join("");
1044
+ return `${XML_HEADER}<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>${body}</sheetData></worksheet>`;
1045
+ }
1046
+ async function writeXlsxBlob(sheets, JSZip) {
1047
+ const zip = new JSZip();
1048
+ const safeNames = sheets.map((sheet, index) => safeSheetName(sheet.name, index));
1049
+ zip.file(
1050
+ "[Content_Types].xml",
1051
+ `${XML_HEADER}<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>` + safeNames.map(
1052
+ (_, index) => `<Override PartName="/xl/worksheets/sheet${index + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`
1053
+ ).join("") + "</Types>"
1054
+ );
1055
+ zip.file(
1056
+ "_rels/.rels",
1057
+ `${XML_HEADER}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`
1058
+ );
1059
+ zip.file(
1060
+ "xl/workbook.xml",
1061
+ `${XML_HEADER}<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets>` + safeNames.map((name, index) => `<sheet name="${escapeXml(name)}" sheetId="${index + 1}" r:id="rId${index + 1}"/>`).join("") + "</sheets></workbook>"
1062
+ );
1063
+ zip.file(
1064
+ "xl/_rels/workbook.xml.rels",
1065
+ `${XML_HEADER}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` + safeNames.map(
1066
+ (_, index) => `<Relationship Id="rId${index + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${index + 1}.xml"/>`
1067
+ ).join("") + "</Relationships>"
1068
+ );
1069
+ sheets.forEach((sheet, index) => {
1070
+ zip.file(`xl/worksheets/sheet${index + 1}.xml`, sheetXml(sheet.rows));
1071
+ });
1072
+ return zip.generateAsync({ type: "blob" });
1073
+ }
1074
+
1013
1075
  // src/application/services/ReportService.ts
1076
+ function recordsToSheet(name, records) {
1077
+ const headers = [...new Set(records.flatMap((record) => Object.keys(record)))];
1078
+ return {
1079
+ name,
1080
+ rows: [headers, ...records.map((record) => headers.map((header) => record[header]))]
1081
+ };
1082
+ }
1014
1083
  var ReportService = class _ReportService {
1015
1084
  static getInstance() {
1016
1085
  if (!_ReportService.instance) {
@@ -1056,31 +1125,19 @@ var ReportService = class _ReportService {
1056
1125
  * Export data to Excel
1057
1126
  */
1058
1127
  async exportToExcel(data, options = {}) {
1059
- const XLSX = await import("xlsx");
1060
- const wb = XLSX.utils.book_new();
1128
+ const JSZip = (await import("jszip")).default;
1061
1129
  const apps = data.items.filter((i) => i.type === "app").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
1062
1130
  const campaigns = data.items.filter((i) => i.type === "campaign").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
1063
1131
  const posts = data.items.filter((i) => i.type === "post").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
1132
+ const sheets = [];
1064
1133
  if (apps.length > 0) {
1065
- XLSX.utils.book_append_sheet(
1066
- wb,
1067
- XLSX.utils.json_to_sheet(apps),
1068
- options.sheetName || "Apps"
1069
- );
1134
+ sheets.push(recordsToSheet(options.sheetName || "Apps", apps));
1070
1135
  }
1071
1136
  if (campaigns.length > 0) {
1072
- XLSX.utils.book_append_sheet(
1073
- wb,
1074
- XLSX.utils.json_to_sheet(campaigns),
1075
- "Campaigns"
1076
- );
1137
+ sheets.push(recordsToSheet("Campaigns", campaigns));
1077
1138
  }
1078
1139
  if (posts.length > 0) {
1079
- XLSX.utils.book_append_sheet(
1080
- wb,
1081
- XLSX.utils.json_to_sheet(posts),
1082
- "Posts"
1083
- );
1140
+ sheets.push(recordsToSheet("Posts", posts));
1084
1141
  }
1085
1142
  if (options.includeSummary !== false) {
1086
1143
  const summary = [
@@ -1098,13 +1155,18 @@ var ReportService = class _ReportService {
1098
1155
  Value: (/* @__PURE__ */ new Date()).toLocaleDateString()
1099
1156
  }
1100
1157
  ];
1101
- XLSX.utils.book_append_sheet(
1102
- wb,
1103
- XLSX.utils.json_to_sheet(summary),
1104
- "Summary"
1105
- );
1158
+ sheets.push(recordsToSheet("Summary", summary));
1106
1159
  }
1107
- XLSX.writeFile(wb, options.filename || "report.xlsx");
1160
+ if (sheets.length === 0) {
1161
+ sheets.push({ name: options.sheetName || "Sheet1", rows: [] });
1162
+ }
1163
+ const blob = await writeXlsxBlob(sheets, JSZip);
1164
+ const url = URL.createObjectURL(blob);
1165
+ const link = document.createElement("a");
1166
+ link.href = url;
1167
+ link.download = options.filename || "report.xlsx";
1168
+ link.click();
1169
+ URL.revokeObjectURL(url);
1108
1170
  }
1109
1171
  /**
1110
1172
  * Export data to CSV
package/dist/index.mjs CHANGED
@@ -3,11 +3,11 @@ import {
3
3
  useFreehandCanvas,
4
4
  useReportExport,
5
5
  useScreenshotStudio
6
- } from "./chunk-YKXOSUMF.mjs";
6
+ } from "./chunk-YRC4YPMO.mjs";
7
7
  import {
8
8
  ReportService,
9
9
  ScreenshotStudioService
10
- } from "./chunk-DNKRSL6D.mjs";
10
+ } from "./chunk-P6RJ75US.mjs";
11
11
  import {
12
12
  CanvasRenderer,
13
13
  DeviceConfigService,
@@ -1482,7 +1482,76 @@ function useCanvasExport(options = {}) {
1482
1482
  // src/presentation/hooks/useReportExport.ts
1483
1483
  var import_react3 = require("react");
1484
1484
 
1485
+ // src/infrastructure/services/minimalXlsx.ts
1486
+ var XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
1487
+ function escapeXml(value) {
1488
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1489
+ }
1490
+ function safeSheetName(name, index) {
1491
+ const cleaned = name.replace(/[:\\/?*[\]]/g, " ").slice(0, 31).trim();
1492
+ return cleaned.length > 0 ? cleaned : `Sheet${index + 1}`;
1493
+ }
1494
+ function columnName(index) {
1495
+ let name = "";
1496
+ let n = index;
1497
+ while (n >= 0) {
1498
+ name = String.fromCharCode(65 + n % 26) + name;
1499
+ n = Math.floor(n / 26) - 1;
1500
+ }
1501
+ return name;
1502
+ }
1503
+ function cellXml(value, row, column) {
1504
+ const ref = `${columnName(column)}${row}`;
1505
+ if (typeof value === "number" && Number.isFinite(value)) {
1506
+ return `<c r="${ref}"><v>${value}</v></c>`;
1507
+ }
1508
+ const text = value === null || value === void 0 ? "" : typeof value === "boolean" ? value ? "TRUE" : "FALSE" : String(value);
1509
+ return `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${escapeXml(text)}</t></is></c>`;
1510
+ }
1511
+ function sheetXml(rows) {
1512
+ const body = rows.map((row, rowIndex) => {
1513
+ const cells = row.map((value, columnIndex) => cellXml(value, rowIndex + 1, columnIndex)).join("");
1514
+ return `<row r="${rowIndex + 1}">${cells}</row>`;
1515
+ }).join("");
1516
+ return `${XML_HEADER}<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>${body}</sheetData></worksheet>`;
1517
+ }
1518
+ async function writeXlsxBlob(sheets, JSZip) {
1519
+ const zip = new JSZip();
1520
+ const safeNames = sheets.map((sheet, index) => safeSheetName(sheet.name, index));
1521
+ zip.file(
1522
+ "[Content_Types].xml",
1523
+ `${XML_HEADER}<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>` + safeNames.map(
1524
+ (_, index) => `<Override PartName="/xl/worksheets/sheet${index + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`
1525
+ ).join("") + "</Types>"
1526
+ );
1527
+ zip.file(
1528
+ "_rels/.rels",
1529
+ `${XML_HEADER}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`
1530
+ );
1531
+ zip.file(
1532
+ "xl/workbook.xml",
1533
+ `${XML_HEADER}<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets>` + safeNames.map((name, index) => `<sheet name="${escapeXml(name)}" sheetId="${index + 1}" r:id="rId${index + 1}"/>`).join("") + "</sheets></workbook>"
1534
+ );
1535
+ zip.file(
1536
+ "xl/_rels/workbook.xml.rels",
1537
+ `${XML_HEADER}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` + safeNames.map(
1538
+ (_, index) => `<Relationship Id="rId${index + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${index + 1}.xml"/>`
1539
+ ).join("") + "</Relationships>"
1540
+ );
1541
+ sheets.forEach((sheet, index) => {
1542
+ zip.file(`xl/worksheets/sheet${index + 1}.xml`, sheetXml(sheet.rows));
1543
+ });
1544
+ return zip.generateAsync({ type: "blob" });
1545
+ }
1546
+
1485
1547
  // src/application/services/ReportService.ts
1548
+ function recordsToSheet(name, records) {
1549
+ const headers = [...new Set(records.flatMap((record) => Object.keys(record)))];
1550
+ return {
1551
+ name,
1552
+ rows: [headers, ...records.map((record) => headers.map((header) => record[header]))]
1553
+ };
1554
+ }
1486
1555
  var ReportService = class _ReportService {
1487
1556
  static getInstance() {
1488
1557
  if (!_ReportService.instance) {
@@ -1528,31 +1597,19 @@ var ReportService = class _ReportService {
1528
1597
  * Export data to Excel
1529
1598
  */
1530
1599
  async exportToExcel(data, options = {}) {
1531
- const XLSX = await import("xlsx");
1532
- const wb = XLSX.utils.book_new();
1600
+ const JSZip = (await import("jszip")).default;
1533
1601
  const apps = data.items.filter((i) => i.type === "app").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
1534
1602
  const campaigns = data.items.filter((i) => i.type === "campaign").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
1535
1603
  const posts = data.items.filter((i) => i.type === "post").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
1604
+ const sheets = [];
1536
1605
  if (apps.length > 0) {
1537
- XLSX.utils.book_append_sheet(
1538
- wb,
1539
- XLSX.utils.json_to_sheet(apps),
1540
- options.sheetName || "Apps"
1541
- );
1606
+ sheets.push(recordsToSheet(options.sheetName || "Apps", apps));
1542
1607
  }
1543
1608
  if (campaigns.length > 0) {
1544
- XLSX.utils.book_append_sheet(
1545
- wb,
1546
- XLSX.utils.json_to_sheet(campaigns),
1547
- "Campaigns"
1548
- );
1609
+ sheets.push(recordsToSheet("Campaigns", campaigns));
1549
1610
  }
1550
1611
  if (posts.length > 0) {
1551
- XLSX.utils.book_append_sheet(
1552
- wb,
1553
- XLSX.utils.json_to_sheet(posts),
1554
- "Posts"
1555
- );
1612
+ sheets.push(recordsToSheet("Posts", posts));
1556
1613
  }
1557
1614
  if (options.includeSummary !== false) {
1558
1615
  const summary = [
@@ -1570,13 +1627,18 @@ var ReportService = class _ReportService {
1570
1627
  Value: (/* @__PURE__ */ new Date()).toLocaleDateString()
1571
1628
  }
1572
1629
  ];
1573
- XLSX.utils.book_append_sheet(
1574
- wb,
1575
- XLSX.utils.json_to_sheet(summary),
1576
- "Summary"
1577
- );
1630
+ sheets.push(recordsToSheet("Summary", summary));
1578
1631
  }
1579
- XLSX.writeFile(wb, options.filename || "report.xlsx");
1632
+ if (sheets.length === 0) {
1633
+ sheets.push({ name: options.sheetName || "Sheet1", rows: [] });
1634
+ }
1635
+ const blob = await writeXlsxBlob(sheets, JSZip);
1636
+ const url = URL.createObjectURL(blob);
1637
+ const link = document.createElement("a");
1638
+ link.href = url;
1639
+ link.download = options.filename || "report.xlsx";
1640
+ link.click();
1641
+ URL.revokeObjectURL(url);
1580
1642
  }
1581
1643
  /**
1582
1644
  * Export data to CSV
@@ -3,8 +3,8 @@ import {
3
3
  useFreehandCanvas,
4
4
  useReportExport,
5
5
  useScreenshotStudio
6
- } from "../chunk-YKXOSUMF.mjs";
7
- import "../chunk-DNKRSL6D.mjs";
6
+ } from "../chunk-YRC4YPMO.mjs";
7
+ import "../chunk-P6RJ75US.mjs";
8
8
  import "../chunk-UOGTEMIJ.mjs";
9
9
  import "../chunk-Z6IB4BQV.mjs";
10
10
  import "../chunk-MC6PNV2F.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-canvas",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Comprehensive canvas and screenshot generation library with device frames, patterns, and PDF export",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -77,7 +77,7 @@
77
77
  "license": "MIT",
78
78
  "repository": {
79
79
  "type": "git",
80
- "url": "https://github.com/umituz/web-canvas"
80
+ "url": "git+https://github.com/umituz/web-canvas.git"
81
81
  },
82
82
  "bugs": {
83
83
  "url": "https://github.com/umituz/web-canvas/issues"
@@ -97,7 +97,6 @@
97
97
  "dependencies": {
98
98
  "html2canvas": "^1.4.1",
99
99
  "jspdf": "^4.2.1",
100
- "jszip": "^3.10.1",
101
- "xlsx": "^0.18.5"
100
+ "jszip": "^3.10.1"
102
101
  }
103
- }
102
+ }
@@ -5,6 +5,20 @@
5
5
 
6
6
  import type { ReportData } from '../../domain/types/report.types';
7
7
  import type { PDFExportOptions, ExcelExportOptions } from '../../domain/types/report.types';
8
+ import { writeXlsxBlob } from '../../infrastructure/services/minimalXlsx';
9
+ import type { XlsxSheet } from '../../infrastructure/services/minimalXlsx';
10
+
11
+ /** Flat records → header + value rows, matching json_to_sheet's shape. */
12
+ function recordsToSheet(
13
+ name: string,
14
+ records: Record<string, string | number | boolean | null | undefined>[]
15
+ ): XlsxSheet {
16
+ const headers = [...new Set(records.flatMap((record) => Object.keys(record)))];
17
+ return {
18
+ name,
19
+ rows: [headers, ...records.map((record) => headers.map((header) => record[header]))],
20
+ };
21
+ }
8
22
 
9
23
  export class ReportService {
10
24
  private static instance: ReportService;
@@ -71,8 +85,7 @@ export class ReportService {
71
85
  data: ReportData,
72
86
  options: ExcelExportOptions = {}
73
87
  ): Promise<void> {
74
- const XLSX = await import('xlsx');
75
- const wb = XLSX.utils.book_new();
88
+ const JSZip = (await import('jszip')).default;
76
89
 
77
90
  // Group items by type
78
91
  const apps = data.items
@@ -85,26 +98,15 @@ export class ReportService {
85
98
  .filter((i) => i.type === 'post')
86
99
  .map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
87
100
 
101
+ const sheets: XlsxSheet[] = [];
88
102
  if (apps.length > 0) {
89
- XLSX.utils.book_append_sheet(
90
- wb,
91
- XLSX.utils.json_to_sheet(apps),
92
- options.sheetName || 'Apps'
93
- );
103
+ sheets.push(recordsToSheet(options.sheetName || 'Apps', apps));
94
104
  }
95
105
  if (campaigns.length > 0) {
96
- XLSX.utils.book_append_sheet(
97
- wb,
98
- XLSX.utils.json_to_sheet(campaigns),
99
- 'Campaigns'
100
- );
106
+ sheets.push(recordsToSheet('Campaigns', campaigns));
101
107
  }
102
108
  if (posts.length > 0) {
103
- XLSX.utils.book_append_sheet(
104
- wb,
105
- XLSX.utils.json_to_sheet(posts),
106
- 'Posts'
107
- );
109
+ sheets.push(recordsToSheet('Posts', posts));
108
110
  }
109
111
 
110
112
  // Summary sheet
@@ -124,14 +126,20 @@ export class ReportService {
124
126
  Value: new Date().toLocaleDateString(),
125
127
  },
126
128
  ];
127
- XLSX.utils.book_append_sheet(
128
- wb,
129
- XLSX.utils.json_to_sheet(summary),
130
- 'Summary'
131
- );
129
+ sheets.push(recordsToSheet('Summary', summary));
130
+ }
131
+
132
+ if (sheets.length === 0) {
133
+ sheets.push({ name: options.sheetName || 'Sheet1', rows: [] });
132
134
  }
133
135
 
134
- XLSX.writeFile(wb, options.filename || 'report.xlsx');
136
+ const blob = await writeXlsxBlob(sheets, JSZip);
137
+ const url = URL.createObjectURL(blob);
138
+ const link = document.createElement('a');
139
+ link.href = url;
140
+ link.download = options.filename || 'report.xlsx';
141
+ link.click();
142
+ URL.revokeObjectURL(url);
135
143
  }
136
144
 
137
145
  /**
@@ -0,0 +1,122 @@
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
+
10
+ export interface XlsxSheet {
11
+ name: string;
12
+ /** Rows of primitive cells; the first row is the header. */
13
+ rows: (string | number | boolean | null | undefined)[][];
14
+ }
15
+
16
+ const XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
17
+
18
+ function escapeXml(value: string): string {
19
+ return value
20
+ .replace(/&/g, '&amp;')
21
+ .replace(/</g, '&lt;')
22
+ .replace(/>/g, '&gt;')
23
+ .replace(/"/g, '&quot;')
24
+ .replace(/'/g, '&apos;');
25
+ }
26
+
27
+ /** Excel sheet names: max 31 chars, no : \ / ? * [ ]. */
28
+ function safeSheetName(name: string, index: number): string {
29
+ const cleaned = name.replace(/[:\\/?*[\]]/g, ' ').slice(0, 31).trim();
30
+ return cleaned.length > 0 ? cleaned : `Sheet${index + 1}`;
31
+ }
32
+
33
+ function columnName(index: number): string {
34
+ let name = '';
35
+ let n = index;
36
+ while (n >= 0) {
37
+ name = String.fromCharCode(65 + (n % 26)) + name;
38
+ n = Math.floor(n / 26) - 1;
39
+ }
40
+ return name;
41
+ }
42
+
43
+ function cellXml(value: string | number | boolean | null | undefined, row: number, column: number): string {
44
+ const ref = `${columnName(column)}${row}`;
45
+ if (typeof value === 'number' && Number.isFinite(value)) {
46
+ return `<c r="${ref}"><v>${value}</v></c>`;
47
+ }
48
+ const text =
49
+ value === null || value === undefined
50
+ ? ''
51
+ : typeof value === 'boolean'
52
+ ? value
53
+ ? 'TRUE'
54
+ : 'FALSE'
55
+ : String(value);
56
+ return `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${escapeXml(text)}</t></is></c>`;
57
+ }
58
+
59
+ function sheetXml(rows: XlsxSheet['rows']): string {
60
+ const body = rows
61
+ .map((row, rowIndex) => {
62
+ const cells = row.map((value, columnIndex) => cellXml(value, rowIndex + 1, columnIndex)).join('');
63
+ return `<row r="${rowIndex + 1}">${cells}</row>`;
64
+ })
65
+ .join('');
66
+ return `${XML_HEADER}<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>${body}</sheetData></worksheet>`;
67
+ }
68
+
69
+ /** Assembles the sheets into a .xlsx zip blob using jszip. */
70
+ export async function writeXlsxBlob(sheets: XlsxSheet[], JSZip: typeof import('jszip')): Promise<Blob> {
71
+ const zip = new JSZip();
72
+ const safeNames = sheets.map((sheet, index) => safeSheetName(sheet.name, index));
73
+
74
+ zip.file(
75
+ '[Content_Types].xml',
76
+ `${XML_HEADER}<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">` +
77
+ '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
78
+ '<Default Extension="xml" ContentType="application/xml"/>' +
79
+ '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>' +
80
+ safeNames
81
+ .map(
82
+ (_, index) =>
83
+ `<Override PartName="/xl/worksheets/sheet${index + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`,
84
+ )
85
+ .join('') +
86
+ '</Types>',
87
+ );
88
+
89
+ zip.file(
90
+ '_rels/.rels',
91
+ `${XML_HEADER}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` +
92
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>' +
93
+ '</Relationships>',
94
+ );
95
+
96
+ zip.file(
97
+ 'xl/workbook.xml',
98
+ `${XML_HEADER}<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets>` +
99
+ safeNames
100
+ .map((name, index) => `<sheet name="${escapeXml(name)}" sheetId="${index + 1}" r:id="rId${index + 1}"/>`)
101
+ .join('') +
102
+ '</sheets></workbook>',
103
+ );
104
+
105
+ zip.file(
106
+ 'xl/_rels/workbook.xml.rels',
107
+ `${XML_HEADER}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` +
108
+ safeNames
109
+ .map(
110
+ (_, index) =>
111
+ `<Relationship Id="rId${index + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${index + 1}.xml"/>`,
112
+ )
113
+ .join('') +
114
+ '</Relationships>',
115
+ );
116
+
117
+ sheets.forEach((sheet, index) => {
118
+ zip.file(`xl/worksheets/sheet${index + 1}.xml`, sheetXml(sheet.rows));
119
+ });
120
+
121
+ return zip.generateAsync({ type: 'blob' });
122
+ }