@checkflow/sdk 1.1.3 → 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/chunk-NRA75GGU.mjs +1737 -0
- package/dist/highlighter-D0FpwCSU.d.mts +18 -0
- package/dist/highlighter-D0FpwCSU.d.ts +18 -0
- package/dist/{chunk-CD33QAA6.mjs → highlighter-EMSU6IYQ.mjs} +2 -3
- package/dist/index.d.mts +17 -4
- package/dist/index.d.ts +17 -4
- package/dist/index.js +1578 -370
- package/dist/index.mjs +56 -504
- package/dist/react.d.mts +48 -2
- package/dist/react.d.ts +48 -2
- package/dist/react.js +1568 -217
- package/dist/react.mjs +33 -2
- package/dist/types-fCeePy5c.d.mts +101 -0
- package/dist/types-fCeePy5c.d.ts +101 -0
- package/dist/vue.d.mts +2 -1
- package/dist/vue.d.ts +2 -1
- package/dist/vue.js +1532 -216
- package/dist/vue.mjs +1 -1
- package/dist/widget-LD24NDDL.mjs +10 -0
- package/package.json +1 -1
- package/dist/highlighter-Dx2zURb6.d.mts +0 -73
- package/dist/highlighter-Dx2zURb6.d.ts +0 -73
- package/dist/highlighter-W4XDALRE.mjs +0 -8
package/dist/vue.js
CHANGED
|
@@ -210,11 +210,1129 @@ var init_network_interceptor = __esm({
|
|
|
210
210
|
}
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
+
// src/annotation/types.ts
|
|
214
|
+
var DEFAULT_STYLE, HIGHLIGHT_STYLE, BLUR_STYLE, COLOR_PALETTE, STROKE_WIDTHS;
|
|
215
|
+
var init_types = __esm({
|
|
216
|
+
"src/annotation/types.ts"() {
|
|
217
|
+
"use strict";
|
|
218
|
+
DEFAULT_STYLE = {
|
|
219
|
+
strokeColor: "#FF3B30",
|
|
220
|
+
strokeWidth: 3,
|
|
221
|
+
fillColor: "transparent",
|
|
222
|
+
opacity: 1,
|
|
223
|
+
fontSize: 16,
|
|
224
|
+
fontFamily: "system-ui, -apple-system, sans-serif"
|
|
225
|
+
};
|
|
226
|
+
HIGHLIGHT_STYLE = {
|
|
227
|
+
strokeColor: "transparent",
|
|
228
|
+
fillColor: "#FFEB3B",
|
|
229
|
+
opacity: 0.4
|
|
230
|
+
};
|
|
231
|
+
BLUR_STYLE = {
|
|
232
|
+
strokeColor: "#666666",
|
|
233
|
+
fillColor: "#666666",
|
|
234
|
+
opacity: 0.8
|
|
235
|
+
};
|
|
236
|
+
COLOR_PALETTE = [
|
|
237
|
+
"#FF3B30",
|
|
238
|
+
// Red
|
|
239
|
+
"#FF9500",
|
|
240
|
+
// Orange
|
|
241
|
+
"#FFCC00",
|
|
242
|
+
// Yellow
|
|
243
|
+
"#34C759",
|
|
244
|
+
// Green
|
|
245
|
+
"#007AFF",
|
|
246
|
+
// Blue
|
|
247
|
+
"#5856D6",
|
|
248
|
+
// Purple
|
|
249
|
+
"#AF52DE",
|
|
250
|
+
// Pink
|
|
251
|
+
"#000000",
|
|
252
|
+
// Black
|
|
253
|
+
"#FFFFFF"
|
|
254
|
+
// White
|
|
255
|
+
];
|
|
256
|
+
STROKE_WIDTHS = [1, 2, 3, 5, 8];
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// src/annotation/toolbar.ts
|
|
261
|
+
var TOOL_ICONS, TOOL_LABELS, AnnotationToolbar;
|
|
262
|
+
var init_toolbar = __esm({
|
|
263
|
+
"src/annotation/toolbar.ts"() {
|
|
264
|
+
"use strict";
|
|
265
|
+
init_types();
|
|
266
|
+
TOOL_ICONS = {
|
|
267
|
+
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>`,
|
|
268
|
+
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>`,
|
|
269
|
+
ellipse: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="12" rx="9" ry="6"/></svg>`,
|
|
270
|
+
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>`,
|
|
271
|
+
line: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="19" x2="19" y2="5"/></svg>`,
|
|
272
|
+
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>`,
|
|
273
|
+
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>`,
|
|
274
|
+
text: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 4v3h5.5v12h3V7H19V4z"/></svg>`,
|
|
275
|
+
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>`
|
|
276
|
+
};
|
|
277
|
+
TOOL_LABELS = {
|
|
278
|
+
select: "S\xE9lection",
|
|
279
|
+
rectangle: "Rectangle",
|
|
280
|
+
ellipse: "Ellipse",
|
|
281
|
+
arrow: "Fl\xE8che",
|
|
282
|
+
line: "Ligne",
|
|
283
|
+
highlight: "Surbrillance",
|
|
284
|
+
blur: "Floutage",
|
|
285
|
+
text: "Texte",
|
|
286
|
+
freehand: "Dessin libre"
|
|
287
|
+
};
|
|
288
|
+
AnnotationToolbar = class {
|
|
289
|
+
constructor(config2) {
|
|
290
|
+
this.colorPicker = null;
|
|
291
|
+
this.strokePicker = null;
|
|
292
|
+
this.config = config2;
|
|
293
|
+
this.element = this.createToolbar();
|
|
294
|
+
}
|
|
295
|
+
getElement() {
|
|
296
|
+
return this.element;
|
|
297
|
+
}
|
|
298
|
+
setActiveTool(tool) {
|
|
299
|
+
this.config.activeTool = tool;
|
|
300
|
+
this.updateActiveState();
|
|
301
|
+
}
|
|
302
|
+
setStyle(style) {
|
|
303
|
+
this.config.style = style;
|
|
304
|
+
this.updateStyleDisplay();
|
|
305
|
+
}
|
|
306
|
+
createToolbar() {
|
|
307
|
+
const toolbar = document.createElement("div");
|
|
308
|
+
toolbar.className = "cf-toolbar";
|
|
309
|
+
const toolsSection = document.createElement("div");
|
|
310
|
+
toolsSection.className = "cf-toolbar-section cf-toolbar-tools";
|
|
311
|
+
for (const tool of this.config.tools) {
|
|
312
|
+
const button = this.createToolButton(tool);
|
|
313
|
+
toolsSection.appendChild(button);
|
|
314
|
+
}
|
|
315
|
+
const separator1 = document.createElement("div");
|
|
316
|
+
separator1.className = "cf-toolbar-separator";
|
|
317
|
+
const styleSection = document.createElement("div");
|
|
318
|
+
styleSection.className = "cf-toolbar-section cf-toolbar-style";
|
|
319
|
+
const colorBtn = document.createElement("button");
|
|
320
|
+
colorBtn.className = "cf-toolbar-btn cf-color-btn";
|
|
321
|
+
colorBtn.title = "Couleur";
|
|
322
|
+
colorBtn.innerHTML = `<span class="cf-color-preview" style="background: ${this.config.style.strokeColor}"></span>`;
|
|
323
|
+
colorBtn.addEventListener("click", () => this.toggleColorPicker());
|
|
324
|
+
styleSection.appendChild(colorBtn);
|
|
325
|
+
const strokeBtn = document.createElement("button");
|
|
326
|
+
strokeBtn.className = "cf-toolbar-btn cf-stroke-btn";
|
|
327
|
+
strokeBtn.title = "\xC9paisseur";
|
|
328
|
+
strokeBtn.innerHTML = `<span class="cf-stroke-preview">${this.config.style.strokeWidth}px</span>`;
|
|
329
|
+
strokeBtn.addEventListener("click", () => this.toggleStrokePicker());
|
|
330
|
+
styleSection.appendChild(strokeBtn);
|
|
331
|
+
const separator2 = document.createElement("div");
|
|
332
|
+
separator2.className = "cf-toolbar-separator";
|
|
333
|
+
const actionsSection = document.createElement("div");
|
|
334
|
+
actionsSection.className = "cf-toolbar-section cf-toolbar-actions";
|
|
335
|
+
const undoBtn = document.createElement("button");
|
|
336
|
+
undoBtn.className = "cf-toolbar-btn";
|
|
337
|
+
undoBtn.title = "Annuler (Ctrl+Z)";
|
|
338
|
+
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>`;
|
|
339
|
+
undoBtn.addEventListener("click", () => this.config.onUndo());
|
|
340
|
+
actionsSection.appendChild(undoBtn);
|
|
341
|
+
const clearBtn = document.createElement("button");
|
|
342
|
+
clearBtn.className = "cf-toolbar-btn";
|
|
343
|
+
clearBtn.title = "Tout effacer";
|
|
344
|
+
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>`;
|
|
345
|
+
clearBtn.addEventListener("click", () => this.config.onClear());
|
|
346
|
+
actionsSection.appendChild(clearBtn);
|
|
347
|
+
toolbar.appendChild(toolsSection);
|
|
348
|
+
toolbar.appendChild(separator1);
|
|
349
|
+
toolbar.appendChild(styleSection);
|
|
350
|
+
toolbar.appendChild(separator2);
|
|
351
|
+
toolbar.appendChild(actionsSection);
|
|
352
|
+
return toolbar;
|
|
353
|
+
}
|
|
354
|
+
createToolButton(tool) {
|
|
355
|
+
const button = document.createElement("button");
|
|
356
|
+
button.className = `cf-toolbar-btn cf-tool-btn ${tool === this.config.activeTool ? "active" : ""}`;
|
|
357
|
+
button.dataset.tool = tool;
|
|
358
|
+
button.title = TOOL_LABELS[tool];
|
|
359
|
+
button.innerHTML = TOOL_ICONS[tool];
|
|
360
|
+
button.addEventListener("click", () => {
|
|
361
|
+
this.config.onToolChange(tool);
|
|
362
|
+
this.setActiveTool(tool);
|
|
363
|
+
});
|
|
364
|
+
return button;
|
|
365
|
+
}
|
|
366
|
+
updateActiveState() {
|
|
367
|
+
const buttons = this.element.querySelectorAll(".cf-tool-btn");
|
|
368
|
+
buttons.forEach((btn) => {
|
|
369
|
+
const button = btn;
|
|
370
|
+
button.classList.toggle("active", button.dataset.tool === this.config.activeTool);
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
updateStyleDisplay() {
|
|
374
|
+
const colorPreview = this.element.querySelector(".cf-color-preview");
|
|
375
|
+
if (colorPreview) {
|
|
376
|
+
colorPreview.style.background = this.config.style.strokeColor;
|
|
377
|
+
}
|
|
378
|
+
const strokePreview = this.element.querySelector(".cf-stroke-preview");
|
|
379
|
+
if (strokePreview) {
|
|
380
|
+
strokePreview.textContent = `${this.config.style.strokeWidth}px`;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
toggleColorPicker() {
|
|
384
|
+
if (this.colorPicker) {
|
|
385
|
+
this.colorPicker.remove();
|
|
386
|
+
this.colorPicker = null;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (this.strokePicker) {
|
|
390
|
+
this.strokePicker.remove();
|
|
391
|
+
this.strokePicker = null;
|
|
392
|
+
}
|
|
393
|
+
this.colorPicker = document.createElement("div");
|
|
394
|
+
this.colorPicker.className = "cf-picker cf-color-picker";
|
|
395
|
+
for (const color of COLOR_PALETTE) {
|
|
396
|
+
const swatch = document.createElement("button");
|
|
397
|
+
swatch.className = `cf-color-swatch ${color === this.config.style.strokeColor ? "active" : ""}`;
|
|
398
|
+
swatch.style.background = color;
|
|
399
|
+
swatch.addEventListener("click", () => {
|
|
400
|
+
this.config.onStyleChange({ strokeColor: color });
|
|
401
|
+
this.setStyle({ ...this.config.style, strokeColor: color });
|
|
402
|
+
this.colorPicker?.remove();
|
|
403
|
+
this.colorPicker = null;
|
|
404
|
+
});
|
|
405
|
+
this.colorPicker.appendChild(swatch);
|
|
406
|
+
}
|
|
407
|
+
const colorBtn = this.element.querySelector(".cf-color-btn");
|
|
408
|
+
colorBtn?.appendChild(this.colorPicker);
|
|
409
|
+
}
|
|
410
|
+
toggleStrokePicker() {
|
|
411
|
+
if (this.strokePicker) {
|
|
412
|
+
this.strokePicker.remove();
|
|
413
|
+
this.strokePicker = null;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (this.colorPicker) {
|
|
417
|
+
this.colorPicker.remove();
|
|
418
|
+
this.colorPicker = null;
|
|
419
|
+
}
|
|
420
|
+
this.strokePicker = document.createElement("div");
|
|
421
|
+
this.strokePicker.className = "cf-picker cf-stroke-picker";
|
|
422
|
+
for (const width of STROKE_WIDTHS) {
|
|
423
|
+
const option = document.createElement("button");
|
|
424
|
+
option.className = `cf-stroke-option ${width === this.config.style.strokeWidth ? "active" : ""}`;
|
|
425
|
+
option.innerHTML = `<span style="height: ${width}px"></span> ${width}px`;
|
|
426
|
+
option.addEventListener("click", () => {
|
|
427
|
+
this.config.onStyleChange({ strokeWidth: width });
|
|
428
|
+
this.setStyle({ ...this.config.style, strokeWidth: width });
|
|
429
|
+
this.strokePicker?.remove();
|
|
430
|
+
this.strokePicker = null;
|
|
431
|
+
});
|
|
432
|
+
this.strokePicker.appendChild(option);
|
|
433
|
+
}
|
|
434
|
+
const strokeBtn = this.element.querySelector(".cf-stroke-btn");
|
|
435
|
+
strokeBtn?.appendChild(this.strokePicker);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// src/annotation/styles.ts
|
|
442
|
+
function injectAnnotationStyles() {
|
|
443
|
+
if (stylesInjected) return;
|
|
444
|
+
const styleElement = document.createElement("style");
|
|
445
|
+
styleElement.id = "cf-annotation-styles";
|
|
446
|
+
styleElement.textContent = STYLES;
|
|
447
|
+
document.head.appendChild(styleElement);
|
|
448
|
+
stylesInjected = true;
|
|
449
|
+
}
|
|
450
|
+
var STYLES, stylesInjected;
|
|
451
|
+
var init_styles = __esm({
|
|
452
|
+
"src/annotation/styles.ts"() {
|
|
453
|
+
"use strict";
|
|
454
|
+
STYLES = `
|
|
455
|
+
/* Annotation Overlay */
|
|
456
|
+
.cf-annotation-overlay {
|
|
457
|
+
position: fixed;
|
|
458
|
+
top: 0;
|
|
459
|
+
left: 0;
|
|
460
|
+
right: 0;
|
|
461
|
+
bottom: 0;
|
|
462
|
+
z-index: 999999;
|
|
463
|
+
background: rgba(0, 0, 0, 0.85);
|
|
464
|
+
display: flex;
|
|
465
|
+
align-items: center;
|
|
466
|
+
justify-content: center;
|
|
467
|
+
animation: cf-fade-in 0.2s ease-out;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
@keyframes cf-fade-in {
|
|
471
|
+
from { opacity: 0; }
|
|
472
|
+
to { opacity: 1; }
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/* Editor Wrapper */
|
|
476
|
+
.cf-annotation-wrapper {
|
|
477
|
+
display: flex;
|
|
478
|
+
flex-direction: column;
|
|
479
|
+
max-width: 95vw;
|
|
480
|
+
max-height: 95vh;
|
|
481
|
+
background: #1a1a1a;
|
|
482
|
+
border-radius: 12px;
|
|
483
|
+
overflow: hidden;
|
|
484
|
+
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/* Canvas Container */
|
|
488
|
+
.cf-annotation-canvas-container {
|
|
489
|
+
flex: 1;
|
|
490
|
+
display: flex;
|
|
491
|
+
align-items: center;
|
|
492
|
+
justify-content: center;
|
|
493
|
+
overflow: auto;
|
|
494
|
+
padding: 16px;
|
|
495
|
+
background: #0d0d0d;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.cf-annotation-canvas {
|
|
499
|
+
max-width: 100%;
|
|
500
|
+
max-height: calc(95vh - 140px);
|
|
501
|
+
border-radius: 4px;
|
|
502
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
503
|
+
cursor: crosshair;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/* Toolbar */
|
|
507
|
+
.cf-toolbar {
|
|
508
|
+
display: flex;
|
|
509
|
+
align-items: center;
|
|
510
|
+
gap: 8px;
|
|
511
|
+
padding: 12px 16px;
|
|
512
|
+
background: #2a2a2a;
|
|
513
|
+
border-bottom: 1px solid #3a3a3a;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.cf-toolbar-section {
|
|
517
|
+
display: flex;
|
|
518
|
+
align-items: center;
|
|
519
|
+
gap: 4px;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.cf-toolbar-separator {
|
|
523
|
+
width: 1px;
|
|
524
|
+
height: 24px;
|
|
525
|
+
background: #4a4a4a;
|
|
526
|
+
margin: 0 8px;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.cf-toolbar-btn {
|
|
530
|
+
display: flex;
|
|
531
|
+
align-items: center;
|
|
532
|
+
justify-content: center;
|
|
533
|
+
width: 36px;
|
|
534
|
+
height: 36px;
|
|
535
|
+
padding: 0;
|
|
536
|
+
border: none;
|
|
537
|
+
border-radius: 8px;
|
|
538
|
+
background: transparent;
|
|
539
|
+
color: #999;
|
|
540
|
+
cursor: pointer;
|
|
541
|
+
transition: all 0.15s ease;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.cf-toolbar-btn:hover {
|
|
545
|
+
background: #3a3a3a;
|
|
546
|
+
color: #fff;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.cf-toolbar-btn.active {
|
|
550
|
+
background: #007AFF;
|
|
551
|
+
color: #fff;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.cf-toolbar-btn svg {
|
|
555
|
+
width: 20px;
|
|
556
|
+
height: 20px;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/* Color Button */
|
|
560
|
+
.cf-color-btn {
|
|
561
|
+
position: relative;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.cf-color-preview {
|
|
565
|
+
width: 20px;
|
|
566
|
+
height: 20px;
|
|
567
|
+
border-radius: 50%;
|
|
568
|
+
border: 2px solid #fff;
|
|
569
|
+
box-shadow: 0 0 0 1px rgba(0,0,0,0.2);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/* Stroke Button */
|
|
573
|
+
.cf-stroke-btn {
|
|
574
|
+
width: auto;
|
|
575
|
+
padding: 0 12px;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.cf-stroke-preview {
|
|
579
|
+
font-size: 12px;
|
|
580
|
+
font-weight: 500;
|
|
581
|
+
color: inherit;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/* Pickers */
|
|
585
|
+
.cf-picker {
|
|
586
|
+
position: absolute;
|
|
587
|
+
top: 100%;
|
|
588
|
+
left: 50%;
|
|
589
|
+
transform: translateX(-50%);
|
|
590
|
+
margin-top: 8px;
|
|
591
|
+
padding: 8px;
|
|
592
|
+
background: #2a2a2a;
|
|
593
|
+
border-radius: 8px;
|
|
594
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
595
|
+
z-index: 10;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.cf-color-picker {
|
|
599
|
+
display: grid;
|
|
600
|
+
grid-template-columns: repeat(5, 1fr);
|
|
601
|
+
gap: 4px;
|
|
602
|
+
width: 140px;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.cf-color-swatch {
|
|
606
|
+
width: 24px;
|
|
607
|
+
height: 24px;
|
|
608
|
+
border-radius: 50%;
|
|
609
|
+
border: 2px solid transparent;
|
|
610
|
+
cursor: pointer;
|
|
611
|
+
transition: transform 0.15s ease;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.cf-color-swatch:hover {
|
|
615
|
+
transform: scale(1.15);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.cf-color-swatch.active {
|
|
619
|
+
border-color: #fff;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.cf-stroke-picker {
|
|
623
|
+
display: flex;
|
|
624
|
+
flex-direction: column;
|
|
625
|
+
gap: 4px;
|
|
626
|
+
min-width: 80px;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.cf-stroke-option {
|
|
630
|
+
display: flex;
|
|
631
|
+
align-items: center;
|
|
632
|
+
gap: 8px;
|
|
633
|
+
padding: 6px 10px;
|
|
634
|
+
border: none;
|
|
635
|
+
border-radius: 4px;
|
|
636
|
+
background: transparent;
|
|
637
|
+
color: #999;
|
|
638
|
+
font-size: 12px;
|
|
639
|
+
cursor: pointer;
|
|
640
|
+
transition: all 0.15s ease;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.cf-stroke-option:hover {
|
|
644
|
+
background: #3a3a3a;
|
|
645
|
+
color: #fff;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.cf-stroke-option.active {
|
|
649
|
+
background: #007AFF;
|
|
650
|
+
color: #fff;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.cf-stroke-option span {
|
|
654
|
+
width: 24px;
|
|
655
|
+
background: currentColor;
|
|
656
|
+
border-radius: 2px;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/* Action Buttons */
|
|
660
|
+
.cf-annotation-actions {
|
|
661
|
+
display: flex;
|
|
662
|
+
justify-content: flex-end;
|
|
663
|
+
gap: 12px;
|
|
664
|
+
padding: 12px 16px;
|
|
665
|
+
background: #2a2a2a;
|
|
666
|
+
border-top: 1px solid #3a3a3a;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.cf-btn {
|
|
670
|
+
display: flex;
|
|
671
|
+
align-items: center;
|
|
672
|
+
justify-content: center;
|
|
673
|
+
padding: 10px 20px;
|
|
674
|
+
border: none;
|
|
675
|
+
border-radius: 8px;
|
|
676
|
+
font-size: 14px;
|
|
677
|
+
font-weight: 500;
|
|
678
|
+
cursor: pointer;
|
|
679
|
+
transition: all 0.15s ease;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.cf-btn-primary {
|
|
683
|
+
background: #007AFF;
|
|
684
|
+
color: #fff;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.cf-btn-primary:hover {
|
|
688
|
+
background: #0066DD;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.cf-btn-secondary {
|
|
692
|
+
background: #3a3a3a;
|
|
693
|
+
color: #fff;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.cf-btn-secondary:hover {
|
|
697
|
+
background: #4a4a4a;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/* Text Input */
|
|
701
|
+
.cf-annotation-text-input {
|
|
702
|
+
position: fixed;
|
|
703
|
+
z-index: 1000000;
|
|
704
|
+
padding: 4px 8px;
|
|
705
|
+
border: 2px solid #007AFF;
|
|
706
|
+
border-radius: 4px;
|
|
707
|
+
background: rgba(255, 255, 255, 0.95);
|
|
708
|
+
font-size: 16px;
|
|
709
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
710
|
+
outline: none;
|
|
711
|
+
min-width: 150px;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/* Tool-specific cursors */
|
|
715
|
+
.cf-annotation-canvas[data-tool="select"] { cursor: default; }
|
|
716
|
+
.cf-annotation-canvas[data-tool="rectangle"] { cursor: crosshair; }
|
|
717
|
+
.cf-annotation-canvas[data-tool="ellipse"] { cursor: crosshair; }
|
|
718
|
+
.cf-annotation-canvas[data-tool="arrow"] { cursor: crosshair; }
|
|
719
|
+
.cf-annotation-canvas[data-tool="line"] { cursor: crosshair; }
|
|
720
|
+
.cf-annotation-canvas[data-tool="highlight"] { cursor: crosshair; }
|
|
721
|
+
.cf-annotation-canvas[data-tool="blur"] { cursor: crosshair; }
|
|
722
|
+
.cf-annotation-canvas[data-tool="text"] { cursor: text; }
|
|
723
|
+
.cf-annotation-canvas[data-tool="freehand"] { cursor: crosshair; }
|
|
724
|
+
|
|
725
|
+
/* Responsive */
|
|
726
|
+
@media (max-width: 768px) {
|
|
727
|
+
.cf-toolbar {
|
|
728
|
+
flex-wrap: wrap;
|
|
729
|
+
justify-content: center;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
.cf-toolbar-separator {
|
|
733
|
+
display: none;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.cf-annotation-actions {
|
|
737
|
+
justify-content: stretch;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.cf-annotation-actions .cf-btn {
|
|
741
|
+
flex: 1;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
`;
|
|
745
|
+
stylesInjected = false;
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
// src/annotation/editor.ts
|
|
750
|
+
var AnnotationEditor;
|
|
751
|
+
var init_editor = __esm({
|
|
752
|
+
"src/annotation/editor.ts"() {
|
|
753
|
+
"use strict";
|
|
754
|
+
init_types();
|
|
755
|
+
init_toolbar();
|
|
756
|
+
init_styles();
|
|
757
|
+
AnnotationEditor = class {
|
|
758
|
+
constructor(config2) {
|
|
759
|
+
this.container = null;
|
|
760
|
+
this.canvas = null;
|
|
761
|
+
this.ctx = null;
|
|
762
|
+
this.toolbar = null;
|
|
763
|
+
this.annotations = [];
|
|
764
|
+
this.backgroundImage = null;
|
|
765
|
+
this.state = {
|
|
766
|
+
activeTool: "rectangle",
|
|
767
|
+
style: { ...DEFAULT_STYLE },
|
|
768
|
+
isDrawing: false,
|
|
769
|
+
currentAnnotation: null
|
|
770
|
+
};
|
|
771
|
+
this.startPoint = null;
|
|
772
|
+
this.freehandPoints = [];
|
|
773
|
+
this.textInput = null;
|
|
774
|
+
this.config = {
|
|
775
|
+
...config2,
|
|
776
|
+
tools: config2.tools || ["select", "rectangle", "arrow", "highlight", "blur", "text", "freehand"],
|
|
777
|
+
defaultStyle: config2.defaultStyle || DEFAULT_STYLE
|
|
778
|
+
};
|
|
779
|
+
this.state.style = { ...this.config.defaultStyle };
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Open the annotation editor with a screenshot
|
|
783
|
+
*/
|
|
784
|
+
async open(screenshotDataUrl2) {
|
|
785
|
+
injectAnnotationStyles();
|
|
786
|
+
this.backgroundImage = await this.loadImage(screenshotDataUrl2);
|
|
787
|
+
this.createEditorUI();
|
|
788
|
+
this.setupEventListeners();
|
|
789
|
+
this.render();
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Close the editor
|
|
793
|
+
*/
|
|
794
|
+
close() {
|
|
795
|
+
if (this.container) {
|
|
796
|
+
this.container.remove();
|
|
797
|
+
this.container = null;
|
|
798
|
+
}
|
|
799
|
+
this.canvas = null;
|
|
800
|
+
this.ctx = null;
|
|
801
|
+
this.toolbar = null;
|
|
802
|
+
this.annotations = [];
|
|
803
|
+
this.backgroundImage = null;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Get the annotated image as data URL
|
|
807
|
+
*/
|
|
808
|
+
getAnnotatedImage() {
|
|
809
|
+
if (!this.canvas || !this.ctx) return "";
|
|
810
|
+
const tempCanvas = document.createElement("canvas");
|
|
811
|
+
tempCanvas.width = this.canvas.width;
|
|
812
|
+
tempCanvas.height = this.canvas.height;
|
|
813
|
+
const tempCtx = tempCanvas.getContext("2d");
|
|
814
|
+
if (this.backgroundImage) {
|
|
815
|
+
tempCtx.drawImage(this.backgroundImage, 0, 0);
|
|
816
|
+
}
|
|
817
|
+
this.drawAnnotations(tempCtx);
|
|
818
|
+
return tempCanvas.toDataURL("image/png");
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Get annotations data
|
|
822
|
+
*/
|
|
823
|
+
getAnnotations() {
|
|
824
|
+
return [...this.annotations];
|
|
825
|
+
}
|
|
826
|
+
// Private methods
|
|
827
|
+
loadImage(src) {
|
|
828
|
+
return new Promise((resolve, reject) => {
|
|
829
|
+
const img = new Image();
|
|
830
|
+
img.onload = () => resolve(img);
|
|
831
|
+
img.onerror = reject;
|
|
832
|
+
img.src = src;
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
createEditorUI() {
|
|
836
|
+
this.container = document.createElement("div");
|
|
837
|
+
this.container.className = "cf-annotation-overlay";
|
|
838
|
+
const wrapper = document.createElement("div");
|
|
839
|
+
wrapper.className = "cf-annotation-wrapper";
|
|
840
|
+
const canvasContainer = document.createElement("div");
|
|
841
|
+
canvasContainer.className = "cf-annotation-canvas-container";
|
|
842
|
+
this.canvas = document.createElement("canvas");
|
|
843
|
+
this.canvas.className = "cf-annotation-canvas";
|
|
844
|
+
this.canvas.width = this.backgroundImage?.width || 1920;
|
|
845
|
+
this.canvas.height = this.backgroundImage?.height || 1080;
|
|
846
|
+
this.ctx = this.canvas.getContext("2d");
|
|
847
|
+
canvasContainer.appendChild(this.canvas);
|
|
848
|
+
this.toolbar = new AnnotationToolbar({
|
|
849
|
+
tools: this.config.tools,
|
|
850
|
+
activeTool: this.state.activeTool,
|
|
851
|
+
style: this.state.style,
|
|
852
|
+
onToolChange: (tool) => this.setActiveTool(tool),
|
|
853
|
+
onStyleChange: (style) => this.setStyle(style),
|
|
854
|
+
onUndo: () => this.undo(),
|
|
855
|
+
onClear: () => this.clearAll(),
|
|
856
|
+
onSave: () => this.save(),
|
|
857
|
+
onCancel: () => this.cancel()
|
|
858
|
+
});
|
|
859
|
+
const actions = document.createElement("div");
|
|
860
|
+
actions.className = "cf-annotation-actions";
|
|
861
|
+
actions.innerHTML = `
|
|
862
|
+
<button class="cf-btn cf-btn-secondary" data-action="cancel">Annuler</button>
|
|
863
|
+
<button class="cf-btn cf-btn-primary" data-action="save">Enregistrer</button>
|
|
864
|
+
`;
|
|
865
|
+
wrapper.appendChild(this.toolbar.getElement());
|
|
866
|
+
wrapper.appendChild(canvasContainer);
|
|
867
|
+
wrapper.appendChild(actions);
|
|
868
|
+
this.container.appendChild(wrapper);
|
|
869
|
+
document.body.appendChild(this.container);
|
|
870
|
+
actions.querySelector('[data-action="cancel"]')?.addEventListener("click", () => this.cancel());
|
|
871
|
+
actions.querySelector('[data-action="save"]')?.addEventListener("click", () => this.save());
|
|
872
|
+
}
|
|
873
|
+
setupEventListeners() {
|
|
874
|
+
if (!this.canvas) return;
|
|
875
|
+
this.canvas.addEventListener("mousedown", this.handleMouseDown.bind(this));
|
|
876
|
+
this.canvas.addEventListener("mousemove", this.handleMouseMove.bind(this));
|
|
877
|
+
this.canvas.addEventListener("mouseup", this.handleMouseUp.bind(this));
|
|
878
|
+
this.canvas.addEventListener("mouseleave", this.handleMouseUp.bind(this));
|
|
879
|
+
this.canvas.addEventListener("touchstart", this.handleTouchStart.bind(this));
|
|
880
|
+
this.canvas.addEventListener("touchmove", this.handleTouchMove.bind(this));
|
|
881
|
+
this.canvas.addEventListener("touchend", this.handleTouchEnd.bind(this));
|
|
882
|
+
document.addEventListener("keydown", this.handleKeyDown.bind(this));
|
|
883
|
+
}
|
|
884
|
+
handleMouseDown(e) {
|
|
885
|
+
const point = this.getCanvasPoint(e);
|
|
886
|
+
this.startDrawing(point);
|
|
887
|
+
}
|
|
888
|
+
handleMouseMove(e) {
|
|
889
|
+
const point = this.getCanvasPoint(e);
|
|
890
|
+
this.continueDrawing(point);
|
|
891
|
+
}
|
|
892
|
+
handleMouseUp(_e) {
|
|
893
|
+
this.finishDrawing();
|
|
894
|
+
}
|
|
895
|
+
handleTouchStart(e) {
|
|
896
|
+
e.preventDefault();
|
|
897
|
+
const touch = e.touches[0];
|
|
898
|
+
const point = this.getCanvasPointFromTouch(touch);
|
|
899
|
+
this.startDrawing(point);
|
|
900
|
+
}
|
|
901
|
+
handleTouchMove(e) {
|
|
902
|
+
e.preventDefault();
|
|
903
|
+
const touch = e.touches[0];
|
|
904
|
+
const point = this.getCanvasPointFromTouch(touch);
|
|
905
|
+
this.continueDrawing(point);
|
|
906
|
+
}
|
|
907
|
+
handleTouchEnd(e) {
|
|
908
|
+
e.preventDefault();
|
|
909
|
+
this.finishDrawing();
|
|
910
|
+
}
|
|
911
|
+
handleKeyDown(e) {
|
|
912
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "z") {
|
|
913
|
+
e.preventDefault();
|
|
914
|
+
this.undo();
|
|
915
|
+
}
|
|
916
|
+
if (e.key === "Escape") {
|
|
917
|
+
if (this.state.isDrawing) {
|
|
918
|
+
this.state.isDrawing = false;
|
|
919
|
+
this.state.currentAnnotation = null;
|
|
920
|
+
this.render();
|
|
921
|
+
} else {
|
|
922
|
+
this.cancel();
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
if (e.key === "Enter" && !this.state.isDrawing) {
|
|
926
|
+
this.save();
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
getCanvasPoint(e) {
|
|
930
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
931
|
+
const scaleX = this.canvas.width / rect.width;
|
|
932
|
+
const scaleY = this.canvas.height / rect.height;
|
|
933
|
+
return {
|
|
934
|
+
x: (e.clientX - rect.left) * scaleX,
|
|
935
|
+
y: (e.clientY - rect.top) * scaleY
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
getCanvasPointFromTouch(touch) {
|
|
939
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
940
|
+
const scaleX = this.canvas.width / rect.width;
|
|
941
|
+
const scaleY = this.canvas.height / rect.height;
|
|
942
|
+
return {
|
|
943
|
+
x: (touch.clientX - rect.left) * scaleX,
|
|
944
|
+
y: (touch.clientY - rect.top) * scaleY
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
startDrawing(point) {
|
|
948
|
+
if (this.state.activeTool === "select") return;
|
|
949
|
+
this.state.isDrawing = true;
|
|
950
|
+
this.startPoint = point;
|
|
951
|
+
if (this.state.activeTool === "text") {
|
|
952
|
+
this.showTextInput(point);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (this.state.activeTool === "freehand") {
|
|
956
|
+
this.freehandPoints = [point];
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
continueDrawing(point) {
|
|
960
|
+
if (!this.state.isDrawing || !this.startPoint) return;
|
|
961
|
+
if (this.state.activeTool === "freehand") {
|
|
962
|
+
this.freehandPoints.push(point);
|
|
963
|
+
}
|
|
964
|
+
this.state.currentAnnotation = this.createAnnotation(this.startPoint, point);
|
|
965
|
+
this.render();
|
|
966
|
+
}
|
|
967
|
+
finishDrawing() {
|
|
968
|
+
if (!this.state.isDrawing || !this.state.currentAnnotation) {
|
|
969
|
+
this.state.isDrawing = false;
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (this.isValidAnnotation(this.state.currentAnnotation)) {
|
|
973
|
+
this.annotations.push(this.state.currentAnnotation);
|
|
974
|
+
}
|
|
975
|
+
this.state.isDrawing = false;
|
|
976
|
+
this.state.currentAnnotation = null;
|
|
977
|
+
this.startPoint = null;
|
|
978
|
+
this.freehandPoints = [];
|
|
979
|
+
this.render();
|
|
980
|
+
}
|
|
981
|
+
isValidAnnotation(annotation) {
|
|
982
|
+
switch (annotation.type) {
|
|
983
|
+
case "rectangle":
|
|
984
|
+
case "ellipse":
|
|
985
|
+
case "highlight":
|
|
986
|
+
case "blur":
|
|
987
|
+
const bounds = annotation.bounds;
|
|
988
|
+
return Math.abs(bounds.width) > 5 && Math.abs(bounds.height) > 5;
|
|
989
|
+
case "arrow":
|
|
990
|
+
case "line":
|
|
991
|
+
const start = annotation.start;
|
|
992
|
+
const end = annotation.end;
|
|
993
|
+
const dist = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
|
|
994
|
+
return dist > 10;
|
|
995
|
+
case "freehand":
|
|
996
|
+
return annotation.points.length > 2;
|
|
997
|
+
case "text":
|
|
998
|
+
return !!annotation.text?.trim();
|
|
999
|
+
default:
|
|
1000
|
+
return true;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
createAnnotation(start, end) {
|
|
1004
|
+
const id = this.generateId();
|
|
1005
|
+
const baseStyle = { ...this.state.style };
|
|
1006
|
+
switch (this.state.activeTool) {
|
|
1007
|
+
case "rectangle":
|
|
1008
|
+
return {
|
|
1009
|
+
id,
|
|
1010
|
+
type: "rectangle",
|
|
1011
|
+
bounds: this.createBounds(start, end),
|
|
1012
|
+
style: baseStyle,
|
|
1013
|
+
timestamp: Date.now()
|
|
1014
|
+
};
|
|
1015
|
+
case "ellipse":
|
|
1016
|
+
return {
|
|
1017
|
+
id,
|
|
1018
|
+
type: "ellipse",
|
|
1019
|
+
bounds: this.createBounds(start, end),
|
|
1020
|
+
style: baseStyle,
|
|
1021
|
+
timestamp: Date.now()
|
|
1022
|
+
};
|
|
1023
|
+
case "arrow":
|
|
1024
|
+
return {
|
|
1025
|
+
id,
|
|
1026
|
+
type: "arrow",
|
|
1027
|
+
start: { ...start },
|
|
1028
|
+
end: { ...end },
|
|
1029
|
+
style: baseStyle,
|
|
1030
|
+
timestamp: Date.now()
|
|
1031
|
+
};
|
|
1032
|
+
case "line":
|
|
1033
|
+
return {
|
|
1034
|
+
id,
|
|
1035
|
+
type: "line",
|
|
1036
|
+
start: { ...start },
|
|
1037
|
+
end: { ...end },
|
|
1038
|
+
style: baseStyle,
|
|
1039
|
+
timestamp: Date.now()
|
|
1040
|
+
};
|
|
1041
|
+
case "highlight":
|
|
1042
|
+
return {
|
|
1043
|
+
id,
|
|
1044
|
+
type: "highlight",
|
|
1045
|
+
bounds: this.createBounds(start, end),
|
|
1046
|
+
style: { ...baseStyle, ...HIGHLIGHT_STYLE },
|
|
1047
|
+
timestamp: Date.now()
|
|
1048
|
+
};
|
|
1049
|
+
case "blur":
|
|
1050
|
+
return {
|
|
1051
|
+
id,
|
|
1052
|
+
type: "blur",
|
|
1053
|
+
bounds: this.createBounds(start, end),
|
|
1054
|
+
style: { ...baseStyle, ...BLUR_STYLE },
|
|
1055
|
+
blurAmount: 10,
|
|
1056
|
+
timestamp: Date.now()
|
|
1057
|
+
};
|
|
1058
|
+
case "freehand":
|
|
1059
|
+
return {
|
|
1060
|
+
id,
|
|
1061
|
+
type: "freehand",
|
|
1062
|
+
points: [...this.freehandPoints],
|
|
1063
|
+
style: baseStyle,
|
|
1064
|
+
timestamp: Date.now()
|
|
1065
|
+
};
|
|
1066
|
+
default:
|
|
1067
|
+
return {
|
|
1068
|
+
id,
|
|
1069
|
+
type: "rectangle",
|
|
1070
|
+
bounds: this.createBounds(start, end),
|
|
1071
|
+
style: baseStyle,
|
|
1072
|
+
timestamp: Date.now()
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
createBounds(start, end) {
|
|
1077
|
+
return {
|
|
1078
|
+
x: Math.min(start.x, end.x),
|
|
1079
|
+
y: Math.min(start.y, end.y),
|
|
1080
|
+
width: Math.abs(end.x - start.x),
|
|
1081
|
+
height: Math.abs(end.y - start.y)
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
showTextInput(point) {
|
|
1085
|
+
if (this.textInput) {
|
|
1086
|
+
this.textInput.remove();
|
|
1087
|
+
}
|
|
1088
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
1089
|
+
const scaleX = rect.width / this.canvas.width;
|
|
1090
|
+
const scaleY = rect.height / this.canvas.height;
|
|
1091
|
+
this.textInput = document.createElement("input");
|
|
1092
|
+
this.textInput.type = "text";
|
|
1093
|
+
this.textInput.className = "cf-annotation-text-input";
|
|
1094
|
+
this.textInput.style.left = `${rect.left + point.x * scaleX}px`;
|
|
1095
|
+
this.textInput.style.top = `${rect.top + point.y * scaleY}px`;
|
|
1096
|
+
this.textInput.style.color = this.state.style.strokeColor;
|
|
1097
|
+
this.textInput.style.fontSize = `${(this.state.style.fontSize || 16) * scaleY}px`;
|
|
1098
|
+
this.textInput.placeholder = "Tapez votre texte...";
|
|
1099
|
+
document.body.appendChild(this.textInput);
|
|
1100
|
+
this.textInput.focus();
|
|
1101
|
+
const handleTextSubmit = () => {
|
|
1102
|
+
if (this.textInput && this.textInput.value.trim()) {
|
|
1103
|
+
const textAnnotation = {
|
|
1104
|
+
id: this.generateId(),
|
|
1105
|
+
type: "text",
|
|
1106
|
+
position: point,
|
|
1107
|
+
text: this.textInput.value.trim(),
|
|
1108
|
+
style: { ...this.state.style },
|
|
1109
|
+
timestamp: Date.now()
|
|
1110
|
+
};
|
|
1111
|
+
this.annotations.push(textAnnotation);
|
|
1112
|
+
this.render();
|
|
1113
|
+
}
|
|
1114
|
+
this.textInput?.remove();
|
|
1115
|
+
this.textInput = null;
|
|
1116
|
+
this.state.isDrawing = false;
|
|
1117
|
+
};
|
|
1118
|
+
this.textInput.addEventListener("blur", handleTextSubmit);
|
|
1119
|
+
this.textInput.addEventListener("keydown", (e) => {
|
|
1120
|
+
if (e.key === "Enter") {
|
|
1121
|
+
handleTextSubmit();
|
|
1122
|
+
} else if (e.key === "Escape") {
|
|
1123
|
+
this.textInput?.remove();
|
|
1124
|
+
this.textInput = null;
|
|
1125
|
+
this.state.isDrawing = false;
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
generateId() {
|
|
1130
|
+
return `ann_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1131
|
+
}
|
|
1132
|
+
setActiveTool(tool) {
|
|
1133
|
+
this.state.activeTool = tool;
|
|
1134
|
+
this.toolbar?.setActiveTool(tool);
|
|
1135
|
+
if (this.canvas) {
|
|
1136
|
+
this.canvas.style.cursor = tool === "select" ? "default" : "crosshair";
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
setStyle(style) {
|
|
1140
|
+
this.state.style = { ...this.state.style, ...style };
|
|
1141
|
+
this.toolbar?.setStyle(this.state.style);
|
|
1142
|
+
}
|
|
1143
|
+
undo() {
|
|
1144
|
+
if (this.annotations.length > 0) {
|
|
1145
|
+
this.annotations.pop();
|
|
1146
|
+
this.render();
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
clearAll() {
|
|
1150
|
+
this.annotations = [];
|
|
1151
|
+
this.render();
|
|
1152
|
+
}
|
|
1153
|
+
save() {
|
|
1154
|
+
const imageData = this.getAnnotatedImage();
|
|
1155
|
+
this.config.onSave?.(this.annotations, imageData);
|
|
1156
|
+
this.close();
|
|
1157
|
+
}
|
|
1158
|
+
cancel() {
|
|
1159
|
+
this.config.onCancel?.();
|
|
1160
|
+
this.close();
|
|
1161
|
+
}
|
|
1162
|
+
render() {
|
|
1163
|
+
if (!this.ctx || !this.canvas) return;
|
|
1164
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
1165
|
+
if (this.backgroundImage) {
|
|
1166
|
+
this.ctx.drawImage(this.backgroundImage, 0, 0);
|
|
1167
|
+
}
|
|
1168
|
+
this.drawAnnotations(this.ctx);
|
|
1169
|
+
if (this.state.currentAnnotation) {
|
|
1170
|
+
this.drawAnnotation(this.ctx, this.state.currentAnnotation);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
drawAnnotations(ctx) {
|
|
1174
|
+
for (const annotation of this.annotations) {
|
|
1175
|
+
this.drawAnnotation(ctx, annotation);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
drawAnnotation(ctx, annotation) {
|
|
1179
|
+
ctx.save();
|
|
1180
|
+
ctx.globalAlpha = annotation.style.opacity;
|
|
1181
|
+
ctx.strokeStyle = annotation.style.strokeColor;
|
|
1182
|
+
ctx.fillStyle = annotation.style.fillColor;
|
|
1183
|
+
ctx.lineWidth = annotation.style.strokeWidth;
|
|
1184
|
+
ctx.lineCap = "round";
|
|
1185
|
+
ctx.lineJoin = "round";
|
|
1186
|
+
switch (annotation.type) {
|
|
1187
|
+
case "rectangle":
|
|
1188
|
+
this.drawRectangle(ctx, annotation.bounds, annotation.style);
|
|
1189
|
+
break;
|
|
1190
|
+
case "ellipse":
|
|
1191
|
+
this.drawEllipse(ctx, annotation.bounds, annotation.style);
|
|
1192
|
+
break;
|
|
1193
|
+
case "arrow":
|
|
1194
|
+
this.drawArrow(ctx, annotation.start, annotation.end, annotation.style);
|
|
1195
|
+
break;
|
|
1196
|
+
case "line":
|
|
1197
|
+
this.drawLine(ctx, annotation.start, annotation.end);
|
|
1198
|
+
break;
|
|
1199
|
+
case "highlight":
|
|
1200
|
+
this.drawHighlight(ctx, annotation.bounds, annotation.style);
|
|
1201
|
+
break;
|
|
1202
|
+
case "blur":
|
|
1203
|
+
this.drawBlur(ctx, annotation.bounds, annotation.blurAmount);
|
|
1204
|
+
break;
|
|
1205
|
+
case "text":
|
|
1206
|
+
this.drawText(ctx, annotation.position, annotation.text, annotation.style);
|
|
1207
|
+
break;
|
|
1208
|
+
case "freehand":
|
|
1209
|
+
this.drawFreehand(ctx, annotation.points);
|
|
1210
|
+
break;
|
|
1211
|
+
}
|
|
1212
|
+
ctx.restore();
|
|
1213
|
+
}
|
|
1214
|
+
drawRectangle(ctx, bounds, style) {
|
|
1215
|
+
if (style.fillColor !== "transparent") {
|
|
1216
|
+
ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
1217
|
+
}
|
|
1218
|
+
ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
1219
|
+
}
|
|
1220
|
+
drawEllipse(ctx, bounds, style) {
|
|
1221
|
+
const centerX = bounds.x + bounds.width / 2;
|
|
1222
|
+
const centerY = bounds.y + bounds.height / 2;
|
|
1223
|
+
const radiusX = bounds.width / 2;
|
|
1224
|
+
const radiusY = bounds.height / 2;
|
|
1225
|
+
ctx.beginPath();
|
|
1226
|
+
ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
|
|
1227
|
+
if (style.fillColor !== "transparent") {
|
|
1228
|
+
ctx.fill();
|
|
1229
|
+
}
|
|
1230
|
+
ctx.stroke();
|
|
1231
|
+
}
|
|
1232
|
+
drawArrow(ctx, start, end, style) {
|
|
1233
|
+
const headLength = 15 + style.strokeWidth * 2;
|
|
1234
|
+
const angle = Math.atan2(end.y - start.y, end.x - start.x);
|
|
1235
|
+
ctx.beginPath();
|
|
1236
|
+
ctx.moveTo(start.x, start.y);
|
|
1237
|
+
ctx.lineTo(end.x, end.y);
|
|
1238
|
+
ctx.stroke();
|
|
1239
|
+
ctx.beginPath();
|
|
1240
|
+
ctx.moveTo(end.x, end.y);
|
|
1241
|
+
ctx.lineTo(
|
|
1242
|
+
end.x - headLength * Math.cos(angle - Math.PI / 6),
|
|
1243
|
+
end.y - headLength * Math.sin(angle - Math.PI / 6)
|
|
1244
|
+
);
|
|
1245
|
+
ctx.lineTo(
|
|
1246
|
+
end.x - headLength * Math.cos(angle + Math.PI / 6),
|
|
1247
|
+
end.y - headLength * Math.sin(angle + Math.PI / 6)
|
|
1248
|
+
);
|
|
1249
|
+
ctx.closePath();
|
|
1250
|
+
ctx.fillStyle = style.strokeColor;
|
|
1251
|
+
ctx.fill();
|
|
1252
|
+
}
|
|
1253
|
+
drawLine(ctx, start, end) {
|
|
1254
|
+
ctx.beginPath();
|
|
1255
|
+
ctx.moveTo(start.x, start.y);
|
|
1256
|
+
ctx.lineTo(end.x, end.y);
|
|
1257
|
+
ctx.stroke();
|
|
1258
|
+
}
|
|
1259
|
+
drawHighlight(ctx, bounds, style) {
|
|
1260
|
+
ctx.fillStyle = style.fillColor;
|
|
1261
|
+
ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
1262
|
+
}
|
|
1263
|
+
drawBlur(ctx, bounds, blurAmount) {
|
|
1264
|
+
const pixelSize = Math.max(blurAmount, 5);
|
|
1265
|
+
if (this.backgroundImage) {
|
|
1266
|
+
const tempCanvas = document.createElement("canvas");
|
|
1267
|
+
tempCanvas.width = bounds.width;
|
|
1268
|
+
tempCanvas.height = bounds.height;
|
|
1269
|
+
const tempCtx = tempCanvas.getContext("2d");
|
|
1270
|
+
tempCtx.drawImage(
|
|
1271
|
+
this.backgroundImage,
|
|
1272
|
+
bounds.x,
|
|
1273
|
+
bounds.y,
|
|
1274
|
+
bounds.width,
|
|
1275
|
+
bounds.height,
|
|
1276
|
+
0,
|
|
1277
|
+
0,
|
|
1278
|
+
bounds.width,
|
|
1279
|
+
bounds.height
|
|
1280
|
+
);
|
|
1281
|
+
const w = tempCanvas.width;
|
|
1282
|
+
const h = tempCanvas.height;
|
|
1283
|
+
tempCtx.imageSmoothingEnabled = false;
|
|
1284
|
+
tempCtx.drawImage(tempCanvas, 0, 0, w, h, 0, 0, w / pixelSize, h / pixelSize);
|
|
1285
|
+
tempCtx.drawImage(tempCanvas, 0, 0, w / pixelSize, h / pixelSize, 0, 0, w, h);
|
|
1286
|
+
ctx.drawImage(tempCanvas, bounds.x, bounds.y);
|
|
1287
|
+
}
|
|
1288
|
+
ctx.strokeStyle = "#999";
|
|
1289
|
+
ctx.lineWidth = 1;
|
|
1290
|
+
ctx.setLineDash([5, 5]);
|
|
1291
|
+
ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
1292
|
+
ctx.setLineDash([]);
|
|
1293
|
+
}
|
|
1294
|
+
drawText(ctx, position, text, style) {
|
|
1295
|
+
ctx.font = `${style.fontSize || 16}px ${style.fontFamily || "system-ui"}`;
|
|
1296
|
+
ctx.fillStyle = style.strokeColor;
|
|
1297
|
+
ctx.textBaseline = "top";
|
|
1298
|
+
const metrics = ctx.measureText(text);
|
|
1299
|
+
const padding = 4;
|
|
1300
|
+
ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
|
|
1301
|
+
ctx.fillRect(
|
|
1302
|
+
position.x - padding,
|
|
1303
|
+
position.y - padding,
|
|
1304
|
+
metrics.width + padding * 2,
|
|
1305
|
+
(style.fontSize || 16) + padding * 2
|
|
1306
|
+
);
|
|
1307
|
+
ctx.fillStyle = style.strokeColor;
|
|
1308
|
+
ctx.fillText(text, position.x, position.y);
|
|
1309
|
+
}
|
|
1310
|
+
drawFreehand(ctx, points) {
|
|
1311
|
+
if (points.length < 2) return;
|
|
1312
|
+
ctx.beginPath();
|
|
1313
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
1314
|
+
for (let i = 1; i < points.length; i++) {
|
|
1315
|
+
ctx.lineTo(points[i].x, points[i].y);
|
|
1316
|
+
}
|
|
1317
|
+
ctx.stroke();
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
|
|
213
1323
|
// src/widget.ts
|
|
1324
|
+
var widget_exports = {};
|
|
1325
|
+
__export(widget_exports, {
|
|
1326
|
+
mountWidget: () => mountWidget,
|
|
1327
|
+
openFeedbackModal: () => openFeedbackModal,
|
|
1328
|
+
unmountWidget: () => unmountWidget
|
|
1329
|
+
});
|
|
214
1330
|
function mountWidget(config2 = {}, onSubmit, opts) {
|
|
215
1331
|
if (container) return;
|
|
216
1332
|
onSubmitCallback = onSubmit;
|
|
1333
|
+
currentUser = opts?.user;
|
|
217
1334
|
const cfg = { ...DEFAULT_CONFIG, ...config2 };
|
|
1335
|
+
currentCfg = cfg;
|
|
218
1336
|
container = document.createElement("div");
|
|
219
1337
|
container.id = "checkflow-widget";
|
|
220
1338
|
injectStyles(cfg);
|
|
@@ -223,15 +1341,29 @@ function mountWidget(config2 = {}, onSubmit, opts) {
|
|
|
223
1341
|
const btn = container.querySelector("#cf-trigger");
|
|
224
1342
|
btn?.addEventListener("click", () => openModal(cfg, opts));
|
|
225
1343
|
}
|
|
1344
|
+
function openFeedbackModal(config2 = {}, onSubmit, opts) {
|
|
1345
|
+
onSubmitCallback = onSubmit;
|
|
1346
|
+
currentUser = opts?.user;
|
|
1347
|
+
const cfg = { ...DEFAULT_CONFIG, ...config2 };
|
|
1348
|
+
currentCfg = cfg;
|
|
1349
|
+
injectStyles(cfg);
|
|
1350
|
+
openModal(cfg, opts);
|
|
1351
|
+
}
|
|
1352
|
+
function getBackdropClass(cfg, expanded) {
|
|
1353
|
+
if (expanded) return "cf-backdrop--center";
|
|
1354
|
+
if (cfg.position === "center") return "cf-backdrop--center";
|
|
1355
|
+
return "cf-backdrop--corner cf-pos-" + (cfg.position || "bottom-right");
|
|
1356
|
+
}
|
|
226
1357
|
function openModal(cfg, opts) {
|
|
227
|
-
if (!container) return;
|
|
228
1358
|
const existing = document.getElementById("cf-modal-backdrop");
|
|
229
1359
|
if (existing) existing.remove();
|
|
230
1360
|
isExpanded = false;
|
|
231
1361
|
screenshotDataUrl = null;
|
|
1362
|
+
annotatedImageUrl = null;
|
|
232
1363
|
annotationsData = [];
|
|
233
1364
|
const backdrop = document.createElement("div");
|
|
234
1365
|
backdrop.id = "cf-modal-backdrop";
|
|
1366
|
+
backdrop.className = getBackdropClass(cfg, false);
|
|
235
1367
|
backdrop.innerHTML = getModalHTML(cfg, false);
|
|
236
1368
|
document.body.appendChild(backdrop);
|
|
237
1369
|
backdrop.addEventListener("click", (e) => {
|
|
@@ -244,6 +1376,7 @@ function closeModal() {
|
|
|
244
1376
|
if (backdrop) backdrop.remove();
|
|
245
1377
|
isExpanded = false;
|
|
246
1378
|
screenshotDataUrl = null;
|
|
1379
|
+
annotatedImageUrl = null;
|
|
247
1380
|
annotationsData = [];
|
|
248
1381
|
}
|
|
249
1382
|
function expandModal(cfg, opts) {
|
|
@@ -253,6 +1386,7 @@ function expandModal(cfg, opts) {
|
|
|
253
1386
|
const email = backdrop.querySelector("#cf-email")?.value || "";
|
|
254
1387
|
const desc = backdrop.querySelector("#cf-desc")?.value || "";
|
|
255
1388
|
isExpanded = true;
|
|
1389
|
+
backdrop.className = getBackdropClass(cfg, true);
|
|
256
1390
|
backdrop.innerHTML = getModalHTML(cfg, true);
|
|
257
1391
|
bindModalEvents(cfg, opts);
|
|
258
1392
|
const nameEl = backdrop.querySelector("#cf-name");
|
|
@@ -270,7 +1404,9 @@ function collapseModal(cfg, opts) {
|
|
|
270
1404
|
const desc = backdrop.querySelector("#cf-desc")?.value || "";
|
|
271
1405
|
isExpanded = false;
|
|
272
1406
|
screenshotDataUrl = null;
|
|
1407
|
+
annotatedImageUrl = null;
|
|
273
1408
|
annotationsData = [];
|
|
1409
|
+
backdrop.className = getBackdropClass(cfg, false);
|
|
274
1410
|
backdrop.innerHTML = getModalHTML(cfg, false);
|
|
275
1411
|
bindModalEvents(cfg, opts);
|
|
276
1412
|
const nameEl = backdrop.querySelector("#cf-name");
|
|
@@ -280,6 +1416,30 @@ function collapseModal(cfg, opts) {
|
|
|
280
1416
|
if (emailEl) emailEl.value = email;
|
|
281
1417
|
if (descEl) descEl.value = desc;
|
|
282
1418
|
}
|
|
1419
|
+
function openAnnotationEditor(cfg, opts) {
|
|
1420
|
+
if (!screenshotDataUrl) return;
|
|
1421
|
+
const backdrop = document.getElementById("cf-modal-backdrop");
|
|
1422
|
+
if (backdrop) backdrop.style.display = "none";
|
|
1423
|
+
const editor = new AnnotationEditor({
|
|
1424
|
+
tools: ["rectangle", "ellipse", "arrow", "highlight", "blur", "text", "freehand"],
|
|
1425
|
+
defaultStyle: { strokeColor: "#FF3B30", strokeWidth: 3, fillColor: "transparent", opacity: 1, fontSize: 16, fontFamily: "system-ui, -apple-system, sans-serif" },
|
|
1426
|
+
onSave: (annotations, imageData) => {
|
|
1427
|
+
annotationsData = annotations;
|
|
1428
|
+
annotatedImageUrl = imageData;
|
|
1429
|
+
if (backdrop) {
|
|
1430
|
+
backdrop.style.display = "flex";
|
|
1431
|
+
const img = backdrop.querySelector("#cf-screenshot-img");
|
|
1432
|
+
if (img) img.src = annotatedImageUrl;
|
|
1433
|
+
const annotBtn = backdrop.querySelector("#cf-annotate-btn");
|
|
1434
|
+
if (annotBtn) annotBtn.textContent = `Annoter (${annotationsData.length})`;
|
|
1435
|
+
}
|
|
1436
|
+
},
|
|
1437
|
+
onCancel: () => {
|
|
1438
|
+
if (backdrop) backdrop.style.display = "flex";
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
editor.open(screenshotDataUrl);
|
|
1442
|
+
}
|
|
283
1443
|
function bindModalEvents(cfg, opts) {
|
|
284
1444
|
const backdrop = document.getElementById("cf-modal-backdrop");
|
|
285
1445
|
if (!backdrop) return;
|
|
@@ -302,38 +1462,15 @@ function bindModalEvents(cfg, opts) {
|
|
|
302
1462
|
backdrop.querySelector("#cf-remove-screenshot")?.addEventListener("click", () => {
|
|
303
1463
|
collapseModal(cfg, opts);
|
|
304
1464
|
});
|
|
305
|
-
backdrop.querySelector("#cf-
|
|
306
|
-
|
|
307
|
-
backdrop.style.display = "none";
|
|
308
|
-
try {
|
|
309
|
-
const annotations = await opts.onHighlight();
|
|
310
|
-
if (annotations && annotations.length > 0) {
|
|
311
|
-
annotationsData = annotations;
|
|
312
|
-
}
|
|
313
|
-
} catch {
|
|
314
|
-
}
|
|
315
|
-
backdrop.style.display = "flex";
|
|
316
|
-
const hlBtn = backdrop.querySelector("#cf-highlight-btn");
|
|
317
|
-
if (hlBtn && annotationsData.length > 0) {
|
|
318
|
-
hlBtn.textContent = `Surligner (${annotationsData.length})`;
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
backdrop.querySelector("#cf-mask-btn")?.addEventListener("click", async () => {
|
|
322
|
-
if (!opts?.onHighlight) return;
|
|
323
|
-
backdrop.style.display = "none";
|
|
324
|
-
try {
|
|
325
|
-
const annotations = await opts.onHighlight();
|
|
326
|
-
if (annotations && annotations.length > 0) {
|
|
327
|
-
annotationsData = [...annotationsData, ...annotations];
|
|
328
|
-
}
|
|
329
|
-
} catch {
|
|
330
|
-
}
|
|
331
|
-
backdrop.style.display = "flex";
|
|
1465
|
+
backdrop.querySelector("#cf-annotate-btn")?.addEventListener("click", () => {
|
|
1466
|
+
openAnnotationEditor(cfg, opts);
|
|
332
1467
|
});
|
|
333
1468
|
backdrop.querySelector("#cf-submit")?.addEventListener("click", () => {
|
|
334
1469
|
const desc = backdrop.querySelector("#cf-desc")?.value;
|
|
335
|
-
const
|
|
336
|
-
const
|
|
1470
|
+
const nameEl = backdrop.querySelector("#cf-name");
|
|
1471
|
+
const emailEl = backdrop.querySelector("#cf-email");
|
|
1472
|
+
const name = nameEl?.value || currentUser?.name;
|
|
1473
|
+
const email = emailEl?.value || currentUser?.email;
|
|
337
1474
|
if (!desc?.trim()) {
|
|
338
1475
|
const descEl = backdrop.querySelector("#cf-desc");
|
|
339
1476
|
if (descEl) {
|
|
@@ -347,7 +1484,7 @@ function bindModalEvents(cfg, opts) {
|
|
|
347
1484
|
description: desc.trim(),
|
|
348
1485
|
type: "BUG",
|
|
349
1486
|
priority: "MEDIUM",
|
|
350
|
-
screenshot: screenshotDataUrl || void 0,
|
|
1487
|
+
screenshot: annotatedImageUrl || screenshotDataUrl || void 0,
|
|
351
1488
|
annotations: annotationsData.length > 0 ? annotationsData : void 0,
|
|
352
1489
|
reporter_name: name?.trim() || void 0,
|
|
353
1490
|
reporter_email: email?.trim() || void 0
|
|
@@ -375,33 +1512,93 @@ function showToast(msg) {
|
|
|
375
1512
|
setTimeout(() => toast.remove(), 3e3);
|
|
376
1513
|
}
|
|
377
1514
|
function injectStyles(cfg) {
|
|
1515
|
+
const existingStyle = document.getElementById("cf-widget-styles");
|
|
1516
|
+
if (existingStyle) existingStyle.remove();
|
|
1517
|
+
const btn = cfg.button || {};
|
|
1518
|
+
const sizePreset = SIZE_PRESETS[btn.size || "default"] || SIZE_PRESETS.default;
|
|
1519
|
+
const variantPreset = VARIANT_PRESETS[btn.variant || "default"] || VARIANT_PRESETS.default;
|
|
1520
|
+
const bgColor = btn.backgroundColor || cfg.color || variantPreset.bg;
|
|
1521
|
+
const textColor = btn.textColor || variantPreset.color;
|
|
1522
|
+
const borderRadius = btn.borderRadius || "8px";
|
|
1523
|
+
const padding = btn.padding || sizePreset.padding;
|
|
1524
|
+
const fontSize = btn.fontSize || sizePreset.fontSize;
|
|
1525
|
+
const fontWeight = btn.fontWeight || "500";
|
|
1526
|
+
const border = btn.border || variantPreset.border;
|
|
1527
|
+
const boxShadow = btn.boxShadow || "0 2px 8px rgba(0,0,0,0.15)";
|
|
1528
|
+
const primaryColor = cfg.color || "#1e3a5f";
|
|
378
1529
|
const style = document.createElement("style");
|
|
379
1530
|
style.id = "cf-widget-styles";
|
|
380
1531
|
style.textContent = `
|
|
381
1532
|
@keyframes cf-toast-in { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
|
|
382
1533
|
@keyframes cf-modal-in { from { opacity:0; transform:scale(.96) translateY(8px); } to { opacity:1; transform:scale(1) translateY(0); } }
|
|
383
1534
|
|
|
1535
|
+
/* User card styles */
|
|
1536
|
+
.cf-user-card {
|
|
1537
|
+
display:flex;
|
|
1538
|
+
align-items:center;
|
|
1539
|
+
gap:12px;
|
|
1540
|
+
padding:12px;
|
|
1541
|
+
background:#f8f9fa;
|
|
1542
|
+
border-radius:10px;
|
|
1543
|
+
margin-bottom:4px;
|
|
1544
|
+
}
|
|
1545
|
+
.cf-user-avatar {
|
|
1546
|
+
width:40px;
|
|
1547
|
+
height:40px;
|
|
1548
|
+
border-radius:50%;
|
|
1549
|
+
object-fit:cover;
|
|
1550
|
+
}
|
|
1551
|
+
.cf-user-avatar-placeholder {
|
|
1552
|
+
width:40px;
|
|
1553
|
+
height:40px;
|
|
1554
|
+
border-radius:50%;
|
|
1555
|
+
background:${primaryColor};
|
|
1556
|
+
color:#fff;
|
|
1557
|
+
display:flex;
|
|
1558
|
+
align-items:center;
|
|
1559
|
+
justify-content:center;
|
|
1560
|
+
font-size:16px;
|
|
1561
|
+
font-weight:600;
|
|
1562
|
+
}
|
|
1563
|
+
.cf-user-info {
|
|
1564
|
+
flex:1;
|
|
1565
|
+
min-width:0;
|
|
1566
|
+
}
|
|
1567
|
+
.cf-user-name {
|
|
1568
|
+
font-size:14px;
|
|
1569
|
+
font-weight:600;
|
|
1570
|
+
color:#1a1a2e;
|
|
1571
|
+
}
|
|
1572
|
+
.cf-user-email {
|
|
1573
|
+
font-size:12px;
|
|
1574
|
+
color:#666;
|
|
1575
|
+
overflow:hidden;
|
|
1576
|
+
text-overflow:ellipsis;
|
|
1577
|
+
white-space:nowrap;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
/* Trigger button - now inline, not fixed position */
|
|
384
1581
|
#cf-trigger {
|
|
385
1582
|
position:fixed;
|
|
386
|
-
${getPositionCSS(cfg.position)}
|
|
1583
|
+
${getPositionCSS(cfg.position || "bottom-right")}
|
|
387
1584
|
z-index:100000;
|
|
388
|
-
background:${
|
|
389
|
-
color
|
|
390
|
-
border
|
|
391
|
-
border-radius
|
|
392
|
-
padding
|
|
393
|
-
font-size
|
|
394
|
-
font-weight
|
|
1585
|
+
background:${bgColor};
|
|
1586
|
+
color:${textColor};
|
|
1587
|
+
border:${border};
|
|
1588
|
+
border-radius:${borderRadius};
|
|
1589
|
+
padding:${padding};
|
|
1590
|
+
font-size:${fontSize};
|
|
1591
|
+
font-weight:${fontWeight};
|
|
395
1592
|
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
|
|
396
1593
|
cursor:pointer;
|
|
397
|
-
box-shadow
|
|
398
|
-
display:flex;
|
|
1594
|
+
box-shadow:${boxShadow};
|
|
1595
|
+
display:inline-flex;
|
|
399
1596
|
align-items:center;
|
|
400
1597
|
gap:8px;
|
|
401
|
-
transition:transform .15s,box-shadow .15s;
|
|
1598
|
+
transition:transform .15s,box-shadow .15s,opacity .15s;
|
|
402
1599
|
}
|
|
403
|
-
#cf-trigger:hover {
|
|
404
|
-
#cf-trigger svg { width:
|
|
1600
|
+
#cf-trigger:hover { opacity:0.9; }
|
|
1601
|
+
#cf-trigger svg { width:16px; height:16px; flex-shrink:0; }
|
|
405
1602
|
|
|
406
1603
|
#cf-modal-backdrop {
|
|
407
1604
|
position:fixed;
|
|
@@ -409,9 +1606,30 @@ function injectStyles(cfg) {
|
|
|
409
1606
|
background:rgba(0,0,0,.45);
|
|
410
1607
|
z-index:100001;
|
|
411
1608
|
display:flex;
|
|
1609
|
+
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
|
|
1610
|
+
}
|
|
1611
|
+
#cf-modal-backdrop.cf-backdrop--center {
|
|
412
1612
|
align-items:center;
|
|
413
1613
|
justify-content:center;
|
|
414
|
-
|
|
1614
|
+
}
|
|
1615
|
+
#cf-modal-backdrop.cf-backdrop--corner {
|
|
1616
|
+
padding:20px;
|
|
1617
|
+
}
|
|
1618
|
+
#cf-modal-backdrop.cf-pos-bottom-right {
|
|
1619
|
+
align-items:flex-end;
|
|
1620
|
+
justify-content:flex-end;
|
|
1621
|
+
}
|
|
1622
|
+
#cf-modal-backdrop.cf-pos-bottom-left {
|
|
1623
|
+
align-items:flex-end;
|
|
1624
|
+
justify-content:flex-start;
|
|
1625
|
+
}
|
|
1626
|
+
#cf-modal-backdrop.cf-pos-top-right {
|
|
1627
|
+
align-items:flex-start;
|
|
1628
|
+
justify-content:flex-end;
|
|
1629
|
+
}
|
|
1630
|
+
#cf-modal-backdrop.cf-pos-top-left {
|
|
1631
|
+
align-items:flex-start;
|
|
1632
|
+
justify-content:flex-start;
|
|
415
1633
|
}
|
|
416
1634
|
|
|
417
1635
|
.cf-modal {
|
|
@@ -481,7 +1699,7 @@ function injectStyles(cfg) {
|
|
|
481
1699
|
transition:all .15s;
|
|
482
1700
|
}
|
|
483
1701
|
.cf-tool-highlight {
|
|
484
|
-
background:${
|
|
1702
|
+
background:${primaryColor};
|
|
485
1703
|
color:#fff;
|
|
486
1704
|
}
|
|
487
1705
|
.cf-tool-highlight:hover { opacity:.9; }
|
|
@@ -529,8 +1747,8 @@ function injectStyles(cfg) {
|
|
|
529
1747
|
}
|
|
530
1748
|
.cf-field input::placeholder, .cf-field textarea::placeholder { color:#bbb; }
|
|
531
1749
|
.cf-field input:focus, .cf-field textarea:focus {
|
|
532
|
-
border-color:${
|
|
533
|
-
box-shadow:0 0 0 3px ${
|
|
1750
|
+
border-color:${primaryColor};
|
|
1751
|
+
box-shadow:0 0 0 3px ${primaryColor}18;
|
|
534
1752
|
}
|
|
535
1753
|
.cf-field textarea { resize:vertical; min-height:100px; }
|
|
536
1754
|
|
|
@@ -569,7 +1787,7 @@ function injectStyles(cfg) {
|
|
|
569
1787
|
padding:13px;
|
|
570
1788
|
border:none;
|
|
571
1789
|
border-radius:10px;
|
|
572
|
-
background:${
|
|
1790
|
+
background:${primaryColor};
|
|
573
1791
|
color:#fff;
|
|
574
1792
|
font-size:15px;
|
|
575
1793
|
font-weight:600;
|
|
@@ -577,7 +1795,7 @@ function injectStyles(cfg) {
|
|
|
577
1795
|
font-family:inherit;
|
|
578
1796
|
transition:all .15s;
|
|
579
1797
|
}
|
|
580
|
-
.cf-submit-btn:hover { opacity:.92; transform:translateY(-1px); box-shadow:0 4px 12px ${
|
|
1798
|
+
.cf-submit-btn:hover { opacity:.92; transform:translateY(-1px); box-shadow:0 4px 12px ${primaryColor}40; }
|
|
581
1799
|
.cf-submit-btn:disabled { opacity:.5; cursor:not-allowed; transform:none; }
|
|
582
1800
|
|
|
583
1801
|
.cf-cancel-btn {
|
|
@@ -608,7 +1826,7 @@ function injectStyles(cfg) {
|
|
|
608
1826
|
text-decoration:none;
|
|
609
1827
|
font-weight:500;
|
|
610
1828
|
}
|
|
611
|
-
.cf-footer a:hover { color:${
|
|
1829
|
+
.cf-footer a:hover { color:${primaryColor}; }
|
|
612
1830
|
`;
|
|
613
1831
|
document.head.appendChild(style);
|
|
614
1832
|
}
|
|
@@ -620,15 +1838,22 @@ function getPositionCSS(pos) {
|
|
|
620
1838
|
return "top:20px;right:20px;";
|
|
621
1839
|
case "top-left":
|
|
622
1840
|
return "top:20px;left:20px;";
|
|
1841
|
+
case "center":
|
|
1842
|
+
return "bottom:20px;left:50%;transform:translateX(-50%);";
|
|
623
1843
|
default:
|
|
624
1844
|
return "bottom:20px;right:20px;";
|
|
625
1845
|
}
|
|
626
1846
|
}
|
|
627
1847
|
function getTriggerHTML(cfg) {
|
|
1848
|
+
const btn = cfg.button || {};
|
|
1849
|
+
const text = btn.text || cfg.text || "Report Bug";
|
|
1850
|
+
const showIcon = btn.showIcon !== false;
|
|
1851
|
+
const customClass = btn.className || "";
|
|
1852
|
+
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>` : "";
|
|
628
1853
|
return `
|
|
629
|
-
<button id="cf-trigger">
|
|
630
|
-
|
|
631
|
-
${
|
|
1854
|
+
<button id="cf-trigger" class="${customClass}">
|
|
1855
|
+
${icon}
|
|
1856
|
+
${text}
|
|
632
1857
|
</button>
|
|
633
1858
|
`;
|
|
634
1859
|
}
|
|
@@ -637,21 +1862,37 @@ function getCheckflowLogo() {
|
|
|
637
1862
|
}
|
|
638
1863
|
function getModalHTML(cfg, expanded) {
|
|
639
1864
|
const modalClass = expanded ? "cf-modal cf-modal--expanded" : "cf-modal cf-modal--compact";
|
|
640
|
-
const
|
|
1865
|
+
const hasUser = currentUser && (currentUser.name || currentUser.email);
|
|
1866
|
+
const userInfoCard = hasUser ? `
|
|
1867
|
+
<div class="cf-user-card">
|
|
1868
|
+
${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>`}
|
|
1869
|
+
<div class="cf-user-info">
|
|
1870
|
+
${currentUser?.name ? `<div class="cf-user-name">${currentUser.name}</div>` : ""}
|
|
1871
|
+
${currentUser?.email ? `<div class="cf-user-email">${currentUser.email}</div>` : ""}
|
|
1872
|
+
</div>
|
|
1873
|
+
</div>
|
|
1874
|
+
` : "";
|
|
1875
|
+
const nameEmailFields = hasUser ? "" : `
|
|
641
1876
|
<div class="cf-field">
|
|
642
|
-
<label>Nom <span>(
|
|
1877
|
+
<label>Nom <span>(optionnel)</span></label>
|
|
643
1878
|
<input id="cf-name" type="text" placeholder="Votre nom" />
|
|
644
1879
|
</div>
|
|
645
1880
|
<div class="cf-field">
|
|
646
|
-
<label>Email <span>(
|
|
1881
|
+
<label>Email <span>(optionnel)</span></label>
|
|
647
1882
|
<input id="cf-email" type="email" placeholder="votre.email@exemple.com" />
|
|
648
1883
|
</div>
|
|
1884
|
+
`;
|
|
1885
|
+
const formFields = `
|
|
1886
|
+
${userInfoCard}
|
|
1887
|
+
${nameEmailFields}
|
|
649
1888
|
<div class="cf-field">
|
|
650
1889
|
<label>Description <span>(obligatoire)</span></label>
|
|
651
1890
|
<textarea id="cf-desc" placeholder="Quel est le probl\xE8me ? Que vous attendiez-vous \xE0 voir ?"></textarea>
|
|
652
1891
|
</div>
|
|
653
1892
|
`;
|
|
654
1893
|
if (expanded && screenshotDataUrl) {
|
|
1894
|
+
const displayImg = annotatedImageUrl || screenshotDataUrl;
|
|
1895
|
+
const annotLabel = annotationsData.length > 0 ? `Annoter (${annotationsData.length})` : "Annoter";
|
|
655
1896
|
return `
|
|
656
1897
|
<div class="${modalClass}">
|
|
657
1898
|
<div class="cf-modal-header">
|
|
@@ -660,10 +1901,9 @@ function getModalHTML(cfg, expanded) {
|
|
|
660
1901
|
</div>
|
|
661
1902
|
<div class="cf-modal-body">
|
|
662
1903
|
<div class="cf-screenshot-panel">
|
|
663
|
-
<img src="${
|
|
1904
|
+
<img id="cf-screenshot-img" src="${displayImg}" alt="Capture d'\xE9cran" />
|
|
664
1905
|
<div class="cf-screenshot-tools">
|
|
665
|
-
<button class="cf-tool-highlight" id="cf-
|
|
666
|
-
<button class="cf-tool-mask" id="cf-mask-btn">Masquer</button>
|
|
1906
|
+
<button class="cf-tool-highlight" id="cf-annotate-btn">${annotLabel}</button>
|
|
667
1907
|
</div>
|
|
668
1908
|
</div>
|
|
669
1909
|
<div class="cf-form-panel">
|
|
@@ -693,21 +1933,53 @@ function getModalHTML(cfg, expanded) {
|
|
|
693
1933
|
</div>
|
|
694
1934
|
`;
|
|
695
1935
|
}
|
|
696
|
-
var DEFAULT_CONFIG, container, onSubmitCallback, screenshotDataUrl, annotationsData, isExpanded;
|
|
1936
|
+
var DEFAULT_BUTTON_CONFIG, SIZE_PRESETS, VARIANT_PRESETS, DEFAULT_CONFIG, container, onSubmitCallback, screenshotDataUrl, annotatedImageUrl, annotationsData, isExpanded, currentCfg, currentUser;
|
|
697
1937
|
var init_widget = __esm({
|
|
698
1938
|
"src/widget.ts"() {
|
|
699
1939
|
"use strict";
|
|
1940
|
+
init_editor();
|
|
1941
|
+
DEFAULT_BUTTON_CONFIG = {
|
|
1942
|
+
text: "Report Bug",
|
|
1943
|
+
backgroundColor: "#1e3a5f",
|
|
1944
|
+
textColor: "#ffffff",
|
|
1945
|
+
borderRadius: "8px",
|
|
1946
|
+
padding: "10px 18px",
|
|
1947
|
+
fontSize: "14px",
|
|
1948
|
+
fontWeight: "500",
|
|
1949
|
+
border: "none",
|
|
1950
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
|
|
1951
|
+
className: "",
|
|
1952
|
+
showIcon: true,
|
|
1953
|
+
size: "default",
|
|
1954
|
+
variant: "default"
|
|
1955
|
+
};
|
|
1956
|
+
SIZE_PRESETS = {
|
|
1957
|
+
xs: { padding: "6px 10px", fontSize: "12px" },
|
|
1958
|
+
sm: { padding: "8px 14px", fontSize: "13px" },
|
|
1959
|
+
default: { padding: "10px 18px", fontSize: "14px" },
|
|
1960
|
+
lg: { padding: "12px 24px", fontSize: "15px" }
|
|
1961
|
+
};
|
|
1962
|
+
VARIANT_PRESETS = {
|
|
1963
|
+
default: { bg: "#1e3a5f", color: "#ffffff", border: "none" },
|
|
1964
|
+
outline: { bg: "transparent", color: "#1e3a5f", border: "1px solid #1e3a5f" },
|
|
1965
|
+
ghost: { bg: "transparent", color: "#1e3a5f", border: "none" },
|
|
1966
|
+
link: { bg: "transparent", color: "#1e3a5f", border: "none" }
|
|
1967
|
+
};
|
|
700
1968
|
DEFAULT_CONFIG = {
|
|
701
1969
|
position: "bottom-right",
|
|
702
1970
|
color: "#1e3a5f",
|
|
703
1971
|
text: "Report Bug",
|
|
704
|
-
showOnInit: true
|
|
1972
|
+
showOnInit: true,
|
|
1973
|
+
button: DEFAULT_BUTTON_CONFIG
|
|
705
1974
|
};
|
|
706
1975
|
container = null;
|
|
707
1976
|
onSubmitCallback = null;
|
|
708
1977
|
screenshotDataUrl = null;
|
|
1978
|
+
annotatedImageUrl = null;
|
|
709
1979
|
annotationsData = [];
|
|
710
1980
|
isExpanded = false;
|
|
1981
|
+
currentCfg = { ...DEFAULT_CONFIG };
|
|
1982
|
+
currentUser = void 0;
|
|
711
1983
|
}
|
|
712
1984
|
});
|
|
713
1985
|
|
|
@@ -777,154 +2049,18 @@ var init_screenshot = __esm({
|
|
|
777
2049
|
}
|
|
778
2050
|
});
|
|
779
2051
|
|
|
780
|
-
// src/
|
|
781
|
-
var
|
|
782
|
-
__export(
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (current.id) {
|
|
793
|
-
parts.unshift(`#${current.id}`);
|
|
794
|
-
break;
|
|
795
|
-
}
|
|
796
|
-
if (current.className && typeof current.className === "string") {
|
|
797
|
-
const cls = current.className.trim().split(/\s+/).slice(0, 2).join(".");
|
|
798
|
-
if (cls) sel += `.${cls}`;
|
|
799
|
-
}
|
|
800
|
-
const parent = current.parentElement;
|
|
801
|
-
if (parent) {
|
|
802
|
-
const siblings = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
|
|
803
|
-
if (siblings.length > 1) sel += `:nth-child(${Array.from(parent.children).indexOf(current) + 1})`;
|
|
804
|
-
}
|
|
805
|
-
parts.unshift(sel);
|
|
806
|
-
current = current.parentElement;
|
|
807
|
-
}
|
|
808
|
-
return parts.join(" > ");
|
|
809
|
-
}
|
|
810
|
-
function createOverlay() {
|
|
811
|
-
overlay = document.createElement("div");
|
|
812
|
-
overlay.id = "cf-highlight-overlay";
|
|
813
|
-
overlay.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;z-index:99999;cursor:crosshair;";
|
|
814
|
-
const toolbar = document.createElement("div");
|
|
815
|
-
toolbar.style.cssText = "position:fixed;top:12px;left:50%;transform:translateX(-50%);z-index:100002;background:#1e3a5f;color:#fff;padding:8px 16px;border-radius:10px;font-family:system-ui;font-size:13px;display:flex;align-items:center;gap:12px;box-shadow:0 4px 20px rgba(0,0,0,.25);";
|
|
816
|
-
toolbar.innerHTML = `
|
|
817
|
-
<span>Click elements to highlight them</span>
|
|
818
|
-
<button id="cf-hl-done" style="background:#10b981;color:#fff;border:none;border-radius:6px;padding:6px 14px;font-size:12px;cursor:pointer;font-weight:500;">Done (${highlights.length})</button>
|
|
819
|
-
<button id="cf-hl-cancel" style="background:transparent;color:#fff;border:1px solid rgba(255,255,255,.3);border-radius:6px;padding:6px 14px;font-size:12px;cursor:pointer;">Cancel</button>
|
|
820
|
-
`;
|
|
821
|
-
overlay.appendChild(toolbar);
|
|
822
|
-
document.body.appendChild(overlay);
|
|
823
|
-
overlay.addEventListener("click", handleClick);
|
|
824
|
-
overlay.addEventListener("mousemove", handleHover);
|
|
825
|
-
document.getElementById("cf-hl-done")?.addEventListener("click", finishHighlighting);
|
|
826
|
-
document.getElementById("cf-hl-cancel")?.addEventListener("click", cancelHighlighting);
|
|
827
|
-
}
|
|
828
|
-
function handleHover(e) {
|
|
829
|
-
if (!hoverBox) {
|
|
830
|
-
hoverBox = document.createElement("div");
|
|
831
|
-
hoverBox.style.cssText = "position:fixed;border:2px solid #0c66e4;background:rgba(12,102,228,0.08);pointer-events:none;z-index:100001;border-radius:3px;transition:all 0.05s;";
|
|
832
|
-
document.body.appendChild(hoverBox);
|
|
833
|
-
}
|
|
834
|
-
const target = document.elementFromPoint(e.clientX, e.clientY);
|
|
835
|
-
if (target && target !== overlay && !overlay?.contains(target)) {
|
|
836
|
-
const rect = target.getBoundingClientRect();
|
|
837
|
-
hoverBox.style.left = rect.left + "px";
|
|
838
|
-
hoverBox.style.top = rect.top + "px";
|
|
839
|
-
hoverBox.style.width = rect.width + "px";
|
|
840
|
-
hoverBox.style.height = rect.height + "px";
|
|
841
|
-
hoverBox.style.display = "block";
|
|
842
|
-
} else {
|
|
843
|
-
hoverBox.style.display = "none";
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
function handleClick(e) {
|
|
847
|
-
e.preventDefault();
|
|
848
|
-
e.stopPropagation();
|
|
849
|
-
const target = document.elementFromPoint(e.clientX, e.clientY);
|
|
850
|
-
if (!target || target === overlay || overlay?.contains(target)) return;
|
|
851
|
-
const rect = target.getBoundingClientRect();
|
|
852
|
-
highlights.push({ el: target, rect });
|
|
853
|
-
const marker = document.createElement("div");
|
|
854
|
-
marker.className = "cf-hl-marker";
|
|
855
|
-
marker.style.cssText = `position:fixed;left:${rect.left}px;top:${rect.top}px;width:${rect.width}px;height:${rect.height}px;border:2px solid #ae2a19;background:rgba(174,42,25,0.12);z-index:100001;pointer-events:none;border-radius:3px;`;
|
|
856
|
-
const badge = document.createElement("div");
|
|
857
|
-
badge.style.cssText = "position:absolute;top:-10px;right:-10px;background:#ae2a19;color:#fff;width:20px;height:20px;border-radius:50%;font-size:11px;display:flex;align-items:center;justify-content:center;font-family:system-ui;font-weight:600;";
|
|
858
|
-
badge.textContent = String(highlights.length);
|
|
859
|
-
marker.appendChild(badge);
|
|
860
|
-
overlay?.appendChild(marker);
|
|
861
|
-
const doneBtn = document.getElementById("cf-hl-done");
|
|
862
|
-
if (doneBtn) doneBtn.textContent = `Done (${highlights.length})`;
|
|
863
|
-
}
|
|
864
|
-
function finishHighlighting() {
|
|
865
|
-
const annotations = highlights.map((h) => ({
|
|
866
|
-
selector: getSelector(h.el),
|
|
867
|
-
tagName: h.el.tagName.toLowerCase(),
|
|
868
|
-
text: h.el.textContent?.slice(0, 100) || void 0,
|
|
869
|
-
rect: { x: Math.round(h.rect.x), y: Math.round(h.rect.y), width: Math.round(h.rect.width), height: Math.round(h.rect.height) },
|
|
870
|
-
note: h.note
|
|
871
|
-
}));
|
|
872
|
-
cleanup();
|
|
873
|
-
onDoneCallback?.(annotations);
|
|
874
|
-
}
|
|
875
|
-
function cancelHighlighting() {
|
|
876
|
-
cleanup();
|
|
877
|
-
onDoneCallback?.([]);
|
|
878
|
-
}
|
|
879
|
-
function cleanup() {
|
|
880
|
-
if (hoverBox) {
|
|
881
|
-
hoverBox.remove();
|
|
882
|
-
hoverBox = null;
|
|
883
|
-
}
|
|
884
|
-
if (overlay) {
|
|
885
|
-
overlay.remove();
|
|
886
|
-
overlay = null;
|
|
887
|
-
}
|
|
888
|
-
highlights = [];
|
|
889
|
-
isActive = false;
|
|
890
|
-
}
|
|
891
|
-
function startHighlighting() {
|
|
892
|
-
return new Promise((resolve) => {
|
|
893
|
-
if (isActive) {
|
|
894
|
-
resolve([]);
|
|
895
|
-
return;
|
|
896
|
-
}
|
|
897
|
-
isActive = true;
|
|
898
|
-
highlights = [];
|
|
899
|
-
onDoneCallback = resolve;
|
|
900
|
-
createOverlay();
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
function isHighlighting() {
|
|
904
|
-
return isActive;
|
|
905
|
-
}
|
|
906
|
-
var overlay, highlights, isActive, onDoneCallback, hoverBox;
|
|
907
|
-
var init_highlighter = __esm({
|
|
908
|
-
"src/highlighter.ts"() {
|
|
909
|
-
"use strict";
|
|
910
|
-
overlay = null;
|
|
911
|
-
highlights = [];
|
|
912
|
-
isActive = false;
|
|
913
|
-
onDoneCallback = null;
|
|
914
|
-
hoverBox = null;
|
|
915
|
-
}
|
|
916
|
-
});
|
|
917
|
-
|
|
918
|
-
// src/index.ts
|
|
919
|
-
var index_exports = {};
|
|
920
|
-
__export(index_exports, {
|
|
921
|
-
captureScreenshot: () => captureScreenshot,
|
|
922
|
-
destroy: () => destroy,
|
|
923
|
-
hideWidget: () => hideWidget,
|
|
924
|
-
init: () => init,
|
|
925
|
-
sendFeedback: () => sendFeedback,
|
|
926
|
-
showWidget: () => showWidget,
|
|
927
|
-
startHighlighting: () => startHighlighting
|
|
2052
|
+
// src/index.ts
|
|
2053
|
+
var index_exports = {};
|
|
2054
|
+
__export(index_exports, {
|
|
2055
|
+
captureScreenshot: () => captureScreenshot,
|
|
2056
|
+
clearUser: () => clearUser,
|
|
2057
|
+
destroy: () => destroy,
|
|
2058
|
+
hideWidget: () => hideWidget,
|
|
2059
|
+
init: () => init,
|
|
2060
|
+
openFeedbackModal: () => openFeedbackModal2,
|
|
2061
|
+
sendFeedback: () => sendFeedback,
|
|
2062
|
+
setUser: () => setUser,
|
|
2063
|
+
showWidget: () => showWidget
|
|
928
2064
|
});
|
|
929
2065
|
function init(cfg) {
|
|
930
2066
|
config = {
|
|
@@ -953,7 +2089,7 @@ function init(cfg) {
|
|
|
953
2089
|
},
|
|
954
2090
|
{
|
|
955
2091
|
onScreenshot: () => captureScreenshot(),
|
|
956
|
-
|
|
2092
|
+
user: config.user
|
|
957
2093
|
}
|
|
958
2094
|
);
|
|
959
2095
|
}
|
|
@@ -983,12 +2119,22 @@ async function sendFeedback(data) {
|
|
|
983
2119
|
if (data.annotations && data.annotations.length > 0) {
|
|
984
2120
|
payload.annotations = data.annotations;
|
|
985
2121
|
}
|
|
986
|
-
if (
|
|
2122
|
+
if (config.user?.name) {
|
|
2123
|
+
payload.reporter_name = config.user.name;
|
|
2124
|
+
} else if (data.reporter_name) {
|
|
987
2125
|
payload.reporter_name = data.reporter_name;
|
|
988
2126
|
}
|
|
989
|
-
if (
|
|
2127
|
+
if (config.user?.email) {
|
|
2128
|
+
payload.reporter_email = config.user.email;
|
|
2129
|
+
} else if (data.reporter_email) {
|
|
990
2130
|
payload.reporter_email = data.reporter_email;
|
|
991
2131
|
}
|
|
2132
|
+
if (config.user?.id) {
|
|
2133
|
+
payload.user_id = config.user.id;
|
|
2134
|
+
}
|
|
2135
|
+
if (config.user?.avatar) {
|
|
2136
|
+
payload.user_avatar = config.user.avatar;
|
|
2137
|
+
}
|
|
992
2138
|
try {
|
|
993
2139
|
const res = await fetch(`${config.endpoint}/sdk/feedback`, {
|
|
994
2140
|
method: "POST",
|
|
@@ -1025,10 +2171,44 @@ function showWidget() {
|
|
|
1025
2171
|
},
|
|
1026
2172
|
{
|
|
1027
2173
|
onScreenshot: () => captureScreenshot(),
|
|
1028
|
-
|
|
2174
|
+
user: config.user
|
|
1029
2175
|
}
|
|
1030
2176
|
);
|
|
1031
2177
|
}
|
|
2178
|
+
function setUser(user) {
|
|
2179
|
+
if (config) {
|
|
2180
|
+
config.user = user;
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
function clearUser() {
|
|
2184
|
+
if (config) {
|
|
2185
|
+
config.user = void 0;
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
function openFeedbackModal2() {
|
|
2189
|
+
if (!config) return;
|
|
2190
|
+
Promise.resolve().then(() => (init_widget(), widget_exports)).then(({ openFeedbackModal: openModal2 }) => {
|
|
2191
|
+
openModal2(
|
|
2192
|
+
config.widget,
|
|
2193
|
+
(data) => {
|
|
2194
|
+
sendFeedback({
|
|
2195
|
+
title: data.title,
|
|
2196
|
+
description: data.description,
|
|
2197
|
+
type: data.type,
|
|
2198
|
+
priority: data.priority,
|
|
2199
|
+
screenshot_data: data.screenshot,
|
|
2200
|
+
annotations: data.annotations,
|
|
2201
|
+
reporter_name: data.reporter_name,
|
|
2202
|
+
reporter_email: data.reporter_email
|
|
2203
|
+
});
|
|
2204
|
+
},
|
|
2205
|
+
{
|
|
2206
|
+
onScreenshot: () => captureScreenshot(),
|
|
2207
|
+
user: config.user
|
|
2208
|
+
}
|
|
2209
|
+
);
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
1032
2212
|
function hideWidget() {
|
|
1033
2213
|
unmountWidget();
|
|
1034
2214
|
}
|
|
@@ -1048,15 +2228,151 @@ var init_index = __esm({
|
|
|
1048
2228
|
init_network_interceptor();
|
|
1049
2229
|
init_widget();
|
|
1050
2230
|
init_screenshot();
|
|
1051
|
-
init_highlighter();
|
|
1052
2231
|
init_screenshot();
|
|
1053
|
-
init_highlighter();
|
|
1054
2232
|
SDK_VERSION = "1.1.0";
|
|
1055
2233
|
DEFAULT_ENDPOINT = "https://api.checkflow.space/api/v1";
|
|
1056
2234
|
config = null;
|
|
1057
2235
|
}
|
|
1058
2236
|
});
|
|
1059
2237
|
|
|
2238
|
+
// src/highlighter.ts
|
|
2239
|
+
var highlighter_exports = {};
|
|
2240
|
+
__export(highlighter_exports, {
|
|
2241
|
+
isHighlighting: () => isHighlighting,
|
|
2242
|
+
startHighlighting: () => startHighlighting
|
|
2243
|
+
});
|
|
2244
|
+
function getSelector(el) {
|
|
2245
|
+
if (el.id) return `#${el.id}`;
|
|
2246
|
+
const parts = [];
|
|
2247
|
+
let current = el;
|
|
2248
|
+
while (current && current !== document.body) {
|
|
2249
|
+
let sel = current.tagName.toLowerCase();
|
|
2250
|
+
if (current.id) {
|
|
2251
|
+
parts.unshift(`#${current.id}`);
|
|
2252
|
+
break;
|
|
2253
|
+
}
|
|
2254
|
+
if (current.className && typeof current.className === "string") {
|
|
2255
|
+
const cls = current.className.trim().split(/\s+/).slice(0, 2).join(".");
|
|
2256
|
+
if (cls) sel += `.${cls}`;
|
|
2257
|
+
}
|
|
2258
|
+
const parent = current.parentElement;
|
|
2259
|
+
if (parent) {
|
|
2260
|
+
const siblings = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
|
|
2261
|
+
if (siblings.length > 1) sel += `:nth-child(${Array.from(parent.children).indexOf(current) + 1})`;
|
|
2262
|
+
}
|
|
2263
|
+
parts.unshift(sel);
|
|
2264
|
+
current = current.parentElement;
|
|
2265
|
+
}
|
|
2266
|
+
return parts.join(" > ");
|
|
2267
|
+
}
|
|
2268
|
+
function createOverlay() {
|
|
2269
|
+
overlay = document.createElement("div");
|
|
2270
|
+
overlay.id = "cf-highlight-overlay";
|
|
2271
|
+
overlay.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;z-index:99999;cursor:crosshair;";
|
|
2272
|
+
const toolbar = document.createElement("div");
|
|
2273
|
+
toolbar.style.cssText = "position:fixed;top:12px;left:50%;transform:translateX(-50%);z-index:100002;background:#1e3a5f;color:#fff;padding:8px 16px;border-radius:10px;font-family:system-ui;font-size:13px;display:flex;align-items:center;gap:12px;box-shadow:0 4px 20px rgba(0,0,0,.25);";
|
|
2274
|
+
toolbar.innerHTML = `
|
|
2275
|
+
<span>Click elements to highlight them</span>
|
|
2276
|
+
<button id="cf-hl-done" style="background:#10b981;color:#fff;border:none;border-radius:6px;padding:6px 14px;font-size:12px;cursor:pointer;font-weight:500;">Done (${highlights.length})</button>
|
|
2277
|
+
<button id="cf-hl-cancel" style="background:transparent;color:#fff;border:1px solid rgba(255,255,255,.3);border-radius:6px;padding:6px 14px;font-size:12px;cursor:pointer;">Cancel</button>
|
|
2278
|
+
`;
|
|
2279
|
+
overlay.appendChild(toolbar);
|
|
2280
|
+
document.body.appendChild(overlay);
|
|
2281
|
+
overlay.addEventListener("click", handleClick);
|
|
2282
|
+
overlay.addEventListener("mousemove", handleHover);
|
|
2283
|
+
document.getElementById("cf-hl-done")?.addEventListener("click", finishHighlighting);
|
|
2284
|
+
document.getElementById("cf-hl-cancel")?.addEventListener("click", cancelHighlighting);
|
|
2285
|
+
}
|
|
2286
|
+
function handleHover(e) {
|
|
2287
|
+
if (!hoverBox) {
|
|
2288
|
+
hoverBox = document.createElement("div");
|
|
2289
|
+
hoverBox.style.cssText = "position:fixed;border:2px solid #0c66e4;background:rgba(12,102,228,0.08);pointer-events:none;z-index:100001;border-radius:3px;transition:all 0.05s;";
|
|
2290
|
+
document.body.appendChild(hoverBox);
|
|
2291
|
+
}
|
|
2292
|
+
const target = document.elementFromPoint(e.clientX, e.clientY);
|
|
2293
|
+
if (target && target !== overlay && !overlay?.contains(target)) {
|
|
2294
|
+
const rect = target.getBoundingClientRect();
|
|
2295
|
+
hoverBox.style.left = rect.left + "px";
|
|
2296
|
+
hoverBox.style.top = rect.top + "px";
|
|
2297
|
+
hoverBox.style.width = rect.width + "px";
|
|
2298
|
+
hoverBox.style.height = rect.height + "px";
|
|
2299
|
+
hoverBox.style.display = "block";
|
|
2300
|
+
} else {
|
|
2301
|
+
hoverBox.style.display = "none";
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
function handleClick(e) {
|
|
2305
|
+
e.preventDefault();
|
|
2306
|
+
e.stopPropagation();
|
|
2307
|
+
const target = document.elementFromPoint(e.clientX, e.clientY);
|
|
2308
|
+
if (!target || target === overlay || overlay?.contains(target)) return;
|
|
2309
|
+
const rect = target.getBoundingClientRect();
|
|
2310
|
+
highlights.push({ el: target, rect });
|
|
2311
|
+
const marker = document.createElement("div");
|
|
2312
|
+
marker.className = "cf-hl-marker";
|
|
2313
|
+
marker.style.cssText = `position:fixed;left:${rect.left}px;top:${rect.top}px;width:${rect.width}px;height:${rect.height}px;border:2px solid #ae2a19;background:rgba(174,42,25,0.12);z-index:100001;pointer-events:none;border-radius:3px;`;
|
|
2314
|
+
const badge = document.createElement("div");
|
|
2315
|
+
badge.style.cssText = "position:absolute;top:-10px;right:-10px;background:#ae2a19;color:#fff;width:20px;height:20px;border-radius:50%;font-size:11px;display:flex;align-items:center;justify-content:center;font-family:system-ui;font-weight:600;";
|
|
2316
|
+
badge.textContent = String(highlights.length);
|
|
2317
|
+
marker.appendChild(badge);
|
|
2318
|
+
overlay?.appendChild(marker);
|
|
2319
|
+
const doneBtn = document.getElementById("cf-hl-done");
|
|
2320
|
+
if (doneBtn) doneBtn.textContent = `Done (${highlights.length})`;
|
|
2321
|
+
}
|
|
2322
|
+
function finishHighlighting() {
|
|
2323
|
+
const annotations = highlights.map((h) => ({
|
|
2324
|
+
selector: getSelector(h.el),
|
|
2325
|
+
tagName: h.el.tagName.toLowerCase(),
|
|
2326
|
+
text: h.el.textContent?.slice(0, 100) || void 0,
|
|
2327
|
+
rect: { x: Math.round(h.rect.x), y: Math.round(h.rect.y), width: Math.round(h.rect.width), height: Math.round(h.rect.height) },
|
|
2328
|
+
note: h.note
|
|
2329
|
+
}));
|
|
2330
|
+
cleanup();
|
|
2331
|
+
onDoneCallback?.(annotations);
|
|
2332
|
+
}
|
|
2333
|
+
function cancelHighlighting() {
|
|
2334
|
+
cleanup();
|
|
2335
|
+
onDoneCallback?.([]);
|
|
2336
|
+
}
|
|
2337
|
+
function cleanup() {
|
|
2338
|
+
if (hoverBox) {
|
|
2339
|
+
hoverBox.remove();
|
|
2340
|
+
hoverBox = null;
|
|
2341
|
+
}
|
|
2342
|
+
if (overlay) {
|
|
2343
|
+
overlay.remove();
|
|
2344
|
+
overlay = null;
|
|
2345
|
+
}
|
|
2346
|
+
highlights = [];
|
|
2347
|
+
isActive = false;
|
|
2348
|
+
}
|
|
2349
|
+
function startHighlighting() {
|
|
2350
|
+
return new Promise((resolve) => {
|
|
2351
|
+
if (isActive) {
|
|
2352
|
+
resolve([]);
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
isActive = true;
|
|
2356
|
+
highlights = [];
|
|
2357
|
+
onDoneCallback = resolve;
|
|
2358
|
+
createOverlay();
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
function isHighlighting() {
|
|
2362
|
+
return isActive;
|
|
2363
|
+
}
|
|
2364
|
+
var overlay, highlights, isActive, onDoneCallback, hoverBox;
|
|
2365
|
+
var init_highlighter = __esm({
|
|
2366
|
+
"src/highlighter.ts"() {
|
|
2367
|
+
"use strict";
|
|
2368
|
+
overlay = null;
|
|
2369
|
+
highlights = [];
|
|
2370
|
+
isActive = false;
|
|
2371
|
+
onDoneCallback = null;
|
|
2372
|
+
hoverBox = null;
|
|
2373
|
+
}
|
|
2374
|
+
});
|
|
2375
|
+
|
|
1060
2376
|
// src/vue.ts
|
|
1061
2377
|
var vue_exports = {};
|
|
1062
2378
|
__export(vue_exports, {
|