@checkflow/sdk 1.1.4 → 1.1.5

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/index.js CHANGED
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
6
9
  var __export = (target, all) => {
7
10
  for (var name in all)
8
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -17,408 +20,248 @@ var __copyProps = (to, from, except, desc) => {
17
20
  };
18
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
22
 
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- captureScreenshot: () => captureScreenshot,
24
- destroy: () => destroy,
25
- hideWidget: () => hideWidget,
26
- init: () => init,
27
- sendFeedback: () => sendFeedback,
28
- showWidget: () => showWidget
29
- });
30
- module.exports = __toCommonJS(index_exports);
31
-
32
- // src/collector.ts
33
- function collectContext() {
34
- const ua = navigator.userAgent;
35
- return {
36
- url: window.location.href,
37
- viewport: {
38
- width: window.innerWidth,
39
- height: window.innerHeight,
40
- device: window.innerWidth < 768 ? "mobile" : window.innerWidth < 1024 ? "tablet" : "desktop"
41
- },
42
- user_agent: ua,
43
- browser: detectBrowser(ua),
44
- os: detectOS(ua),
45
- locale: navigator.language,
46
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
47
- };
48
- }
49
- function collectPerformance() {
50
- try {
51
- const entries = performance.getEntriesByType("paint");
52
- const metrics = {};
53
- entries.forEach((e) => {
54
- if (e.name === "first-contentful-paint") metrics.fcp = Math.round(e.startTime);
55
- });
56
- const nav = performance.getEntriesByType("navigation")[0];
57
- if (nav) {
58
- metrics.dom_load = Math.round(nav.domContentLoadedEventEnd - nav.startTime);
59
- metrics.load = Math.round(nav.loadEventEnd - nav.startTime);
60
- }
61
- return Object.keys(metrics).length > 0 ? metrics : void 0;
62
- } catch {
63
- return void 0;
64
- }
65
- }
66
- function detectBrowser(ua) {
67
- if (ua.includes("Firefox/")) return "Firefox";
68
- if (ua.includes("Edg/")) return "Edge";
69
- if (ua.includes("Chrome/")) return "Chrome";
70
- if (ua.includes("Safari/")) return "Safari";
71
- return "Unknown";
72
- }
73
- function detectOS(ua) {
74
- if (ua.includes("Windows")) return "Windows";
75
- if (ua.includes("Mac OS")) return "macOS";
76
- if (ua.includes("Linux")) return "Linux";
77
- if (ua.includes("Android")) return "Android";
78
- if (ua.includes("iPhone") || ua.includes("iPad")) return "iOS";
79
- return "Unknown";
80
- }
81
-
82
- // src/console-interceptor.ts
83
- var MAX_LOGS = 50;
84
- var logs = [];
85
- var errorsCapture = [];
86
- var installed = false;
87
- function installInterceptors() {
88
- if (installed) return;
89
- installed = true;
90
- const origConsole = {
91
- log: console.log,
92
- warn: console.warn,
93
- error: console.error
94
- };
95
- ["log", "warn", "error"].forEach((level) => {
96
- console[level] = (...args) => {
97
- logs.push({
98
- level,
99
- message: args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" "),
100
- timestamp: Date.now()
101
- });
102
- if (logs.length > MAX_LOGS) logs.shift();
103
- origConsole[level](...args);
23
+ // src/annotation/types.ts
24
+ var DEFAULT_STYLE, HIGHLIGHT_STYLE, BLUR_STYLE, COLOR_PALETTE, STROKE_WIDTHS;
25
+ var init_types = __esm({
26
+ "src/annotation/types.ts"() {
27
+ "use strict";
28
+ DEFAULT_STYLE = {
29
+ strokeColor: "#FF3B30",
30
+ strokeWidth: 3,
31
+ fillColor: "transparent",
32
+ opacity: 1,
33
+ fontSize: 16,
34
+ fontFamily: "system-ui, -apple-system, sans-serif"
104
35
  };
105
- });
106
- window.addEventListener("error", (event) => {
107
- errorsCapture.push({
108
- message: event.message,
109
- filename: event.filename,
110
- lineno: event.lineno,
111
- colno: event.colno,
112
- timestamp: Date.now()
113
- });
114
- if (errorsCapture.length > MAX_LOGS) errorsCapture.shift();
115
- });
116
- window.addEventListener("unhandledrejection", (event) => {
117
- errorsCapture.push({
118
- message: String(event.reason),
119
- type: "unhandledrejection",
120
- timestamp: Date.now()
121
- });
122
- if (errorsCapture.length > MAX_LOGS) errorsCapture.shift();
123
- });
124
- }
125
- function getConsoleLogs() {
126
- return [...logs];
127
- }
128
- function getJavascriptErrors() {
129
- return [...errorsCapture];
130
- }
131
- function clearLogs() {
132
- logs = [];
133
- errorsCapture = [];
134
- }
135
-
136
- // src/network-interceptor.ts
137
- var MAX_LOGS2 = 50;
138
- var logs2 = [];
139
- var installed2 = false;
140
- function installNetworkInterceptor() {
141
- if (installed2 || typeof window === "undefined") return;
142
- installed2 = true;
143
- const origFetch = window.fetch;
144
- window.fetch = async function(...args) {
145
- const start = Date.now();
146
- const req = new Request(...args);
147
- const entry = {
148
- method: req.method,
149
- url: req.url,
150
- type: "fetch",
151
- timestamp: start
36
+ HIGHLIGHT_STYLE = {
37
+ strokeColor: "transparent",
38
+ fillColor: "#FFEB3B",
39
+ opacity: 0.4
152
40
  };
153
- try {
154
- const res = await origFetch.apply(this, args);
155
- entry.status = res.status;
156
- entry.duration = Date.now() - start;
157
- entry.response_type = res.headers.get("content-type") || void 0;
158
- pushLog(entry);
159
- return res;
160
- } catch (err) {
161
- entry.duration = Date.now() - start;
162
- entry.error = err.message || "Network error";
163
- pushLog(entry);
164
- throw err;
165
- }
166
- };
167
- const origOpen = XMLHttpRequest.prototype.open;
168
- const origSend = XMLHttpRequest.prototype.send;
169
- XMLHttpRequest.prototype.open = function(method, url, ...rest) {
170
- this.__cf_method = method;
171
- this.__cf_url = String(url);
172
- return origOpen.apply(this, [method, url, ...rest]);
173
- };
174
- XMLHttpRequest.prototype.send = function(...args) {
175
- const start = Date.now();
176
- const xhr = this;
177
- xhr.addEventListener("loadend", () => {
178
- const entry = {
179
- method: xhr.__cf_method || "GET",
180
- url: xhr.__cf_url || "",
181
- status: xhr.status,
182
- duration: Date.now() - start,
183
- type: "xhr",
184
- timestamp: start,
185
- response_type: xhr.getResponseHeader("content-type") || void 0
186
- };
187
- if (xhr.status === 0) entry.error = "Request failed";
188
- pushLog(entry);
189
- });
190
- return origSend.apply(this, args);
191
- };
192
- }
193
- function pushLog(entry) {
194
- if (entry.url.includes("/sdk/feedback")) return;
195
- logs2.push(entry);
196
- if (logs2.length > MAX_LOGS2) logs2.shift();
197
- }
198
- function getNetworkLogs() {
199
- return [...logs2];
200
- }
201
- function clearNetworkLogs() {
202
- logs2 = [];
203
- }
204
-
205
- // src/annotation/types.ts
206
- var DEFAULT_STYLE = {
207
- strokeColor: "#FF3B30",
208
- strokeWidth: 3,
209
- fillColor: "transparent",
210
- opacity: 1,
211
- fontSize: 16,
212
- fontFamily: "system-ui, -apple-system, sans-serif"
213
- };
214
- var HIGHLIGHT_STYLE = {
215
- strokeColor: "transparent",
216
- fillColor: "#FFEB3B",
217
- opacity: 0.4
218
- };
219
- var BLUR_STYLE = {
220
- strokeColor: "#666666",
221
- fillColor: "#666666",
222
- opacity: 0.8
223
- };
224
- var COLOR_PALETTE = [
225
- "#FF3B30",
226
- // Red
227
- "#FF9500",
228
- // Orange
229
- "#FFCC00",
230
- // Yellow
231
- "#34C759",
232
- // Green
233
- "#007AFF",
234
- // Blue
235
- "#5856D6",
236
- // Purple
237
- "#AF52DE",
238
- // Pink
239
- "#000000",
240
- // Black
241
- "#FFFFFF"
242
- // White
243
- ];
244
- var STROKE_WIDTHS = [1, 2, 3, 5, 8];
41
+ BLUR_STYLE = {
42
+ strokeColor: "#666666",
43
+ fillColor: "#666666",
44
+ opacity: 0.8
45
+ };
46
+ COLOR_PALETTE = [
47
+ "#FF3B30",
48
+ // Red
49
+ "#FF9500",
50
+ // Orange
51
+ "#FFCC00",
52
+ // Yellow
53
+ "#34C759",
54
+ // Green
55
+ "#007AFF",
56
+ // Blue
57
+ "#5856D6",
58
+ // Purple
59
+ "#AF52DE",
60
+ // Pink
61
+ "#000000",
62
+ // Black
63
+ "#FFFFFF"
64
+ // White
65
+ ];
66
+ STROKE_WIDTHS = [1, 2, 3, 5, 8];
67
+ }
68
+ });
245
69
 
246
70
  // src/annotation/toolbar.ts
247
- var TOOL_ICONS = {
248
- select: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/><path d="M13 13l6 6"/></svg>`,
249
- rectangle: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>`,
250
- ellipse: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="12" rx="9" ry="6"/></svg>`,
251
- arrow: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>`,
252
- line: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="19" x2="19" y2="5"/></svg>`,
253
- highlight: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>`,
254
- blur: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 13c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm-3 .5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM6 5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm15 5.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM14 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-3.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm-11 10c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm7 7c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm0-17c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM10 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0 5.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm8 .5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm3 8.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM14 17c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm-4-12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0 8.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm4-4.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-4c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5z"/></svg>`,
255
- text: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 4v3h5.5v12h3V7H19V4z"/></svg>`,
256
- freehand: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/></svg>`
257
- };
258
- var TOOL_LABELS = {
259
- select: "S\xE9lection",
260
- rectangle: "Rectangle",
261
- ellipse: "Ellipse",
262
- arrow: "Fl\xE8che",
263
- line: "Ligne",
264
- highlight: "Surbrillance",
265
- blur: "Floutage",
266
- text: "Texte",
267
- freehand: "Dessin libre"
268
- };
269
- var AnnotationToolbar = class {
270
- constructor(config2) {
271
- this.colorPicker = null;
272
- this.strokePicker = null;
273
- this.config = config2;
274
- this.element = this.createToolbar();
275
- }
276
- getElement() {
277
- return this.element;
278
- }
279
- setActiveTool(tool) {
280
- this.config.activeTool = tool;
281
- this.updateActiveState();
282
- }
283
- setStyle(style) {
284
- this.config.style = style;
285
- this.updateStyleDisplay();
286
- }
287
- createToolbar() {
288
- const toolbar = document.createElement("div");
289
- toolbar.className = "cf-toolbar";
290
- const toolsSection = document.createElement("div");
291
- toolsSection.className = "cf-toolbar-section cf-toolbar-tools";
292
- for (const tool of this.config.tools) {
293
- const button = this.createToolButton(tool);
294
- toolsSection.appendChild(button);
295
- }
296
- const separator1 = document.createElement("div");
297
- separator1.className = "cf-toolbar-separator";
298
- const styleSection = document.createElement("div");
299
- styleSection.className = "cf-toolbar-section cf-toolbar-style";
300
- const colorBtn = document.createElement("button");
301
- colorBtn.className = "cf-toolbar-btn cf-color-btn";
302
- colorBtn.title = "Couleur";
303
- colorBtn.innerHTML = `<span class="cf-color-preview" style="background: ${this.config.style.strokeColor}"></span>`;
304
- colorBtn.addEventListener("click", () => this.toggleColorPicker());
305
- styleSection.appendChild(colorBtn);
306
- const strokeBtn = document.createElement("button");
307
- strokeBtn.className = "cf-toolbar-btn cf-stroke-btn";
308
- strokeBtn.title = "\xC9paisseur";
309
- strokeBtn.innerHTML = `<span class="cf-stroke-preview">${this.config.style.strokeWidth}px</span>`;
310
- strokeBtn.addEventListener("click", () => this.toggleStrokePicker());
311
- styleSection.appendChild(strokeBtn);
312
- const separator2 = document.createElement("div");
313
- separator2.className = "cf-toolbar-separator";
314
- const actionsSection = document.createElement("div");
315
- actionsSection.className = "cf-toolbar-section cf-toolbar-actions";
316
- const undoBtn = document.createElement("button");
317
- undoBtn.className = "cf-toolbar-btn";
318
- undoBtn.title = "Annuler (Ctrl+Z)";
319
- undoBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 00-9-9 9 9 0 00-6 2.3L3 13"/></svg>`;
320
- undoBtn.addEventListener("click", () => this.config.onUndo());
321
- actionsSection.appendChild(undoBtn);
322
- const clearBtn = document.createElement("button");
323
- clearBtn.className = "cf-toolbar-btn";
324
- clearBtn.title = "Tout effacer";
325
- clearBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>`;
326
- clearBtn.addEventListener("click", () => this.config.onClear());
327
- actionsSection.appendChild(clearBtn);
328
- toolbar.appendChild(toolsSection);
329
- toolbar.appendChild(separator1);
330
- toolbar.appendChild(styleSection);
331
- toolbar.appendChild(separator2);
332
- toolbar.appendChild(actionsSection);
333
- return toolbar;
334
- }
335
- createToolButton(tool) {
336
- const button = document.createElement("button");
337
- button.className = `cf-toolbar-btn cf-tool-btn ${tool === this.config.activeTool ? "active" : ""}`;
338
- button.dataset.tool = tool;
339
- button.title = TOOL_LABELS[tool];
340
- button.innerHTML = TOOL_ICONS[tool];
341
- button.addEventListener("click", () => {
342
- this.config.onToolChange(tool);
343
- this.setActiveTool(tool);
344
- });
345
- return button;
346
- }
347
- updateActiveState() {
348
- const buttons = this.element.querySelectorAll(".cf-tool-btn");
349
- buttons.forEach((btn) => {
350
- const button = btn;
351
- button.classList.toggle("active", button.dataset.tool === this.config.activeTool);
352
- });
353
- }
354
- updateStyleDisplay() {
355
- const colorPreview = this.element.querySelector(".cf-color-preview");
356
- if (colorPreview) {
357
- colorPreview.style.background = this.config.style.strokeColor;
358
- }
359
- const strokePreview = this.element.querySelector(".cf-stroke-preview");
360
- if (strokePreview) {
361
- strokePreview.textContent = `${this.config.style.strokeWidth}px`;
362
- }
363
- }
364
- toggleColorPicker() {
365
- if (this.colorPicker) {
366
- this.colorPicker.remove();
367
- this.colorPicker = null;
368
- return;
369
- }
370
- if (this.strokePicker) {
371
- this.strokePicker.remove();
372
- this.strokePicker = null;
373
- }
374
- this.colorPicker = document.createElement("div");
375
- this.colorPicker.className = "cf-picker cf-color-picker";
376
- for (const color of COLOR_PALETTE) {
377
- const swatch = document.createElement("button");
378
- swatch.className = `cf-color-swatch ${color === this.config.style.strokeColor ? "active" : ""}`;
379
- swatch.style.background = color;
380
- swatch.addEventListener("click", () => {
381
- this.config.onStyleChange({ strokeColor: color });
382
- this.setStyle({ ...this.config.style, strokeColor: color });
383
- this.colorPicker?.remove();
71
+ var TOOL_ICONS, TOOL_LABELS, AnnotationToolbar;
72
+ var init_toolbar = __esm({
73
+ "src/annotation/toolbar.ts"() {
74
+ "use strict";
75
+ init_types();
76
+ TOOL_ICONS = {
77
+ select: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/><path d="M13 13l6 6"/></svg>`,
78
+ rectangle: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>`,
79
+ ellipse: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="12" rx="9" ry="6"/></svg>`,
80
+ arrow: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>`,
81
+ line: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="19" x2="19" y2="5"/></svg>`,
82
+ highlight: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>`,
83
+ blur: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 13c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm-3 .5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM6 5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm15 5.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM14 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-3.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm-11 10c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm7 7c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm0-17c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM10 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0 5.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm8 .5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm3 8.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM14 17c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm-4-12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0 8.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm4-4.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-4c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5z"/></svg>`,
84
+ text: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 4v3h5.5v12h3V7H19V4z"/></svg>`,
85
+ freehand: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/></svg>`
86
+ };
87
+ TOOL_LABELS = {
88
+ select: "S\xE9lection",
89
+ rectangle: "Rectangle",
90
+ ellipse: "Ellipse",
91
+ arrow: "Fl\xE8che",
92
+ line: "Ligne",
93
+ highlight: "Surbrillance",
94
+ blur: "Floutage",
95
+ text: "Texte",
96
+ freehand: "Dessin libre"
97
+ };
98
+ AnnotationToolbar = class {
99
+ constructor(config2) {
384
100
  this.colorPicker = null;
385
- });
386
- this.colorPicker.appendChild(swatch);
387
- }
388
- const colorBtn = this.element.querySelector(".cf-color-btn");
389
- colorBtn?.appendChild(this.colorPicker);
390
- }
391
- toggleStrokePicker() {
392
- if (this.strokePicker) {
393
- this.strokePicker.remove();
394
- this.strokePicker = null;
395
- return;
396
- }
397
- if (this.colorPicker) {
398
- this.colorPicker.remove();
399
- this.colorPicker = null;
400
- }
401
- this.strokePicker = document.createElement("div");
402
- this.strokePicker.className = "cf-picker cf-stroke-picker";
403
- for (const width of STROKE_WIDTHS) {
404
- const option = document.createElement("button");
405
- option.className = `cf-stroke-option ${width === this.config.style.strokeWidth ? "active" : ""}`;
406
- option.innerHTML = `<span style="height: ${width}px"></span> ${width}px`;
407
- option.addEventListener("click", () => {
408
- this.config.onStyleChange({ strokeWidth: width });
409
- this.setStyle({ ...this.config.style, strokeWidth: width });
410
- this.strokePicker?.remove();
411
101
  this.strokePicker = null;
412
- });
413
- this.strokePicker.appendChild(option);
414
- }
415
- const strokeBtn = this.element.querySelector(".cf-stroke-btn");
416
- strokeBtn?.appendChild(this.strokePicker);
102
+ this.config = config2;
103
+ this.element = this.createToolbar();
104
+ }
105
+ getElement() {
106
+ return this.element;
107
+ }
108
+ setActiveTool(tool) {
109
+ this.config.activeTool = tool;
110
+ this.updateActiveState();
111
+ }
112
+ setStyle(style) {
113
+ this.config.style = style;
114
+ this.updateStyleDisplay();
115
+ }
116
+ createToolbar() {
117
+ const toolbar = document.createElement("div");
118
+ toolbar.className = "cf-toolbar";
119
+ const toolsSection = document.createElement("div");
120
+ toolsSection.className = "cf-toolbar-section cf-toolbar-tools";
121
+ for (const tool of this.config.tools) {
122
+ const button = this.createToolButton(tool);
123
+ toolsSection.appendChild(button);
124
+ }
125
+ const separator1 = document.createElement("div");
126
+ separator1.className = "cf-toolbar-separator";
127
+ const styleSection = document.createElement("div");
128
+ styleSection.className = "cf-toolbar-section cf-toolbar-style";
129
+ const colorBtn = document.createElement("button");
130
+ colorBtn.className = "cf-toolbar-btn cf-color-btn";
131
+ colorBtn.title = "Couleur";
132
+ colorBtn.innerHTML = `<span class="cf-color-preview" style="background: ${this.config.style.strokeColor}"></span>`;
133
+ colorBtn.addEventListener("click", () => this.toggleColorPicker());
134
+ styleSection.appendChild(colorBtn);
135
+ const strokeBtn = document.createElement("button");
136
+ strokeBtn.className = "cf-toolbar-btn cf-stroke-btn";
137
+ strokeBtn.title = "\xC9paisseur";
138
+ strokeBtn.innerHTML = `<span class="cf-stroke-preview">${this.config.style.strokeWidth}px</span>`;
139
+ strokeBtn.addEventListener("click", () => this.toggleStrokePicker());
140
+ styleSection.appendChild(strokeBtn);
141
+ const separator2 = document.createElement("div");
142
+ separator2.className = "cf-toolbar-separator";
143
+ const actionsSection = document.createElement("div");
144
+ actionsSection.className = "cf-toolbar-section cf-toolbar-actions";
145
+ const undoBtn = document.createElement("button");
146
+ undoBtn.className = "cf-toolbar-btn";
147
+ undoBtn.title = "Annuler (Ctrl+Z)";
148
+ undoBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 00-9-9 9 9 0 00-6 2.3L3 13"/></svg>`;
149
+ undoBtn.addEventListener("click", () => this.config.onUndo());
150
+ actionsSection.appendChild(undoBtn);
151
+ const clearBtn = document.createElement("button");
152
+ clearBtn.className = "cf-toolbar-btn";
153
+ clearBtn.title = "Tout effacer";
154
+ clearBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>`;
155
+ clearBtn.addEventListener("click", () => this.config.onClear());
156
+ actionsSection.appendChild(clearBtn);
157
+ toolbar.appendChild(toolsSection);
158
+ toolbar.appendChild(separator1);
159
+ toolbar.appendChild(styleSection);
160
+ toolbar.appendChild(separator2);
161
+ toolbar.appendChild(actionsSection);
162
+ return toolbar;
163
+ }
164
+ createToolButton(tool) {
165
+ const button = document.createElement("button");
166
+ button.className = `cf-toolbar-btn cf-tool-btn ${tool === this.config.activeTool ? "active" : ""}`;
167
+ button.dataset.tool = tool;
168
+ button.title = TOOL_LABELS[tool];
169
+ button.innerHTML = TOOL_ICONS[tool];
170
+ button.addEventListener("click", () => {
171
+ this.config.onToolChange(tool);
172
+ this.setActiveTool(tool);
173
+ });
174
+ return button;
175
+ }
176
+ updateActiveState() {
177
+ const buttons = this.element.querySelectorAll(".cf-tool-btn");
178
+ buttons.forEach((btn) => {
179
+ const button = btn;
180
+ button.classList.toggle("active", button.dataset.tool === this.config.activeTool);
181
+ });
182
+ }
183
+ updateStyleDisplay() {
184
+ const colorPreview = this.element.querySelector(".cf-color-preview");
185
+ if (colorPreview) {
186
+ colorPreview.style.background = this.config.style.strokeColor;
187
+ }
188
+ const strokePreview = this.element.querySelector(".cf-stroke-preview");
189
+ if (strokePreview) {
190
+ strokePreview.textContent = `${this.config.style.strokeWidth}px`;
191
+ }
192
+ }
193
+ toggleColorPicker() {
194
+ if (this.colorPicker) {
195
+ this.colorPicker.remove();
196
+ this.colorPicker = null;
197
+ return;
198
+ }
199
+ if (this.strokePicker) {
200
+ this.strokePicker.remove();
201
+ this.strokePicker = null;
202
+ }
203
+ this.colorPicker = document.createElement("div");
204
+ this.colorPicker.className = "cf-picker cf-color-picker";
205
+ for (const color of COLOR_PALETTE) {
206
+ const swatch = document.createElement("button");
207
+ swatch.className = `cf-color-swatch ${color === this.config.style.strokeColor ? "active" : ""}`;
208
+ swatch.style.background = color;
209
+ swatch.addEventListener("click", () => {
210
+ this.config.onStyleChange({ strokeColor: color });
211
+ this.setStyle({ ...this.config.style, strokeColor: color });
212
+ this.colorPicker?.remove();
213
+ this.colorPicker = null;
214
+ });
215
+ this.colorPicker.appendChild(swatch);
216
+ }
217
+ const colorBtn = this.element.querySelector(".cf-color-btn");
218
+ colorBtn?.appendChild(this.colorPicker);
219
+ }
220
+ toggleStrokePicker() {
221
+ if (this.strokePicker) {
222
+ this.strokePicker.remove();
223
+ this.strokePicker = null;
224
+ return;
225
+ }
226
+ if (this.colorPicker) {
227
+ this.colorPicker.remove();
228
+ this.colorPicker = null;
229
+ }
230
+ this.strokePicker = document.createElement("div");
231
+ this.strokePicker.className = "cf-picker cf-stroke-picker";
232
+ for (const width of STROKE_WIDTHS) {
233
+ const option = document.createElement("button");
234
+ option.className = `cf-stroke-option ${width === this.config.style.strokeWidth ? "active" : ""}`;
235
+ option.innerHTML = `<span style="height: ${width}px"></span> ${width}px`;
236
+ option.addEventListener("click", () => {
237
+ this.config.onStyleChange({ strokeWidth: width });
238
+ this.setStyle({ ...this.config.style, strokeWidth: width });
239
+ this.strokePicker?.remove();
240
+ this.strokePicker = null;
241
+ });
242
+ this.strokePicker.appendChild(option);
243
+ }
244
+ const strokeBtn = this.element.querySelector(".cf-stroke-btn");
245
+ strokeBtn?.appendChild(this.strokePicker);
246
+ }
247
+ };
417
248
  }
418
- };
249
+ });
419
250
 
420
251
  // src/annotation/styles.ts
421
- var STYLES = `
252
+ function injectAnnotationStyles() {
253
+ if (stylesInjected) return;
254
+ const styleElement = document.createElement("style");
255
+ styleElement.id = "cf-annotation-styles";
256
+ styleElement.textContent = STYLES;
257
+ document.head.appendChild(styleElement);
258
+ stylesInjected = true;
259
+ }
260
+ var STYLES, stylesInjected;
261
+ var init_styles = __esm({
262
+ "src/annotation/styles.ts"() {
263
+ "use strict";
264
+ STYLES = `
422
265
  /* Annotation Overlay */
423
266
  .cf-annotation-overlay {
424
267
  position: fixed;
@@ -709,598 +552,595 @@ var STYLES = `
709
552
  }
710
553
  }
711
554
  `;
712
- var stylesInjected = false;
713
- function injectAnnotationStyles() {
714
- if (stylesInjected) return;
715
- const styleElement = document.createElement("style");
716
- styleElement.id = "cf-annotation-styles";
717
- styleElement.textContent = STYLES;
718
- document.head.appendChild(styleElement);
719
- stylesInjected = true;
720
- }
555
+ stylesInjected = false;
556
+ }
557
+ });
721
558
 
722
559
  // src/annotation/editor.ts
723
- var AnnotationEditor = class {
724
- constructor(config2) {
725
- this.container = null;
726
- this.canvas = null;
727
- this.ctx = null;
728
- this.toolbar = null;
729
- this.annotations = [];
730
- this.backgroundImage = null;
731
- this.state = {
732
- activeTool: "rectangle",
733
- style: { ...DEFAULT_STYLE },
734
- isDrawing: false,
735
- currentAnnotation: null
736
- };
737
- this.startPoint = null;
738
- this.freehandPoints = [];
739
- this.textInput = null;
740
- this.config = {
741
- ...config2,
742
- tools: config2.tools || ["select", "rectangle", "arrow", "highlight", "blur", "text", "freehand"],
743
- defaultStyle: config2.defaultStyle || DEFAULT_STYLE
744
- };
745
- this.state.style = { ...this.config.defaultStyle };
746
- }
747
- /**
748
- * Open the annotation editor with a screenshot
749
- */
750
- async open(screenshotDataUrl2) {
751
- injectAnnotationStyles();
752
- this.backgroundImage = await this.loadImage(screenshotDataUrl2);
753
- this.createEditorUI();
754
- this.setupEventListeners();
755
- this.render();
756
- }
757
- /**
758
- * Close the editor
759
- */
760
- close() {
761
- if (this.container) {
762
- this.container.remove();
763
- this.container = null;
764
- }
765
- this.canvas = null;
766
- this.ctx = null;
767
- this.toolbar = null;
768
- this.annotations = [];
769
- this.backgroundImage = null;
770
- }
771
- /**
772
- * Get the annotated image as data URL
773
- */
774
- getAnnotatedImage() {
775
- if (!this.canvas || !this.ctx) return "";
776
- const tempCanvas = document.createElement("canvas");
777
- tempCanvas.width = this.canvas.width;
778
- tempCanvas.height = this.canvas.height;
779
- const tempCtx = tempCanvas.getContext("2d");
780
- if (this.backgroundImage) {
781
- tempCtx.drawImage(this.backgroundImage, 0, 0);
782
- }
783
- this.drawAnnotations(tempCtx);
784
- return tempCanvas.toDataURL("image/png");
785
- }
786
- /**
787
- * Get annotations data
788
- */
789
- getAnnotations() {
790
- return [...this.annotations];
791
- }
792
- // Private methods
793
- loadImage(src) {
794
- return new Promise((resolve, reject) => {
795
- const img = new Image();
796
- img.onload = () => resolve(img);
797
- img.onerror = reject;
798
- img.src = src;
799
- });
800
- }
801
- createEditorUI() {
802
- this.container = document.createElement("div");
803
- this.container.className = "cf-annotation-overlay";
804
- const wrapper = document.createElement("div");
805
- wrapper.className = "cf-annotation-wrapper";
806
- const canvasContainer = document.createElement("div");
807
- canvasContainer.className = "cf-annotation-canvas-container";
808
- this.canvas = document.createElement("canvas");
809
- this.canvas.className = "cf-annotation-canvas";
810
- this.canvas.width = this.backgroundImage?.width || 1920;
811
- this.canvas.height = this.backgroundImage?.height || 1080;
812
- this.ctx = this.canvas.getContext("2d");
813
- canvasContainer.appendChild(this.canvas);
814
- this.toolbar = new AnnotationToolbar({
815
- tools: this.config.tools,
816
- activeTool: this.state.activeTool,
817
- style: this.state.style,
818
- onToolChange: (tool) => this.setActiveTool(tool),
819
- onStyleChange: (style) => this.setStyle(style),
820
- onUndo: () => this.undo(),
821
- onClear: () => this.clearAll(),
822
- onSave: () => this.save(),
823
- onCancel: () => this.cancel()
824
- });
825
- const actions = document.createElement("div");
826
- actions.className = "cf-annotation-actions";
827
- actions.innerHTML = `
560
+ var AnnotationEditor;
561
+ var init_editor = __esm({
562
+ "src/annotation/editor.ts"() {
563
+ "use strict";
564
+ init_types();
565
+ init_toolbar();
566
+ init_styles();
567
+ AnnotationEditor = class {
568
+ constructor(config2) {
569
+ this.container = null;
570
+ this.canvas = null;
571
+ this.ctx = null;
572
+ this.toolbar = null;
573
+ this.annotations = [];
574
+ this.backgroundImage = null;
575
+ this.state = {
576
+ activeTool: "rectangle",
577
+ style: { ...DEFAULT_STYLE },
578
+ isDrawing: false,
579
+ currentAnnotation: null
580
+ };
581
+ this.startPoint = null;
582
+ this.freehandPoints = [];
583
+ this.textInput = null;
584
+ this.config = {
585
+ ...config2,
586
+ tools: config2.tools || ["select", "rectangle", "arrow", "highlight", "blur", "text", "freehand"],
587
+ defaultStyle: config2.defaultStyle || DEFAULT_STYLE
588
+ };
589
+ this.state.style = { ...this.config.defaultStyle };
590
+ }
591
+ /**
592
+ * Open the annotation editor with a screenshot
593
+ */
594
+ async open(screenshotDataUrl2) {
595
+ injectAnnotationStyles();
596
+ this.backgroundImage = await this.loadImage(screenshotDataUrl2);
597
+ this.createEditorUI();
598
+ this.setupEventListeners();
599
+ this.render();
600
+ }
601
+ /**
602
+ * Close the editor
603
+ */
604
+ close() {
605
+ if (this.container) {
606
+ this.container.remove();
607
+ this.container = null;
608
+ }
609
+ this.canvas = null;
610
+ this.ctx = null;
611
+ this.toolbar = null;
612
+ this.annotations = [];
613
+ this.backgroundImage = null;
614
+ }
615
+ /**
616
+ * Get the annotated image as data URL
617
+ */
618
+ getAnnotatedImage() {
619
+ if (!this.canvas || !this.ctx) return "";
620
+ const tempCanvas = document.createElement("canvas");
621
+ tempCanvas.width = this.canvas.width;
622
+ tempCanvas.height = this.canvas.height;
623
+ const tempCtx = tempCanvas.getContext("2d");
624
+ if (this.backgroundImage) {
625
+ tempCtx.drawImage(this.backgroundImage, 0, 0);
626
+ }
627
+ this.drawAnnotations(tempCtx);
628
+ return tempCanvas.toDataURL("image/png");
629
+ }
630
+ /**
631
+ * Get annotations data
632
+ */
633
+ getAnnotations() {
634
+ return [...this.annotations];
635
+ }
636
+ // Private methods
637
+ loadImage(src) {
638
+ return new Promise((resolve, reject) => {
639
+ const img = new Image();
640
+ img.onload = () => resolve(img);
641
+ img.onerror = reject;
642
+ img.src = src;
643
+ });
644
+ }
645
+ createEditorUI() {
646
+ this.container = document.createElement("div");
647
+ this.container.className = "cf-annotation-overlay";
648
+ const wrapper = document.createElement("div");
649
+ wrapper.className = "cf-annotation-wrapper";
650
+ const canvasContainer = document.createElement("div");
651
+ canvasContainer.className = "cf-annotation-canvas-container";
652
+ this.canvas = document.createElement("canvas");
653
+ this.canvas.className = "cf-annotation-canvas";
654
+ this.canvas.width = this.backgroundImage?.width || 1920;
655
+ this.canvas.height = this.backgroundImage?.height || 1080;
656
+ this.ctx = this.canvas.getContext("2d");
657
+ canvasContainer.appendChild(this.canvas);
658
+ this.toolbar = new AnnotationToolbar({
659
+ tools: this.config.tools,
660
+ activeTool: this.state.activeTool,
661
+ style: this.state.style,
662
+ onToolChange: (tool) => this.setActiveTool(tool),
663
+ onStyleChange: (style) => this.setStyle(style),
664
+ onUndo: () => this.undo(),
665
+ onClear: () => this.clearAll(),
666
+ onSave: () => this.save(),
667
+ onCancel: () => this.cancel()
668
+ });
669
+ const actions = document.createElement("div");
670
+ actions.className = "cf-annotation-actions";
671
+ actions.innerHTML = `
828
672
  <button class="cf-btn cf-btn-secondary" data-action="cancel">Annuler</button>
829
673
  <button class="cf-btn cf-btn-primary" data-action="save">Enregistrer</button>
830
674
  `;
831
- wrapper.appendChild(this.toolbar.getElement());
832
- wrapper.appendChild(canvasContainer);
833
- wrapper.appendChild(actions);
834
- this.container.appendChild(wrapper);
835
- document.body.appendChild(this.container);
836
- actions.querySelector('[data-action="cancel"]')?.addEventListener("click", () => this.cancel());
837
- actions.querySelector('[data-action="save"]')?.addEventListener("click", () => this.save());
838
- }
839
- setupEventListeners() {
840
- if (!this.canvas) return;
841
- this.canvas.addEventListener("mousedown", this.handleMouseDown.bind(this));
842
- this.canvas.addEventListener("mousemove", this.handleMouseMove.bind(this));
843
- this.canvas.addEventListener("mouseup", this.handleMouseUp.bind(this));
844
- this.canvas.addEventListener("mouseleave", this.handleMouseUp.bind(this));
845
- this.canvas.addEventListener("touchstart", this.handleTouchStart.bind(this));
846
- this.canvas.addEventListener("touchmove", this.handleTouchMove.bind(this));
847
- this.canvas.addEventListener("touchend", this.handleTouchEnd.bind(this));
848
- document.addEventListener("keydown", this.handleKeyDown.bind(this));
849
- }
850
- handleMouseDown(e) {
851
- const point = this.getCanvasPoint(e);
852
- this.startDrawing(point);
853
- }
854
- handleMouseMove(e) {
855
- const point = this.getCanvasPoint(e);
856
- this.continueDrawing(point);
857
- }
858
- handleMouseUp(_e) {
859
- this.finishDrawing();
860
- }
861
- handleTouchStart(e) {
862
- e.preventDefault();
863
- const touch = e.touches[0];
864
- const point = this.getCanvasPointFromTouch(touch);
865
- this.startDrawing(point);
866
- }
867
- handleTouchMove(e) {
868
- e.preventDefault();
869
- const touch = e.touches[0];
870
- const point = this.getCanvasPointFromTouch(touch);
871
- this.continueDrawing(point);
872
- }
873
- handleTouchEnd(e) {
874
- e.preventDefault();
875
- this.finishDrawing();
876
- }
877
- handleKeyDown(e) {
878
- if ((e.ctrlKey || e.metaKey) && e.key === "z") {
879
- e.preventDefault();
880
- this.undo();
881
- }
882
- if (e.key === "Escape") {
883
- if (this.state.isDrawing) {
884
- this.state.isDrawing = false;
885
- this.state.currentAnnotation = null;
886
- this.render();
887
- } else {
888
- this.cancel();
675
+ wrapper.appendChild(this.toolbar.getElement());
676
+ wrapper.appendChild(canvasContainer);
677
+ wrapper.appendChild(actions);
678
+ this.container.appendChild(wrapper);
679
+ document.body.appendChild(this.container);
680
+ actions.querySelector('[data-action="cancel"]')?.addEventListener("click", () => this.cancel());
681
+ actions.querySelector('[data-action="save"]')?.addEventListener("click", () => this.save());
889
682
  }
890
- }
891
- if (e.key === "Enter" && !this.state.isDrawing) {
892
- this.save();
893
- }
894
- }
895
- getCanvasPoint(e) {
896
- const rect = this.canvas.getBoundingClientRect();
897
- const scaleX = this.canvas.width / rect.width;
898
- const scaleY = this.canvas.height / rect.height;
899
- return {
900
- x: (e.clientX - rect.left) * scaleX,
901
- y: (e.clientY - rect.top) * scaleY
902
- };
903
- }
904
- getCanvasPointFromTouch(touch) {
905
- const rect = this.canvas.getBoundingClientRect();
906
- const scaleX = this.canvas.width / rect.width;
907
- const scaleY = this.canvas.height / rect.height;
908
- return {
909
- x: (touch.clientX - rect.left) * scaleX,
910
- y: (touch.clientY - rect.top) * scaleY
911
- };
912
- }
913
- startDrawing(point) {
914
- if (this.state.activeTool === "select") return;
915
- this.state.isDrawing = true;
916
- this.startPoint = point;
917
- if (this.state.activeTool === "text") {
918
- this.showTextInput(point);
919
- return;
920
- }
921
- if (this.state.activeTool === "freehand") {
922
- this.freehandPoints = [point];
923
- }
924
- }
925
- continueDrawing(point) {
926
- if (!this.state.isDrawing || !this.startPoint) return;
927
- if (this.state.activeTool === "freehand") {
928
- this.freehandPoints.push(point);
929
- }
930
- this.state.currentAnnotation = this.createAnnotation(this.startPoint, point);
931
- this.render();
932
- }
933
- finishDrawing() {
934
- if (!this.state.isDrawing || !this.state.currentAnnotation) {
935
- this.state.isDrawing = false;
936
- return;
937
- }
938
- if (this.isValidAnnotation(this.state.currentAnnotation)) {
939
- this.annotations.push(this.state.currentAnnotation);
940
- }
941
- this.state.isDrawing = false;
942
- this.state.currentAnnotation = null;
943
- this.startPoint = null;
944
- this.freehandPoints = [];
945
- this.render();
946
- }
947
- isValidAnnotation(annotation) {
948
- switch (annotation.type) {
949
- case "rectangle":
950
- case "ellipse":
951
- case "highlight":
952
- case "blur":
953
- const bounds = annotation.bounds;
954
- return Math.abs(bounds.width) > 5 && Math.abs(bounds.height) > 5;
955
- case "arrow":
956
- case "line":
957
- const start = annotation.start;
958
- const end = annotation.end;
959
- const dist = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
960
- return dist > 10;
961
- case "freehand":
962
- return annotation.points.length > 2;
963
- case "text":
964
- return !!annotation.text?.trim();
965
- default:
966
- return true;
967
- }
968
- }
969
- createAnnotation(start, end) {
970
- const id = this.generateId();
971
- const baseStyle = { ...this.state.style };
972
- switch (this.state.activeTool) {
973
- case "rectangle":
974
- return {
975
- id,
976
- type: "rectangle",
977
- bounds: this.createBounds(start, end),
978
- style: baseStyle,
979
- timestamp: Date.now()
980
- };
981
- case "ellipse":
982
- return {
983
- id,
984
- type: "ellipse",
985
- bounds: this.createBounds(start, end),
986
- style: baseStyle,
987
- timestamp: Date.now()
988
- };
989
- case "arrow":
990
- return {
991
- id,
992
- type: "arrow",
993
- start: { ...start },
994
- end: { ...end },
995
- style: baseStyle,
996
- timestamp: Date.now()
997
- };
998
- case "line":
999
- return {
1000
- id,
1001
- type: "line",
1002
- start: { ...start },
1003
- end: { ...end },
1004
- style: baseStyle,
1005
- timestamp: Date.now()
1006
- };
1007
- case "highlight":
1008
- return {
1009
- id,
1010
- type: "highlight",
1011
- bounds: this.createBounds(start, end),
1012
- style: { ...baseStyle, ...HIGHLIGHT_STYLE },
1013
- timestamp: Date.now()
1014
- };
1015
- case "blur":
683
+ setupEventListeners() {
684
+ if (!this.canvas) return;
685
+ this.canvas.addEventListener("mousedown", this.handleMouseDown.bind(this));
686
+ this.canvas.addEventListener("mousemove", this.handleMouseMove.bind(this));
687
+ this.canvas.addEventListener("mouseup", this.handleMouseUp.bind(this));
688
+ this.canvas.addEventListener("mouseleave", this.handleMouseUp.bind(this));
689
+ this.canvas.addEventListener("touchstart", this.handleTouchStart.bind(this));
690
+ this.canvas.addEventListener("touchmove", this.handleTouchMove.bind(this));
691
+ this.canvas.addEventListener("touchend", this.handleTouchEnd.bind(this));
692
+ document.addEventListener("keydown", this.handleKeyDown.bind(this));
693
+ }
694
+ handleMouseDown(e) {
695
+ const point = this.getCanvasPoint(e);
696
+ this.startDrawing(point);
697
+ }
698
+ handleMouseMove(e) {
699
+ const point = this.getCanvasPoint(e);
700
+ this.continueDrawing(point);
701
+ }
702
+ handleMouseUp(_e) {
703
+ this.finishDrawing();
704
+ }
705
+ handleTouchStart(e) {
706
+ e.preventDefault();
707
+ const touch = e.touches[0];
708
+ const point = this.getCanvasPointFromTouch(touch);
709
+ this.startDrawing(point);
710
+ }
711
+ handleTouchMove(e) {
712
+ e.preventDefault();
713
+ const touch = e.touches[0];
714
+ const point = this.getCanvasPointFromTouch(touch);
715
+ this.continueDrawing(point);
716
+ }
717
+ handleTouchEnd(e) {
718
+ e.preventDefault();
719
+ this.finishDrawing();
720
+ }
721
+ handleKeyDown(e) {
722
+ if ((e.ctrlKey || e.metaKey) && e.key === "z") {
723
+ e.preventDefault();
724
+ this.undo();
725
+ }
726
+ if (e.key === "Escape") {
727
+ if (this.state.isDrawing) {
728
+ this.state.isDrawing = false;
729
+ this.state.currentAnnotation = null;
730
+ this.render();
731
+ } else {
732
+ this.cancel();
733
+ }
734
+ }
735
+ if (e.key === "Enter" && !this.state.isDrawing) {
736
+ this.save();
737
+ }
738
+ }
739
+ getCanvasPoint(e) {
740
+ const rect = this.canvas.getBoundingClientRect();
741
+ const scaleX = this.canvas.width / rect.width;
742
+ const scaleY = this.canvas.height / rect.height;
1016
743
  return {
1017
- id,
1018
- type: "blur",
1019
- bounds: this.createBounds(start, end),
1020
- style: { ...baseStyle, ...BLUR_STYLE },
1021
- blurAmount: 10,
1022
- timestamp: Date.now()
744
+ x: (e.clientX - rect.left) * scaleX,
745
+ y: (e.clientY - rect.top) * scaleY
1023
746
  };
1024
- case "freehand":
747
+ }
748
+ getCanvasPointFromTouch(touch) {
749
+ const rect = this.canvas.getBoundingClientRect();
750
+ const scaleX = this.canvas.width / rect.width;
751
+ const scaleY = this.canvas.height / rect.height;
1025
752
  return {
1026
- id,
1027
- type: "freehand",
1028
- points: [...this.freehandPoints],
1029
- style: baseStyle,
1030
- timestamp: Date.now()
753
+ x: (touch.clientX - rect.left) * scaleX,
754
+ y: (touch.clientY - rect.top) * scaleY
1031
755
  };
1032
- default:
756
+ }
757
+ startDrawing(point) {
758
+ if (this.state.activeTool === "select") return;
759
+ this.state.isDrawing = true;
760
+ this.startPoint = point;
761
+ if (this.state.activeTool === "text") {
762
+ this.showTextInput(point);
763
+ return;
764
+ }
765
+ if (this.state.activeTool === "freehand") {
766
+ this.freehandPoints = [point];
767
+ }
768
+ }
769
+ continueDrawing(point) {
770
+ if (!this.state.isDrawing || !this.startPoint) return;
771
+ if (this.state.activeTool === "freehand") {
772
+ this.freehandPoints.push(point);
773
+ }
774
+ this.state.currentAnnotation = this.createAnnotation(this.startPoint, point);
775
+ this.render();
776
+ }
777
+ finishDrawing() {
778
+ if (!this.state.isDrawing || !this.state.currentAnnotation) {
779
+ this.state.isDrawing = false;
780
+ return;
781
+ }
782
+ if (this.isValidAnnotation(this.state.currentAnnotation)) {
783
+ this.annotations.push(this.state.currentAnnotation);
784
+ }
785
+ this.state.isDrawing = false;
786
+ this.state.currentAnnotation = null;
787
+ this.startPoint = null;
788
+ this.freehandPoints = [];
789
+ this.render();
790
+ }
791
+ isValidAnnotation(annotation) {
792
+ switch (annotation.type) {
793
+ case "rectangle":
794
+ case "ellipse":
795
+ case "highlight":
796
+ case "blur":
797
+ const bounds = annotation.bounds;
798
+ return Math.abs(bounds.width) > 5 && Math.abs(bounds.height) > 5;
799
+ case "arrow":
800
+ case "line":
801
+ const start = annotation.start;
802
+ const end = annotation.end;
803
+ const dist = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
804
+ return dist > 10;
805
+ case "freehand":
806
+ return annotation.points.length > 2;
807
+ case "text":
808
+ return !!annotation.text?.trim();
809
+ default:
810
+ return true;
811
+ }
812
+ }
813
+ createAnnotation(start, end) {
814
+ const id = this.generateId();
815
+ const baseStyle = { ...this.state.style };
816
+ switch (this.state.activeTool) {
817
+ case "rectangle":
818
+ return {
819
+ id,
820
+ type: "rectangle",
821
+ bounds: this.createBounds(start, end),
822
+ style: baseStyle,
823
+ timestamp: Date.now()
824
+ };
825
+ case "ellipse":
826
+ return {
827
+ id,
828
+ type: "ellipse",
829
+ bounds: this.createBounds(start, end),
830
+ style: baseStyle,
831
+ timestamp: Date.now()
832
+ };
833
+ case "arrow":
834
+ return {
835
+ id,
836
+ type: "arrow",
837
+ start: { ...start },
838
+ end: { ...end },
839
+ style: baseStyle,
840
+ timestamp: Date.now()
841
+ };
842
+ case "line":
843
+ return {
844
+ id,
845
+ type: "line",
846
+ start: { ...start },
847
+ end: { ...end },
848
+ style: baseStyle,
849
+ timestamp: Date.now()
850
+ };
851
+ case "highlight":
852
+ return {
853
+ id,
854
+ type: "highlight",
855
+ bounds: this.createBounds(start, end),
856
+ style: { ...baseStyle, ...HIGHLIGHT_STYLE },
857
+ timestamp: Date.now()
858
+ };
859
+ case "blur":
860
+ return {
861
+ id,
862
+ type: "blur",
863
+ bounds: this.createBounds(start, end),
864
+ style: { ...baseStyle, ...BLUR_STYLE },
865
+ blurAmount: 10,
866
+ timestamp: Date.now()
867
+ };
868
+ case "freehand":
869
+ return {
870
+ id,
871
+ type: "freehand",
872
+ points: [...this.freehandPoints],
873
+ style: baseStyle,
874
+ timestamp: Date.now()
875
+ };
876
+ default:
877
+ return {
878
+ id,
879
+ type: "rectangle",
880
+ bounds: this.createBounds(start, end),
881
+ style: baseStyle,
882
+ timestamp: Date.now()
883
+ };
884
+ }
885
+ }
886
+ createBounds(start, end) {
1033
887
  return {
1034
- id,
1035
- type: "rectangle",
1036
- bounds: this.createBounds(start, end),
1037
- style: baseStyle,
1038
- timestamp: Date.now()
888
+ x: Math.min(start.x, end.x),
889
+ y: Math.min(start.y, end.y),
890
+ width: Math.abs(end.x - start.x),
891
+ height: Math.abs(end.y - start.y)
1039
892
  };
1040
- }
1041
- }
1042
- createBounds(start, end) {
1043
- return {
1044
- x: Math.min(start.x, end.x),
1045
- y: Math.min(start.y, end.y),
1046
- width: Math.abs(end.x - start.x),
1047
- height: Math.abs(end.y - start.y)
1048
- };
1049
- }
1050
- showTextInput(point) {
1051
- if (this.textInput) {
1052
- this.textInput.remove();
1053
- }
1054
- const rect = this.canvas.getBoundingClientRect();
1055
- const scaleX = rect.width / this.canvas.width;
1056
- const scaleY = rect.height / this.canvas.height;
1057
- this.textInput = document.createElement("input");
1058
- this.textInput.type = "text";
1059
- this.textInput.className = "cf-annotation-text-input";
1060
- this.textInput.style.left = `${rect.left + point.x * scaleX}px`;
1061
- this.textInput.style.top = `${rect.top + point.y * scaleY}px`;
1062
- this.textInput.style.color = this.state.style.strokeColor;
1063
- this.textInput.style.fontSize = `${(this.state.style.fontSize || 16) * scaleY}px`;
1064
- this.textInput.placeholder = "Tapez votre texte...";
1065
- document.body.appendChild(this.textInput);
1066
- this.textInput.focus();
1067
- const handleTextSubmit = () => {
1068
- if (this.textInput && this.textInput.value.trim()) {
1069
- const textAnnotation = {
1070
- id: this.generateId(),
1071
- type: "text",
1072
- position: point,
1073
- text: this.textInput.value.trim(),
1074
- style: { ...this.state.style },
1075
- timestamp: Date.now()
893
+ }
894
+ showTextInput(point) {
895
+ if (this.textInput) {
896
+ this.textInput.remove();
897
+ }
898
+ const rect = this.canvas.getBoundingClientRect();
899
+ const scaleX = rect.width / this.canvas.width;
900
+ const scaleY = rect.height / this.canvas.height;
901
+ this.textInput = document.createElement("input");
902
+ this.textInput.type = "text";
903
+ this.textInput.className = "cf-annotation-text-input";
904
+ this.textInput.style.left = `${rect.left + point.x * scaleX}px`;
905
+ this.textInput.style.top = `${rect.top + point.y * scaleY}px`;
906
+ this.textInput.style.color = this.state.style.strokeColor;
907
+ this.textInput.style.fontSize = `${(this.state.style.fontSize || 16) * scaleY}px`;
908
+ this.textInput.placeholder = "Tapez votre texte...";
909
+ document.body.appendChild(this.textInput);
910
+ this.textInput.focus();
911
+ const handleTextSubmit = () => {
912
+ if (this.textInput && this.textInput.value.trim()) {
913
+ const textAnnotation = {
914
+ id: this.generateId(),
915
+ type: "text",
916
+ position: point,
917
+ text: this.textInput.value.trim(),
918
+ style: { ...this.state.style },
919
+ timestamp: Date.now()
920
+ };
921
+ this.annotations.push(textAnnotation);
922
+ this.render();
923
+ }
924
+ this.textInput?.remove();
925
+ this.textInput = null;
926
+ this.state.isDrawing = false;
1076
927
  };
1077
- this.annotations.push(textAnnotation);
928
+ this.textInput.addEventListener("blur", handleTextSubmit);
929
+ this.textInput.addEventListener("keydown", (e) => {
930
+ if (e.key === "Enter") {
931
+ handleTextSubmit();
932
+ } else if (e.key === "Escape") {
933
+ this.textInput?.remove();
934
+ this.textInput = null;
935
+ this.state.isDrawing = false;
936
+ }
937
+ });
938
+ }
939
+ generateId() {
940
+ return `ann_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
941
+ }
942
+ setActiveTool(tool) {
943
+ this.state.activeTool = tool;
944
+ this.toolbar?.setActiveTool(tool);
945
+ if (this.canvas) {
946
+ this.canvas.style.cursor = tool === "select" ? "default" : "crosshair";
947
+ }
948
+ }
949
+ setStyle(style) {
950
+ this.state.style = { ...this.state.style, ...style };
951
+ this.toolbar?.setStyle(this.state.style);
952
+ }
953
+ undo() {
954
+ if (this.annotations.length > 0) {
955
+ this.annotations.pop();
956
+ this.render();
957
+ }
958
+ }
959
+ clearAll() {
960
+ this.annotations = [];
1078
961
  this.render();
1079
962
  }
1080
- this.textInput?.remove();
1081
- this.textInput = null;
1082
- this.state.isDrawing = false;
1083
- };
1084
- this.textInput.addEventListener("blur", handleTextSubmit);
1085
- this.textInput.addEventListener("keydown", (e) => {
1086
- if (e.key === "Enter") {
1087
- handleTextSubmit();
1088
- } else if (e.key === "Escape") {
1089
- this.textInput?.remove();
1090
- this.textInput = null;
1091
- this.state.isDrawing = false;
963
+ save() {
964
+ const imageData = this.getAnnotatedImage();
965
+ this.config.onSave?.(this.annotations, imageData);
966
+ this.close();
1092
967
  }
1093
- });
1094
- }
1095
- generateId() {
1096
- return `ann_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1097
- }
1098
- setActiveTool(tool) {
1099
- this.state.activeTool = tool;
1100
- this.toolbar?.setActiveTool(tool);
1101
- if (this.canvas) {
1102
- this.canvas.style.cursor = tool === "select" ? "default" : "crosshair";
1103
- }
1104
- }
1105
- setStyle(style) {
1106
- this.state.style = { ...this.state.style, ...style };
1107
- this.toolbar?.setStyle(this.state.style);
1108
- }
1109
- undo() {
1110
- if (this.annotations.length > 0) {
1111
- this.annotations.pop();
1112
- this.render();
1113
- }
1114
- }
1115
- clearAll() {
1116
- this.annotations = [];
1117
- this.render();
1118
- }
1119
- save() {
1120
- const imageData = this.getAnnotatedImage();
1121
- this.config.onSave?.(this.annotations, imageData);
1122
- this.close();
1123
- }
1124
- cancel() {
1125
- this.config.onCancel?.();
1126
- this.close();
1127
- }
1128
- render() {
1129
- if (!this.ctx || !this.canvas) return;
1130
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
1131
- if (this.backgroundImage) {
1132
- this.ctx.drawImage(this.backgroundImage, 0, 0);
1133
- }
1134
- this.drawAnnotations(this.ctx);
1135
- if (this.state.currentAnnotation) {
1136
- this.drawAnnotation(this.ctx, this.state.currentAnnotation);
1137
- }
1138
- }
1139
- drawAnnotations(ctx) {
1140
- for (const annotation of this.annotations) {
1141
- this.drawAnnotation(ctx, annotation);
1142
- }
1143
- }
1144
- drawAnnotation(ctx, annotation) {
1145
- ctx.save();
1146
- ctx.globalAlpha = annotation.style.opacity;
1147
- ctx.strokeStyle = annotation.style.strokeColor;
1148
- ctx.fillStyle = annotation.style.fillColor;
1149
- ctx.lineWidth = annotation.style.strokeWidth;
1150
- ctx.lineCap = "round";
1151
- ctx.lineJoin = "round";
1152
- switch (annotation.type) {
1153
- case "rectangle":
1154
- this.drawRectangle(ctx, annotation.bounds, annotation.style);
1155
- break;
1156
- case "ellipse":
1157
- this.drawEllipse(ctx, annotation.bounds, annotation.style);
1158
- break;
1159
- case "arrow":
1160
- this.drawArrow(ctx, annotation.start, annotation.end, annotation.style);
1161
- break;
1162
- case "line":
1163
- this.drawLine(ctx, annotation.start, annotation.end);
1164
- break;
1165
- case "highlight":
1166
- this.drawHighlight(ctx, annotation.bounds, annotation.style);
1167
- break;
1168
- case "blur":
1169
- this.drawBlur(ctx, annotation.bounds, annotation.blurAmount);
1170
- break;
1171
- case "text":
1172
- this.drawText(ctx, annotation.position, annotation.text, annotation.style);
1173
- break;
1174
- case "freehand":
1175
- this.drawFreehand(ctx, annotation.points);
1176
- break;
1177
- }
1178
- ctx.restore();
1179
- }
1180
- drawRectangle(ctx, bounds, style) {
1181
- if (style.fillColor !== "transparent") {
1182
- ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1183
- }
1184
- ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1185
- }
1186
- drawEllipse(ctx, bounds, style) {
1187
- const centerX = bounds.x + bounds.width / 2;
1188
- const centerY = bounds.y + bounds.height / 2;
1189
- const radiusX = bounds.width / 2;
1190
- const radiusY = bounds.height / 2;
1191
- ctx.beginPath();
1192
- ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
1193
- if (style.fillColor !== "transparent") {
1194
- ctx.fill();
1195
- }
1196
- ctx.stroke();
1197
- }
1198
- drawArrow(ctx, start, end, style) {
1199
- const headLength = 15 + style.strokeWidth * 2;
1200
- const angle = Math.atan2(end.y - start.y, end.x - start.x);
1201
- ctx.beginPath();
1202
- ctx.moveTo(start.x, start.y);
1203
- ctx.lineTo(end.x, end.y);
1204
- ctx.stroke();
1205
- ctx.beginPath();
1206
- ctx.moveTo(end.x, end.y);
1207
- ctx.lineTo(
1208
- end.x - headLength * Math.cos(angle - Math.PI / 6),
1209
- end.y - headLength * Math.sin(angle - Math.PI / 6)
1210
- );
1211
- ctx.lineTo(
1212
- end.x - headLength * Math.cos(angle + Math.PI / 6),
1213
- end.y - headLength * Math.sin(angle + Math.PI / 6)
1214
- );
1215
- ctx.closePath();
1216
- ctx.fillStyle = style.strokeColor;
1217
- ctx.fill();
1218
- }
1219
- drawLine(ctx, start, end) {
1220
- ctx.beginPath();
1221
- ctx.moveTo(start.x, start.y);
1222
- ctx.lineTo(end.x, end.y);
1223
- ctx.stroke();
1224
- }
1225
- drawHighlight(ctx, bounds, style) {
1226
- ctx.fillStyle = style.fillColor;
1227
- ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1228
- }
1229
- drawBlur(ctx, bounds, blurAmount) {
1230
- const pixelSize = Math.max(blurAmount, 5);
1231
- if (this.backgroundImage) {
1232
- const tempCanvas = document.createElement("canvas");
1233
- tempCanvas.width = bounds.width;
1234
- tempCanvas.height = bounds.height;
1235
- const tempCtx = tempCanvas.getContext("2d");
1236
- tempCtx.drawImage(
1237
- this.backgroundImage,
1238
- bounds.x,
1239
- bounds.y,
1240
- bounds.width,
1241
- bounds.height,
1242
- 0,
1243
- 0,
1244
- bounds.width,
1245
- bounds.height
1246
- );
1247
- const w = tempCanvas.width;
1248
- const h = tempCanvas.height;
1249
- tempCtx.imageSmoothingEnabled = false;
1250
- tempCtx.drawImage(tempCanvas, 0, 0, w, h, 0, 0, w / pixelSize, h / pixelSize);
1251
- tempCtx.drawImage(tempCanvas, 0, 0, w / pixelSize, h / pixelSize, 0, 0, w, h);
1252
- ctx.drawImage(tempCanvas, bounds.x, bounds.y);
1253
- }
1254
- ctx.strokeStyle = "#999";
1255
- ctx.lineWidth = 1;
1256
- ctx.setLineDash([5, 5]);
1257
- ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1258
- ctx.setLineDash([]);
1259
- }
1260
- drawText(ctx, position, text, style) {
1261
- ctx.font = `${style.fontSize || 16}px ${style.fontFamily || "system-ui"}`;
1262
- ctx.fillStyle = style.strokeColor;
1263
- ctx.textBaseline = "top";
1264
- const metrics = ctx.measureText(text);
1265
- const padding = 4;
1266
- ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
1267
- ctx.fillRect(
1268
- position.x - padding,
1269
- position.y - padding,
1270
- metrics.width + padding * 2,
1271
- (style.fontSize || 16) + padding * 2
1272
- );
1273
- ctx.fillStyle = style.strokeColor;
1274
- ctx.fillText(text, position.x, position.y);
1275
- }
1276
- drawFreehand(ctx, points) {
1277
- if (points.length < 2) return;
1278
- ctx.beginPath();
1279
- ctx.moveTo(points[0].x, points[0].y);
1280
- for (let i = 1; i < points.length; i++) {
1281
- ctx.lineTo(points[i].x, points[i].y);
1282
- }
1283
- ctx.stroke();
968
+ cancel() {
969
+ this.config.onCancel?.();
970
+ this.close();
971
+ }
972
+ render() {
973
+ if (!this.ctx || !this.canvas) return;
974
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
975
+ if (this.backgroundImage) {
976
+ this.ctx.drawImage(this.backgroundImage, 0, 0);
977
+ }
978
+ this.drawAnnotations(this.ctx);
979
+ if (this.state.currentAnnotation) {
980
+ this.drawAnnotation(this.ctx, this.state.currentAnnotation);
981
+ }
982
+ }
983
+ drawAnnotations(ctx) {
984
+ for (const annotation of this.annotations) {
985
+ this.drawAnnotation(ctx, annotation);
986
+ }
987
+ }
988
+ drawAnnotation(ctx, annotation) {
989
+ ctx.save();
990
+ ctx.globalAlpha = annotation.style.opacity;
991
+ ctx.strokeStyle = annotation.style.strokeColor;
992
+ ctx.fillStyle = annotation.style.fillColor;
993
+ ctx.lineWidth = annotation.style.strokeWidth;
994
+ ctx.lineCap = "round";
995
+ ctx.lineJoin = "round";
996
+ switch (annotation.type) {
997
+ case "rectangle":
998
+ this.drawRectangle(ctx, annotation.bounds, annotation.style);
999
+ break;
1000
+ case "ellipse":
1001
+ this.drawEllipse(ctx, annotation.bounds, annotation.style);
1002
+ break;
1003
+ case "arrow":
1004
+ this.drawArrow(ctx, annotation.start, annotation.end, annotation.style);
1005
+ break;
1006
+ case "line":
1007
+ this.drawLine(ctx, annotation.start, annotation.end);
1008
+ break;
1009
+ case "highlight":
1010
+ this.drawHighlight(ctx, annotation.bounds, annotation.style);
1011
+ break;
1012
+ case "blur":
1013
+ this.drawBlur(ctx, annotation.bounds, annotation.blurAmount);
1014
+ break;
1015
+ case "text":
1016
+ this.drawText(ctx, annotation.position, annotation.text, annotation.style);
1017
+ break;
1018
+ case "freehand":
1019
+ this.drawFreehand(ctx, annotation.points);
1020
+ break;
1021
+ }
1022
+ ctx.restore();
1023
+ }
1024
+ drawRectangle(ctx, bounds, style) {
1025
+ if (style.fillColor !== "transparent") {
1026
+ ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1027
+ }
1028
+ ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1029
+ }
1030
+ drawEllipse(ctx, bounds, style) {
1031
+ const centerX = bounds.x + bounds.width / 2;
1032
+ const centerY = bounds.y + bounds.height / 2;
1033
+ const radiusX = bounds.width / 2;
1034
+ const radiusY = bounds.height / 2;
1035
+ ctx.beginPath();
1036
+ ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
1037
+ if (style.fillColor !== "transparent") {
1038
+ ctx.fill();
1039
+ }
1040
+ ctx.stroke();
1041
+ }
1042
+ drawArrow(ctx, start, end, style) {
1043
+ const headLength = 15 + style.strokeWidth * 2;
1044
+ const angle = Math.atan2(end.y - start.y, end.x - start.x);
1045
+ ctx.beginPath();
1046
+ ctx.moveTo(start.x, start.y);
1047
+ ctx.lineTo(end.x, end.y);
1048
+ ctx.stroke();
1049
+ ctx.beginPath();
1050
+ ctx.moveTo(end.x, end.y);
1051
+ ctx.lineTo(
1052
+ end.x - headLength * Math.cos(angle - Math.PI / 6),
1053
+ end.y - headLength * Math.sin(angle - Math.PI / 6)
1054
+ );
1055
+ ctx.lineTo(
1056
+ end.x - headLength * Math.cos(angle + Math.PI / 6),
1057
+ end.y - headLength * Math.sin(angle + Math.PI / 6)
1058
+ );
1059
+ ctx.closePath();
1060
+ ctx.fillStyle = style.strokeColor;
1061
+ ctx.fill();
1062
+ }
1063
+ drawLine(ctx, start, end) {
1064
+ ctx.beginPath();
1065
+ ctx.moveTo(start.x, start.y);
1066
+ ctx.lineTo(end.x, end.y);
1067
+ ctx.stroke();
1068
+ }
1069
+ drawHighlight(ctx, bounds, style) {
1070
+ ctx.fillStyle = style.fillColor;
1071
+ ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1072
+ }
1073
+ drawBlur(ctx, bounds, blurAmount) {
1074
+ const pixelSize = Math.max(blurAmount, 5);
1075
+ if (this.backgroundImage) {
1076
+ const tempCanvas = document.createElement("canvas");
1077
+ tempCanvas.width = bounds.width;
1078
+ tempCanvas.height = bounds.height;
1079
+ const tempCtx = tempCanvas.getContext("2d");
1080
+ tempCtx.drawImage(
1081
+ this.backgroundImage,
1082
+ bounds.x,
1083
+ bounds.y,
1084
+ bounds.width,
1085
+ bounds.height,
1086
+ 0,
1087
+ 0,
1088
+ bounds.width,
1089
+ bounds.height
1090
+ );
1091
+ const w = tempCanvas.width;
1092
+ const h = tempCanvas.height;
1093
+ tempCtx.imageSmoothingEnabled = false;
1094
+ tempCtx.drawImage(tempCanvas, 0, 0, w, h, 0, 0, w / pixelSize, h / pixelSize);
1095
+ tempCtx.drawImage(tempCanvas, 0, 0, w / pixelSize, h / pixelSize, 0, 0, w, h);
1096
+ ctx.drawImage(tempCanvas, bounds.x, bounds.y);
1097
+ }
1098
+ ctx.strokeStyle = "#999";
1099
+ ctx.lineWidth = 1;
1100
+ ctx.setLineDash([5, 5]);
1101
+ ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1102
+ ctx.setLineDash([]);
1103
+ }
1104
+ drawText(ctx, position, text, style) {
1105
+ ctx.font = `${style.fontSize || 16}px ${style.fontFamily || "system-ui"}`;
1106
+ ctx.fillStyle = style.strokeColor;
1107
+ ctx.textBaseline = "top";
1108
+ const metrics = ctx.measureText(text);
1109
+ const padding = 4;
1110
+ ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
1111
+ ctx.fillRect(
1112
+ position.x - padding,
1113
+ position.y - padding,
1114
+ metrics.width + padding * 2,
1115
+ (style.fontSize || 16) + padding * 2
1116
+ );
1117
+ ctx.fillStyle = style.strokeColor;
1118
+ ctx.fillText(text, position.x, position.y);
1119
+ }
1120
+ drawFreehand(ctx, points) {
1121
+ if (points.length < 2) return;
1122
+ ctx.beginPath();
1123
+ ctx.moveTo(points[0].x, points[0].y);
1124
+ for (let i = 1; i < points.length; i++) {
1125
+ ctx.lineTo(points[i].x, points[i].y);
1126
+ }
1127
+ ctx.stroke();
1128
+ }
1129
+ };
1284
1130
  }
1285
- };
1131
+ });
1286
1132
 
1287
1133
  // src/widget.ts
1288
- var DEFAULT_CONFIG = {
1289
- position: "bottom-right",
1290
- color: "#1e3a5f",
1291
- text: "Report Bug",
1292
- showOnInit: true
1293
- };
1294
- var container = null;
1295
- var onSubmitCallback = null;
1296
- var screenshotDataUrl = null;
1297
- var annotatedImageUrl = null;
1298
- var annotationsData = [];
1299
- var isExpanded = false;
1300
- var currentCfg = { ...DEFAULT_CONFIG };
1134
+ var widget_exports = {};
1135
+ __export(widget_exports, {
1136
+ mountWidget: () => mountWidget,
1137
+ openFeedbackModal: () => openFeedbackModal,
1138
+ unmountWidget: () => unmountWidget
1139
+ });
1301
1140
  function mountWidget(config2 = {}, onSubmit, opts) {
1302
1141
  if (container) return;
1303
1142
  onSubmitCallback = onSubmit;
1143
+ currentUser = opts?.user;
1304
1144
  const cfg = { ...DEFAULT_CONFIG, ...config2 };
1305
1145
  currentCfg = cfg;
1306
1146
  container = document.createElement("div");
@@ -1311,13 +1151,20 @@ function mountWidget(config2 = {}, onSubmit, opts) {
1311
1151
  const btn = container.querySelector("#cf-trigger");
1312
1152
  btn?.addEventListener("click", () => openModal(cfg, opts));
1313
1153
  }
1154
+ function openFeedbackModal(config2 = {}, onSubmit, opts) {
1155
+ onSubmitCallback = onSubmit;
1156
+ currentUser = opts?.user;
1157
+ const cfg = { ...DEFAULT_CONFIG, ...config2 };
1158
+ currentCfg = cfg;
1159
+ injectStyles(cfg);
1160
+ openModal(cfg, opts);
1161
+ }
1314
1162
  function getBackdropClass(cfg, expanded) {
1315
1163
  if (expanded) return "cf-backdrop--center";
1316
1164
  if (cfg.position === "center") return "cf-backdrop--center";
1317
- return "cf-backdrop--corner cf-pos-" + cfg.position;
1165
+ return "cf-backdrop--corner cf-pos-" + (cfg.position || "bottom-right");
1318
1166
  }
1319
1167
  function openModal(cfg, opts) {
1320
- if (!container) return;
1321
1168
  const existing = document.getElementById("cf-modal-backdrop");
1322
1169
  if (existing) existing.remove();
1323
1170
  isExpanded = false;
@@ -1430,8 +1277,10 @@ function bindModalEvents(cfg, opts) {
1430
1277
  });
1431
1278
  backdrop.querySelector("#cf-submit")?.addEventListener("click", () => {
1432
1279
  const desc = backdrop.querySelector("#cf-desc")?.value;
1433
- const name = backdrop.querySelector("#cf-name")?.value;
1434
- const email = backdrop.querySelector("#cf-email")?.value;
1280
+ const nameEl = backdrop.querySelector("#cf-name");
1281
+ const emailEl = backdrop.querySelector("#cf-email");
1282
+ const name = nameEl?.value || currentUser?.name;
1283
+ const email = emailEl?.value || currentUser?.email;
1435
1284
  if (!desc?.trim()) {
1436
1285
  const descEl = backdrop.querySelector("#cf-desc");
1437
1286
  if (descEl) {
@@ -1473,33 +1322,93 @@ function showToast(msg) {
1473
1322
  setTimeout(() => toast.remove(), 3e3);
1474
1323
  }
1475
1324
  function injectStyles(cfg) {
1325
+ const existingStyle = document.getElementById("cf-widget-styles");
1326
+ if (existingStyle) existingStyle.remove();
1327
+ const btn = cfg.button || {};
1328
+ const sizePreset = SIZE_PRESETS[btn.size || "default"] || SIZE_PRESETS.default;
1329
+ const variantPreset = VARIANT_PRESETS[btn.variant || "default"] || VARIANT_PRESETS.default;
1330
+ const bgColor = btn.backgroundColor || cfg.color || variantPreset.bg;
1331
+ const textColor = btn.textColor || variantPreset.color;
1332
+ const borderRadius = btn.borderRadius || "8px";
1333
+ const padding = btn.padding || sizePreset.padding;
1334
+ const fontSize = btn.fontSize || sizePreset.fontSize;
1335
+ const fontWeight = btn.fontWeight || "500";
1336
+ const border = btn.border || variantPreset.border;
1337
+ const boxShadow = btn.boxShadow || "0 2px 8px rgba(0,0,0,0.15)";
1338
+ const primaryColor = cfg.color || "#1e3a5f";
1476
1339
  const style = document.createElement("style");
1477
1340
  style.id = "cf-widget-styles";
1478
1341
  style.textContent = `
1479
1342
  @keyframes cf-toast-in { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
1480
1343
  @keyframes cf-modal-in { from { opacity:0; transform:scale(.96) translateY(8px); } to { opacity:1; transform:scale(1) translateY(0); } }
1481
1344
 
1345
+ /* User card styles */
1346
+ .cf-user-card {
1347
+ display:flex;
1348
+ align-items:center;
1349
+ gap:12px;
1350
+ padding:12px;
1351
+ background:#f8f9fa;
1352
+ border-radius:10px;
1353
+ margin-bottom:4px;
1354
+ }
1355
+ .cf-user-avatar {
1356
+ width:40px;
1357
+ height:40px;
1358
+ border-radius:50%;
1359
+ object-fit:cover;
1360
+ }
1361
+ .cf-user-avatar-placeholder {
1362
+ width:40px;
1363
+ height:40px;
1364
+ border-radius:50%;
1365
+ background:${primaryColor};
1366
+ color:#fff;
1367
+ display:flex;
1368
+ align-items:center;
1369
+ justify-content:center;
1370
+ font-size:16px;
1371
+ font-weight:600;
1372
+ }
1373
+ .cf-user-info {
1374
+ flex:1;
1375
+ min-width:0;
1376
+ }
1377
+ .cf-user-name {
1378
+ font-size:14px;
1379
+ font-weight:600;
1380
+ color:#1a1a2e;
1381
+ }
1382
+ .cf-user-email {
1383
+ font-size:12px;
1384
+ color:#666;
1385
+ overflow:hidden;
1386
+ text-overflow:ellipsis;
1387
+ white-space:nowrap;
1388
+ }
1389
+
1390
+ /* Trigger button - now inline, not fixed position */
1482
1391
  #cf-trigger {
1483
1392
  position:fixed;
1484
- ${getPositionCSS(cfg.position)}
1393
+ ${getPositionCSS(cfg.position || "bottom-right")}
1485
1394
  z-index:100000;
1486
- background:${cfg.color};
1487
- color:#fff;
1488
- border:none;
1489
- border-radius:50px;
1490
- padding:11px 20px;
1491
- font-size:14px;
1492
- font-weight:500;
1395
+ background:${bgColor};
1396
+ color:${textColor};
1397
+ border:${border};
1398
+ border-radius:${borderRadius};
1399
+ padding:${padding};
1400
+ font-size:${fontSize};
1401
+ font-weight:${fontWeight};
1493
1402
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
1494
1403
  cursor:pointer;
1495
- box-shadow:0 4px 16px rgba(0,0,0,.18);
1496
- display:flex;
1404
+ box-shadow:${boxShadow};
1405
+ display:inline-flex;
1497
1406
  align-items:center;
1498
1407
  gap:8px;
1499
- transition:transform .15s,box-shadow .15s;
1408
+ transition:transform .15s,box-shadow .15s,opacity .15s;
1500
1409
  }
1501
- #cf-trigger:hover { transform:scale(1.04); box-shadow:0 6px 24px rgba(0,0,0,.22); }
1502
- #cf-trigger svg { width:18px; height:18px; }
1410
+ #cf-trigger:hover { opacity:0.9; }
1411
+ #cf-trigger svg { width:16px; height:16px; flex-shrink:0; }
1503
1412
 
1504
1413
  #cf-modal-backdrop {
1505
1414
  position:fixed;
@@ -1600,7 +1509,7 @@ function injectStyles(cfg) {
1600
1509
  transition:all .15s;
1601
1510
  }
1602
1511
  .cf-tool-highlight {
1603
- background:${cfg.color};
1512
+ background:${primaryColor};
1604
1513
  color:#fff;
1605
1514
  }
1606
1515
  .cf-tool-highlight:hover { opacity:.9; }
@@ -1648,8 +1557,8 @@ function injectStyles(cfg) {
1648
1557
  }
1649
1558
  .cf-field input::placeholder, .cf-field textarea::placeholder { color:#bbb; }
1650
1559
  .cf-field input:focus, .cf-field textarea:focus {
1651
- border-color:${cfg.color};
1652
- box-shadow:0 0 0 3px ${cfg.color}18;
1560
+ border-color:${primaryColor};
1561
+ box-shadow:0 0 0 3px ${primaryColor}18;
1653
1562
  }
1654
1563
  .cf-field textarea { resize:vertical; min-height:100px; }
1655
1564
 
@@ -1688,7 +1597,7 @@ function injectStyles(cfg) {
1688
1597
  padding:13px;
1689
1598
  border:none;
1690
1599
  border-radius:10px;
1691
- background:${cfg.color};
1600
+ background:${primaryColor};
1692
1601
  color:#fff;
1693
1602
  font-size:15px;
1694
1603
  font-weight:600;
@@ -1696,7 +1605,7 @@ function injectStyles(cfg) {
1696
1605
  font-family:inherit;
1697
1606
  transition:all .15s;
1698
1607
  }
1699
- .cf-submit-btn:hover { opacity:.92; transform:translateY(-1px); box-shadow:0 4px 12px ${cfg.color}40; }
1608
+ .cf-submit-btn:hover { opacity:.92; transform:translateY(-1px); box-shadow:0 4px 12px ${primaryColor}40; }
1700
1609
  .cf-submit-btn:disabled { opacity:.5; cursor:not-allowed; transform:none; }
1701
1610
 
1702
1611
  .cf-cancel-btn {
@@ -1727,7 +1636,7 @@ function injectStyles(cfg) {
1727
1636
  text-decoration:none;
1728
1637
  font-weight:500;
1729
1638
  }
1730
- .cf-footer a:hover { color:${cfg.color}; }
1639
+ .cf-footer a:hover { color:${primaryColor}; }
1731
1640
  `;
1732
1641
  document.head.appendChild(style);
1733
1642
  }
@@ -1746,10 +1655,15 @@ function getPositionCSS(pos) {
1746
1655
  }
1747
1656
  }
1748
1657
  function getTriggerHTML(cfg) {
1658
+ const btn = cfg.button || {};
1659
+ const text = btn.text || cfg.text || "Report Bug";
1660
+ const showIcon = btn.showIcon !== false;
1661
+ const customClass = btn.className || "";
1662
+ const icon = showIcon ? `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>` : "";
1749
1663
  return `
1750
- <button id="cf-trigger">
1751
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
1752
- ${cfg.text}
1664
+ <button id="cf-trigger" class="${customClass}">
1665
+ ${icon}
1666
+ ${text}
1753
1667
  </button>
1754
1668
  `;
1755
1669
  }
@@ -1758,15 +1672,29 @@ function getCheckflowLogo() {
1758
1672
  }
1759
1673
  function getModalHTML(cfg, expanded) {
1760
1674
  const modalClass = expanded ? "cf-modal cf-modal--expanded" : "cf-modal cf-modal--compact";
1761
- const formFields = `
1675
+ const hasUser = currentUser && (currentUser.name || currentUser.email);
1676
+ const userInfoCard = hasUser ? `
1677
+ <div class="cf-user-card">
1678
+ ${currentUser?.avatar ? `<img class="cf-user-avatar" src="${currentUser.avatar}" alt="${currentUser.name || "User"}" />` : `<div class="cf-user-avatar-placeholder">${(currentUser?.name || currentUser?.email || "U").charAt(0).toUpperCase()}</div>`}
1679
+ <div class="cf-user-info">
1680
+ ${currentUser?.name ? `<div class="cf-user-name">${currentUser.name}</div>` : ""}
1681
+ ${currentUser?.email ? `<div class="cf-user-email">${currentUser.email}</div>` : ""}
1682
+ </div>
1683
+ </div>
1684
+ ` : "";
1685
+ const nameEmailFields = hasUser ? "" : `
1762
1686
  <div class="cf-field">
1763
- <label>Nom <span>(obligatoire)</span></label>
1687
+ <label>Nom <span>(optionnel)</span></label>
1764
1688
  <input id="cf-name" type="text" placeholder="Votre nom" />
1765
1689
  </div>
1766
1690
  <div class="cf-field">
1767
- <label>Email <span>(obligatoire)</span></label>
1691
+ <label>Email <span>(optionnel)</span></label>
1768
1692
  <input id="cf-email" type="email" placeholder="votre.email@exemple.com" />
1769
1693
  </div>
1694
+ `;
1695
+ const formFields = `
1696
+ ${userInfoCard}
1697
+ ${nameEmailFields}
1770
1698
  <div class="cf-field">
1771
1699
  <label>Description <span>(obligatoire)</span></label>
1772
1700
  <textarea id="cf-desc" placeholder="Quel est le probl\xE8me ? Que vous attendiez-vous \xE0 voir ?"></textarea>
@@ -1815,6 +1743,246 @@ function getModalHTML(cfg, expanded) {
1815
1743
  </div>
1816
1744
  `;
1817
1745
  }
1746
+ var DEFAULT_BUTTON_CONFIG, SIZE_PRESETS, VARIANT_PRESETS, DEFAULT_CONFIG, container, onSubmitCallback, screenshotDataUrl, annotatedImageUrl, annotationsData, isExpanded, currentCfg, currentUser;
1747
+ var init_widget = __esm({
1748
+ "src/widget.ts"() {
1749
+ "use strict";
1750
+ init_editor();
1751
+ DEFAULT_BUTTON_CONFIG = {
1752
+ text: "Report Bug",
1753
+ backgroundColor: "#1e3a5f",
1754
+ textColor: "#ffffff",
1755
+ borderRadius: "8px",
1756
+ padding: "10px 18px",
1757
+ fontSize: "14px",
1758
+ fontWeight: "500",
1759
+ border: "none",
1760
+ boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
1761
+ className: "",
1762
+ showIcon: true,
1763
+ size: "default",
1764
+ variant: "default"
1765
+ };
1766
+ SIZE_PRESETS = {
1767
+ xs: { padding: "6px 10px", fontSize: "12px" },
1768
+ sm: { padding: "8px 14px", fontSize: "13px" },
1769
+ default: { padding: "10px 18px", fontSize: "14px" },
1770
+ lg: { padding: "12px 24px", fontSize: "15px" }
1771
+ };
1772
+ VARIANT_PRESETS = {
1773
+ default: { bg: "#1e3a5f", color: "#ffffff", border: "none" },
1774
+ outline: { bg: "transparent", color: "#1e3a5f", border: "1px solid #1e3a5f" },
1775
+ ghost: { bg: "transparent", color: "#1e3a5f", border: "none" },
1776
+ link: { bg: "transparent", color: "#1e3a5f", border: "none" }
1777
+ };
1778
+ DEFAULT_CONFIG = {
1779
+ position: "bottom-right",
1780
+ color: "#1e3a5f",
1781
+ text: "Report Bug",
1782
+ showOnInit: true,
1783
+ button: DEFAULT_BUTTON_CONFIG
1784
+ };
1785
+ container = null;
1786
+ onSubmitCallback = null;
1787
+ screenshotDataUrl = null;
1788
+ annotatedImageUrl = null;
1789
+ annotationsData = [];
1790
+ isExpanded = false;
1791
+ currentCfg = { ...DEFAULT_CONFIG };
1792
+ currentUser = void 0;
1793
+ }
1794
+ });
1795
+
1796
+ // src/index.ts
1797
+ var index_exports = {};
1798
+ __export(index_exports, {
1799
+ captureScreenshot: () => captureScreenshot,
1800
+ clearUser: () => clearUser,
1801
+ destroy: () => destroy,
1802
+ hideWidget: () => hideWidget,
1803
+ init: () => init,
1804
+ openFeedbackModal: () => openFeedbackModal2,
1805
+ sendFeedback: () => sendFeedback,
1806
+ setUser: () => setUser,
1807
+ showWidget: () => showWidget
1808
+ });
1809
+ module.exports = __toCommonJS(index_exports);
1810
+
1811
+ // src/collector.ts
1812
+ function collectContext() {
1813
+ const ua = navigator.userAgent;
1814
+ return {
1815
+ url: window.location.href,
1816
+ viewport: {
1817
+ width: window.innerWidth,
1818
+ height: window.innerHeight,
1819
+ device: window.innerWidth < 768 ? "mobile" : window.innerWidth < 1024 ? "tablet" : "desktop"
1820
+ },
1821
+ user_agent: ua,
1822
+ browser: detectBrowser(ua),
1823
+ os: detectOS(ua),
1824
+ locale: navigator.language,
1825
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
1826
+ };
1827
+ }
1828
+ function collectPerformance() {
1829
+ try {
1830
+ const entries = performance.getEntriesByType("paint");
1831
+ const metrics = {};
1832
+ entries.forEach((e) => {
1833
+ if (e.name === "first-contentful-paint") metrics.fcp = Math.round(e.startTime);
1834
+ });
1835
+ const nav = performance.getEntriesByType("navigation")[0];
1836
+ if (nav) {
1837
+ metrics.dom_load = Math.round(nav.domContentLoadedEventEnd - nav.startTime);
1838
+ metrics.load = Math.round(nav.loadEventEnd - nav.startTime);
1839
+ }
1840
+ return Object.keys(metrics).length > 0 ? metrics : void 0;
1841
+ } catch {
1842
+ return void 0;
1843
+ }
1844
+ }
1845
+ function detectBrowser(ua) {
1846
+ if (ua.includes("Firefox/")) return "Firefox";
1847
+ if (ua.includes("Edg/")) return "Edge";
1848
+ if (ua.includes("Chrome/")) return "Chrome";
1849
+ if (ua.includes("Safari/")) return "Safari";
1850
+ return "Unknown";
1851
+ }
1852
+ function detectOS(ua) {
1853
+ if (ua.includes("Windows")) return "Windows";
1854
+ if (ua.includes("Mac OS")) return "macOS";
1855
+ if (ua.includes("Linux")) return "Linux";
1856
+ if (ua.includes("Android")) return "Android";
1857
+ if (ua.includes("iPhone") || ua.includes("iPad")) return "iOS";
1858
+ return "Unknown";
1859
+ }
1860
+
1861
+ // src/console-interceptor.ts
1862
+ var MAX_LOGS = 50;
1863
+ var logs = [];
1864
+ var errorsCapture = [];
1865
+ var installed = false;
1866
+ function installInterceptors() {
1867
+ if (installed) return;
1868
+ installed = true;
1869
+ const origConsole = {
1870
+ log: console.log,
1871
+ warn: console.warn,
1872
+ error: console.error
1873
+ };
1874
+ ["log", "warn", "error"].forEach((level) => {
1875
+ console[level] = (...args) => {
1876
+ logs.push({
1877
+ level,
1878
+ message: args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" "),
1879
+ timestamp: Date.now()
1880
+ });
1881
+ if (logs.length > MAX_LOGS) logs.shift();
1882
+ origConsole[level](...args);
1883
+ };
1884
+ });
1885
+ window.addEventListener("error", (event) => {
1886
+ errorsCapture.push({
1887
+ message: event.message,
1888
+ filename: event.filename,
1889
+ lineno: event.lineno,
1890
+ colno: event.colno,
1891
+ timestamp: Date.now()
1892
+ });
1893
+ if (errorsCapture.length > MAX_LOGS) errorsCapture.shift();
1894
+ });
1895
+ window.addEventListener("unhandledrejection", (event) => {
1896
+ errorsCapture.push({
1897
+ message: String(event.reason),
1898
+ type: "unhandledrejection",
1899
+ timestamp: Date.now()
1900
+ });
1901
+ if (errorsCapture.length > MAX_LOGS) errorsCapture.shift();
1902
+ });
1903
+ }
1904
+ function getConsoleLogs() {
1905
+ return [...logs];
1906
+ }
1907
+ function getJavascriptErrors() {
1908
+ return [...errorsCapture];
1909
+ }
1910
+ function clearLogs() {
1911
+ logs = [];
1912
+ errorsCapture = [];
1913
+ }
1914
+
1915
+ // src/network-interceptor.ts
1916
+ var MAX_LOGS2 = 50;
1917
+ var logs2 = [];
1918
+ var installed2 = false;
1919
+ function installNetworkInterceptor() {
1920
+ if (installed2 || typeof window === "undefined") return;
1921
+ installed2 = true;
1922
+ const origFetch = window.fetch;
1923
+ window.fetch = async function(...args) {
1924
+ const start = Date.now();
1925
+ const req = new Request(...args);
1926
+ const entry = {
1927
+ method: req.method,
1928
+ url: req.url,
1929
+ type: "fetch",
1930
+ timestamp: start
1931
+ };
1932
+ try {
1933
+ const res = await origFetch.apply(this, args);
1934
+ entry.status = res.status;
1935
+ entry.duration = Date.now() - start;
1936
+ entry.response_type = res.headers.get("content-type") || void 0;
1937
+ pushLog(entry);
1938
+ return res;
1939
+ } catch (err) {
1940
+ entry.duration = Date.now() - start;
1941
+ entry.error = err.message || "Network error";
1942
+ pushLog(entry);
1943
+ throw err;
1944
+ }
1945
+ };
1946
+ const origOpen = XMLHttpRequest.prototype.open;
1947
+ const origSend = XMLHttpRequest.prototype.send;
1948
+ XMLHttpRequest.prototype.open = function(method, url, ...rest) {
1949
+ this.__cf_method = method;
1950
+ this.__cf_url = String(url);
1951
+ return origOpen.apply(this, [method, url, ...rest]);
1952
+ };
1953
+ XMLHttpRequest.prototype.send = function(...args) {
1954
+ const start = Date.now();
1955
+ const xhr = this;
1956
+ xhr.addEventListener("loadend", () => {
1957
+ const entry = {
1958
+ method: xhr.__cf_method || "GET",
1959
+ url: xhr.__cf_url || "",
1960
+ status: xhr.status,
1961
+ duration: Date.now() - start,
1962
+ type: "xhr",
1963
+ timestamp: start,
1964
+ response_type: xhr.getResponseHeader("content-type") || void 0
1965
+ };
1966
+ if (xhr.status === 0) entry.error = "Request failed";
1967
+ pushLog(entry);
1968
+ });
1969
+ return origSend.apply(this, args);
1970
+ };
1971
+ }
1972
+ function pushLog(entry) {
1973
+ if (entry.url.includes("/sdk/feedback")) return;
1974
+ logs2.push(entry);
1975
+ if (logs2.length > MAX_LOGS2) logs2.shift();
1976
+ }
1977
+ function getNetworkLogs() {
1978
+ return [...logs2];
1979
+ }
1980
+ function clearNetworkLogs() {
1981
+ logs2 = [];
1982
+ }
1983
+
1984
+ // src/index.ts
1985
+ init_widget();
1818
1986
 
1819
1987
  // src/screenshot.ts
1820
1988
  var screenshotData = null;
@@ -1897,7 +2065,8 @@ function init(cfg) {
1897
2065
  });
1898
2066
  },
1899
2067
  {
1900
- onScreenshot: () => captureScreenshot()
2068
+ onScreenshot: () => captureScreenshot(),
2069
+ user: config.user
1901
2070
  }
1902
2071
  );
1903
2072
  }
@@ -1927,12 +2096,22 @@ async function sendFeedback(data) {
1927
2096
  if (data.annotations && data.annotations.length > 0) {
1928
2097
  payload.annotations = data.annotations;
1929
2098
  }
1930
- if (data.reporter_name) {
2099
+ if (config.user?.name) {
2100
+ payload.reporter_name = config.user.name;
2101
+ } else if (data.reporter_name) {
1931
2102
  payload.reporter_name = data.reporter_name;
1932
2103
  }
1933
- if (data.reporter_email) {
2104
+ if (config.user?.email) {
2105
+ payload.reporter_email = config.user.email;
2106
+ } else if (data.reporter_email) {
1934
2107
  payload.reporter_email = data.reporter_email;
1935
2108
  }
2109
+ if (config.user?.id) {
2110
+ payload.user_id = config.user.id;
2111
+ }
2112
+ if (config.user?.avatar) {
2113
+ payload.user_avatar = config.user.avatar;
2114
+ }
1936
2115
  try {
1937
2116
  const res = await fetch(`${config.endpoint}/sdk/feedback`, {
1938
2117
  method: "POST",
@@ -1968,10 +2147,45 @@ function showWidget() {
1968
2147
  });
1969
2148
  },
1970
2149
  {
1971
- onScreenshot: () => captureScreenshot()
2150
+ onScreenshot: () => captureScreenshot(),
2151
+ user: config.user
1972
2152
  }
1973
2153
  );
1974
2154
  }
2155
+ function setUser(user) {
2156
+ if (config) {
2157
+ config.user = user;
2158
+ }
2159
+ }
2160
+ function clearUser() {
2161
+ if (config) {
2162
+ config.user = void 0;
2163
+ }
2164
+ }
2165
+ function openFeedbackModal2() {
2166
+ if (!config) return;
2167
+ Promise.resolve().then(() => (init_widget(), widget_exports)).then(({ openFeedbackModal: openModal2 }) => {
2168
+ openModal2(
2169
+ config.widget,
2170
+ (data) => {
2171
+ sendFeedback({
2172
+ title: data.title,
2173
+ description: data.description,
2174
+ type: data.type,
2175
+ priority: data.priority,
2176
+ screenshot_data: data.screenshot,
2177
+ annotations: data.annotations,
2178
+ reporter_name: data.reporter_name,
2179
+ reporter_email: data.reporter_email
2180
+ });
2181
+ },
2182
+ {
2183
+ onScreenshot: () => captureScreenshot(),
2184
+ user: config.user
2185
+ }
2186
+ );
2187
+ });
2188
+ }
1975
2189
  function hideWidget() {
1976
2190
  unmountWidget();
1977
2191
  }
@@ -1985,9 +2199,12 @@ function destroy() {
1985
2199
  // Annotate the CommonJS export names for ESM import in node:
1986
2200
  0 && (module.exports = {
1987
2201
  captureScreenshot,
2202
+ clearUser,
1988
2203
  destroy,
1989
2204
  hideWidget,
1990
2205
  init,
2206
+ openFeedbackModal,
1991
2207
  sendFeedback,
2208
+ setUser,
1992
2209
  showWidget
1993
2210
  });