@umituz/web-canvas 1.1.1 → 1.2.1
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/application/index.js +85 -23
- package/dist/application/index.mjs +1 -1
- package/dist/{chunk-YKXOSUMF.mjs → chunk-7YLVULSN.mjs} +92 -5
- package/dist/{chunk-DNKRSL6D.mjs → chunk-P6RJ75US.mjs} +85 -23
- package/dist/domain/index.d.mts +2 -22
- package/dist/domain/index.d.ts +2 -22
- package/dist/index.js +176 -27
- package/dist/index.mjs +2 -2
- package/dist/presentation/index.d.mts +0 -26
- package/dist/presentation/index.d.ts +0 -26
- package/dist/presentation/index.js +176 -27
- package/dist/presentation/index.mjs +2 -2
- package/package.json +4 -5
- package/src/application/services/ReportService.ts +31 -23
- package/src/domain/types/freehand.types.ts +2 -23
- package/src/infrastructure/services/minimalXlsx.ts +122 -0
- package/src/presentation/hooks/useFreehandCanvas.ts +102 -40
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
);
|
|
1146
|
+
sheets.push(recordsToSheet("Summary", summary));
|
|
1147
|
+
}
|
|
1148
|
+
if (sheets.length === 0) {
|
|
1149
|
+
sheets.push({ name: options.sheetName || "Sheet1", rows: [] });
|
|
1094
1150
|
}
|
|
1095
|
-
|
|
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-
|
|
4
|
+
} from "./chunk-P6RJ75US.mjs";
|
|
5
5
|
import {
|
|
6
6
|
CanvasRenderer
|
|
7
7
|
} from "./chunk-UOGTEMIJ.mjs";
|
|
@@ -604,19 +604,28 @@ function useReportExport() {
|
|
|
604
604
|
import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
605
605
|
var DEFAULT_BRUSH_SIZE = 3;
|
|
606
606
|
var DEFAULT_COLOR = "#1a1a1a";
|
|
607
|
+
var DEFAULT_KEYBOARD_STEP = 4;
|
|
608
|
+
var KEYBOARD_FAST_FACTOR = 4;
|
|
607
609
|
function widthFor(stroke, point) {
|
|
608
610
|
if (!stroke.dynamic) return stroke.size;
|
|
609
611
|
return stroke.size * (0.5 + Math.min(1, Math.max(0, point.pressure)));
|
|
610
612
|
}
|
|
611
613
|
function useFreehandCanvas(options = {}) {
|
|
612
|
-
const {
|
|
613
|
-
|
|
614
|
-
|
|
614
|
+
const {
|
|
615
|
+
brushSize = DEFAULT_BRUSH_SIZE,
|
|
616
|
+
color = DEFAULT_COLOR,
|
|
617
|
+
pressureEnabled = true,
|
|
618
|
+
keyboard = false,
|
|
619
|
+
keyboardStep = DEFAULT_KEYBOARD_STEP
|
|
620
|
+
} = options;
|
|
621
|
+
const optionsRef = useRef4({ brushSize, color, pressureEnabled, keyboardStep, onStrokeEnd: options.onStrokeEnd });
|
|
622
|
+
optionsRef.current = { brushSize, color, pressureEnabled, keyboardStep, onStrokeEnd: options.onStrokeEnd };
|
|
615
623
|
const canvasRef = useRef4(null);
|
|
616
624
|
const strokesRef = useRef4([]);
|
|
617
625
|
const activeStrokeRef = useRef4(null);
|
|
618
626
|
const activePointerIdRef = useRef4(null);
|
|
619
627
|
const dprRef = useRef4(1);
|
|
628
|
+
const penRef = useRef4(null);
|
|
620
629
|
const [strokeCount, setStrokeCount] = useState4(0);
|
|
621
630
|
const redraw = useCallback4(() => {
|
|
622
631
|
const canvas = canvasRef.current;
|
|
@@ -649,6 +658,18 @@ function useFreehandCanvas(options = {}) {
|
|
|
649
658
|
ctx.stroke();
|
|
650
659
|
}
|
|
651
660
|
}
|
|
661
|
+
const pen = penRef.current;
|
|
662
|
+
if (pen?.engaged && !pen.down) {
|
|
663
|
+
const reach = 5;
|
|
664
|
+
ctx.strokeStyle = "rgba(35, 37, 43, 0.55)";
|
|
665
|
+
ctx.lineWidth = 1;
|
|
666
|
+
ctx.beginPath();
|
|
667
|
+
ctx.moveTo(pen.x - reach, pen.y);
|
|
668
|
+
ctx.lineTo(pen.x + reach, pen.y);
|
|
669
|
+
ctx.moveTo(pen.x, pen.y - reach);
|
|
670
|
+
ctx.lineTo(pen.x, pen.y + reach);
|
|
671
|
+
ctx.stroke();
|
|
672
|
+
}
|
|
652
673
|
}, []);
|
|
653
674
|
const syncDimensions = useCallback4(() => {
|
|
654
675
|
const canvas = canvasRef.current;
|
|
@@ -667,6 +688,8 @@ function useFreehandCanvas(options = {}) {
|
|
|
667
688
|
if (!canvas) return;
|
|
668
689
|
const el = canvas;
|
|
669
690
|
el.style.touchAction = "none";
|
|
691
|
+
if (keyboard && !el.hasAttribute("tabindex")) el.tabIndex = 0;
|
|
692
|
+
if (!penRef.current) penRef.current = { x: el.clientWidth / 2, y: el.clientHeight / 2, down: false, engaged: false };
|
|
670
693
|
syncDimensions();
|
|
671
694
|
redraw();
|
|
672
695
|
const observer = new ResizeObserver(() => {
|
|
@@ -715,6 +738,7 @@ function useFreehandCanvas(options = {}) {
|
|
|
715
738
|
points: [pointFrom(event)]
|
|
716
739
|
};
|
|
717
740
|
activeStrokeRef.current = stroke;
|
|
741
|
+
penRef.current = { x: stroke.points[0].x, y: stroke.points[0].y, down: false, engaged: false };
|
|
718
742
|
drawDot(stroke, stroke.points[0]);
|
|
719
743
|
}
|
|
720
744
|
function onPointerMove(event) {
|
|
@@ -738,18 +762,81 @@ function useFreehandCanvas(options = {}) {
|
|
|
738
762
|
setStrokeCount(strokesRef.current.length);
|
|
739
763
|
optionsRef.current.onStrokeEnd?.(stroke);
|
|
740
764
|
}
|
|
765
|
+
function clampPen(pen) {
|
|
766
|
+
pen.x = Math.min(Math.max(pen.x, 0), Math.max(0, el.clientWidth - 1));
|
|
767
|
+
pen.y = Math.min(Math.max(pen.y, 0), Math.max(0, el.clientHeight - 1));
|
|
768
|
+
}
|
|
769
|
+
function startKeyboardStroke(pen) {
|
|
770
|
+
const { brushSize: size, color: ink } = optionsRef.current;
|
|
771
|
+
const stroke = {
|
|
772
|
+
size,
|
|
773
|
+
color: ink,
|
|
774
|
+
dynamic: false,
|
|
775
|
+
points: [{ x: pen.x, y: pen.y, pressure: 0.5 }]
|
|
776
|
+
};
|
|
777
|
+
activeStrokeRef.current = stroke;
|
|
778
|
+
pen.down = true;
|
|
779
|
+
drawDot(stroke, stroke.points[0]);
|
|
780
|
+
}
|
|
781
|
+
function commitKeyboardStroke(pen) {
|
|
782
|
+
const stroke = activeStrokeRef.current;
|
|
783
|
+
if (!stroke) return;
|
|
784
|
+
activeStrokeRef.current = null;
|
|
785
|
+
pen.down = false;
|
|
786
|
+
strokesRef.current = [...strokesRef.current, stroke];
|
|
787
|
+
setStrokeCount(strokesRef.current.length);
|
|
788
|
+
optionsRef.current.onStrokeEnd?.(stroke);
|
|
789
|
+
redraw();
|
|
790
|
+
}
|
|
791
|
+
function onKeyDown(event) {
|
|
792
|
+
const pen = penRef.current;
|
|
793
|
+
if (!pen) return;
|
|
794
|
+
const fast = event.shiftKey ? KEYBOARD_FAST_FACTOR : 1;
|
|
795
|
+
const step = optionsRef.current.keyboardStep * fast;
|
|
796
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
797
|
+
event.preventDefault();
|
|
798
|
+
pen.engaged = true;
|
|
799
|
+
pen.x += event.key === "ArrowLeft" ? -step : event.key === "ArrowRight" ? step : 0;
|
|
800
|
+
pen.y += event.key === "ArrowUp" ? -step : event.key === "ArrowDown" ? step : 0;
|
|
801
|
+
clampPen(pen);
|
|
802
|
+
const stroke = activeStrokeRef.current;
|
|
803
|
+
if (pen.down && stroke) {
|
|
804
|
+
const point = { x: pen.x, y: pen.y, pressure: 0.5 };
|
|
805
|
+
const previous = stroke.points[stroke.points.length - 1];
|
|
806
|
+
stroke.points.push(point);
|
|
807
|
+
drawSegment(stroke, previous, point);
|
|
808
|
+
} else {
|
|
809
|
+
redraw();
|
|
810
|
+
}
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (event.key === " " || event.key === "Enter") {
|
|
814
|
+
event.preventDefault();
|
|
815
|
+
pen.engaged = true;
|
|
816
|
+
if (pen.down) commitKeyboardStroke(pen);
|
|
817
|
+
else startKeyboardStroke(pen);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if (event.key === "Escape" && pen.down) {
|
|
821
|
+
activeStrokeRef.current = null;
|
|
822
|
+
pen.down = false;
|
|
823
|
+
redraw();
|
|
824
|
+
}
|
|
825
|
+
}
|
|
741
826
|
el.addEventListener("pointerdown", onPointerDown);
|
|
742
827
|
el.addEventListener("pointermove", onPointerMove);
|
|
743
828
|
el.addEventListener("pointerup", commit);
|
|
744
829
|
el.addEventListener("pointercancel", commit);
|
|
830
|
+
if (keyboard) el.addEventListener("keydown", onKeyDown);
|
|
745
831
|
return () => {
|
|
746
832
|
observer.disconnect();
|
|
747
833
|
el.removeEventListener("pointerdown", onPointerDown);
|
|
748
834
|
el.removeEventListener("pointermove", onPointerMove);
|
|
749
835
|
el.removeEventListener("pointerup", commit);
|
|
750
836
|
el.removeEventListener("pointercancel", commit);
|
|
837
|
+
el.removeEventListener("keydown", onKeyDown);
|
|
751
838
|
};
|
|
752
|
-
}, [redraw, syncDimensions]);
|
|
839
|
+
}, [redraw, syncDimensions, keyboard]);
|
|
753
840
|
const undo = useCallback4(() => {
|
|
754
841
|
if (strokesRef.current.length === 0) return;
|
|
755
842
|
strokesRef.current = strokesRef.current.slice(0, -1);
|
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
);
|
|
358
|
+
sheets.push(recordsToSheet("Summary", summary));
|
|
359
|
+
}
|
|
360
|
+
if (sheets.length === 0) {
|
|
361
|
+
sheets.push({ name: options.sheetName || "Sheet1", rows: [] });
|
|
306
362
|
}
|
|
307
|
-
|
|
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
|
package/dist/domain/index.d.mts
CHANGED
|
@@ -2,43 +2,23 @@ export { A as AppStoreMetadata, B as BackgroundPattern, u as BackgroundPatternEn
|
|
|
2
2
|
export { C as CSVExportOptions, E as ExcelExportOptions, a as ExportFormat, P as PDFExportOptions, R as ReportData, b as ReportExportResult, c as ReportItem, d as ReportItemType, e as ReportSummary } from '../report.types-DWDnprBx.mjs';
|
|
3
3
|
export { AIConfig, DEFAULT_DEVICE_CONFIG, DEFAULT_SCREENSHOT_STUDIO_CONFIG, DEVICE_DIMENSIONS, DeviceConfigOption, ExportConfig, ScreenshotStudioConfig } from './config/index.mjs';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Freehand Types
|
|
7
|
-
* Types for pointer-based freehand drawing on a canvas
|
|
8
|
-
*/
|
|
9
|
-
/**
|
|
10
|
-
* A single sampled point along a stroke, in CSS pixels relative to the canvas
|
|
11
|
-
*/
|
|
12
5
|
interface FreehandPoint {
|
|
13
6
|
x: number;
|
|
14
7
|
y: number;
|
|
15
|
-
/** Normalized stylus pressure (0–1). ~0.5 for mouse/touch when unsupported. */
|
|
16
8
|
pressure: number;
|
|
17
9
|
}
|
|
18
|
-
/**
|
|
19
|
-
* A completed (or in-progress) freehand stroke
|
|
20
|
-
*/
|
|
21
10
|
interface FreehandStroke {
|
|
22
|
-
/** Base brush size in CSS pixels. */
|
|
23
11
|
size: number;
|
|
24
|
-
/** CSS color of the stroke. */
|
|
25
12
|
color: string;
|
|
26
|
-
/** True when the stroke should vary width with per-point pressure. */
|
|
27
13
|
dynamic: boolean;
|
|
28
|
-
/** Ordered points from pointer-down to pointer-up. */
|
|
29
14
|
points: FreehandPoint[];
|
|
30
15
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Options for the useFreehandCanvas hook
|
|
33
|
-
*/
|
|
34
16
|
interface UseFreehandCanvasOptions {
|
|
35
|
-
/** Base brush size in CSS pixels. Default: 3 */
|
|
36
17
|
brushSize?: number;
|
|
37
|
-
/** Stroke color. Default: '#1a1a1a' */
|
|
38
18
|
color?: string;
|
|
39
|
-
/** Vary stroke width with stylus pressure (mouse/touch stay constant). Default: true */
|
|
40
19
|
pressureEnabled?: boolean;
|
|
41
|
-
|
|
20
|
+
keyboard?: boolean;
|
|
21
|
+
keyboardStep?: number;
|
|
42
22
|
onStrokeEnd?: (stroke: FreehandStroke) => void;
|
|
43
23
|
}
|
|
44
24
|
|
package/dist/domain/index.d.ts
CHANGED
|
@@ -2,43 +2,23 @@ export { A as AppStoreMetadata, B as BackgroundPattern, u as BackgroundPatternEn
|
|
|
2
2
|
export { C as CSVExportOptions, E as ExcelExportOptions, a as ExportFormat, P as PDFExportOptions, R as ReportData, b as ReportExportResult, c as ReportItem, d as ReportItemType, e as ReportSummary } from '../report.types-DWDnprBx.js';
|
|
3
3
|
export { AIConfig, DEFAULT_DEVICE_CONFIG, DEFAULT_SCREENSHOT_STUDIO_CONFIG, DEVICE_DIMENSIONS, DeviceConfigOption, ExportConfig, ScreenshotStudioConfig } from './config/index.js';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Freehand Types
|
|
7
|
-
* Types for pointer-based freehand drawing on a canvas
|
|
8
|
-
*/
|
|
9
|
-
/**
|
|
10
|
-
* A single sampled point along a stroke, in CSS pixels relative to the canvas
|
|
11
|
-
*/
|
|
12
5
|
interface FreehandPoint {
|
|
13
6
|
x: number;
|
|
14
7
|
y: number;
|
|
15
|
-
/** Normalized stylus pressure (0–1). ~0.5 for mouse/touch when unsupported. */
|
|
16
8
|
pressure: number;
|
|
17
9
|
}
|
|
18
|
-
/**
|
|
19
|
-
* A completed (or in-progress) freehand stroke
|
|
20
|
-
*/
|
|
21
10
|
interface FreehandStroke {
|
|
22
|
-
/** Base brush size in CSS pixels. */
|
|
23
11
|
size: number;
|
|
24
|
-
/** CSS color of the stroke. */
|
|
25
12
|
color: string;
|
|
26
|
-
/** True when the stroke should vary width with per-point pressure. */
|
|
27
13
|
dynamic: boolean;
|
|
28
|
-
/** Ordered points from pointer-down to pointer-up. */
|
|
29
14
|
points: FreehandPoint[];
|
|
30
15
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Options for the useFreehandCanvas hook
|
|
33
|
-
*/
|
|
34
16
|
interface UseFreehandCanvasOptions {
|
|
35
|
-
/** Base brush size in CSS pixels. Default: 3 */
|
|
36
17
|
brushSize?: number;
|
|
37
|
-
/** Stroke color. Default: '#1a1a1a' */
|
|
38
18
|
color?: string;
|
|
39
|
-
/** Vary stroke width with stylus pressure (mouse/touch stay constant). Default: true */
|
|
40
19
|
pressureEnabled?: boolean;
|
|
41
|
-
|
|
20
|
+
keyboard?: boolean;
|
|
21
|
+
keyboardStep?: number;
|
|
42
22
|
onStrokeEnd?: (stroke: FreehandStroke) => void;
|
|
43
23
|
}
|
|
44
24
|
|