@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.mjs CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ mountWidget,
3
+ unmountWidget
4
+ } from "./chunk-NRA75GGU.mjs";
1
5
  import {
2
6
  captureScreenshot,
3
7
  clearScreenshot
@@ -176,1620 +180,6 @@ function clearNetworkLogs() {
176
180
  logs2 = [];
177
181
  }
178
182
 
179
- // src/annotation/types.ts
180
- var DEFAULT_STYLE = {
181
- strokeColor: "#FF3B30",
182
- strokeWidth: 3,
183
- fillColor: "transparent",
184
- opacity: 1,
185
- fontSize: 16,
186
- fontFamily: "system-ui, -apple-system, sans-serif"
187
- };
188
- var HIGHLIGHT_STYLE = {
189
- strokeColor: "transparent",
190
- fillColor: "#FFEB3B",
191
- opacity: 0.4
192
- };
193
- var BLUR_STYLE = {
194
- strokeColor: "#666666",
195
- fillColor: "#666666",
196
- opacity: 0.8
197
- };
198
- var COLOR_PALETTE = [
199
- "#FF3B30",
200
- // Red
201
- "#FF9500",
202
- // Orange
203
- "#FFCC00",
204
- // Yellow
205
- "#34C759",
206
- // Green
207
- "#007AFF",
208
- // Blue
209
- "#5856D6",
210
- // Purple
211
- "#AF52DE",
212
- // Pink
213
- "#000000",
214
- // Black
215
- "#FFFFFF"
216
- // White
217
- ];
218
- var STROKE_WIDTHS = [1, 2, 3, 5, 8];
219
-
220
- // src/annotation/toolbar.ts
221
- var TOOL_ICONS = {
222
- 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>`,
223
- 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>`,
224
- ellipse: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="12" rx="9" ry="6"/></svg>`,
225
- 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>`,
226
- line: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="19" x2="19" y2="5"/></svg>`,
227
- 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>`,
228
- 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>`,
229
- text: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 4v3h5.5v12h3V7H19V4z"/></svg>`,
230
- 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>`
231
- };
232
- var TOOL_LABELS = {
233
- select: "S\xE9lection",
234
- rectangle: "Rectangle",
235
- ellipse: "Ellipse",
236
- arrow: "Fl\xE8che",
237
- line: "Ligne",
238
- highlight: "Surbrillance",
239
- blur: "Floutage",
240
- text: "Texte",
241
- freehand: "Dessin libre"
242
- };
243
- var AnnotationToolbar = class {
244
- constructor(config2) {
245
- this.colorPicker = null;
246
- this.strokePicker = null;
247
- this.config = config2;
248
- this.element = this.createToolbar();
249
- }
250
- getElement() {
251
- return this.element;
252
- }
253
- setActiveTool(tool) {
254
- this.config.activeTool = tool;
255
- this.updateActiveState();
256
- }
257
- setStyle(style) {
258
- this.config.style = style;
259
- this.updateStyleDisplay();
260
- }
261
- createToolbar() {
262
- const toolbar = document.createElement("div");
263
- toolbar.className = "cf-toolbar";
264
- const toolsSection = document.createElement("div");
265
- toolsSection.className = "cf-toolbar-section cf-toolbar-tools";
266
- for (const tool of this.config.tools) {
267
- const button = this.createToolButton(tool);
268
- toolsSection.appendChild(button);
269
- }
270
- const separator1 = document.createElement("div");
271
- separator1.className = "cf-toolbar-separator";
272
- const styleSection = document.createElement("div");
273
- styleSection.className = "cf-toolbar-section cf-toolbar-style";
274
- const colorBtn = document.createElement("button");
275
- colorBtn.className = "cf-toolbar-btn cf-color-btn";
276
- colorBtn.title = "Couleur";
277
- colorBtn.innerHTML = `<span class="cf-color-preview" style="background: ${this.config.style.strokeColor}"></span>`;
278
- colorBtn.addEventListener("click", () => this.toggleColorPicker());
279
- styleSection.appendChild(colorBtn);
280
- const strokeBtn = document.createElement("button");
281
- strokeBtn.className = "cf-toolbar-btn cf-stroke-btn";
282
- strokeBtn.title = "\xC9paisseur";
283
- strokeBtn.innerHTML = `<span class="cf-stroke-preview">${this.config.style.strokeWidth}px</span>`;
284
- strokeBtn.addEventListener("click", () => this.toggleStrokePicker());
285
- styleSection.appendChild(strokeBtn);
286
- const separator2 = document.createElement("div");
287
- separator2.className = "cf-toolbar-separator";
288
- const actionsSection = document.createElement("div");
289
- actionsSection.className = "cf-toolbar-section cf-toolbar-actions";
290
- const undoBtn = document.createElement("button");
291
- undoBtn.className = "cf-toolbar-btn";
292
- undoBtn.title = "Annuler (Ctrl+Z)";
293
- 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>`;
294
- undoBtn.addEventListener("click", () => this.config.onUndo());
295
- actionsSection.appendChild(undoBtn);
296
- const clearBtn = document.createElement("button");
297
- clearBtn.className = "cf-toolbar-btn";
298
- clearBtn.title = "Tout effacer";
299
- 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>`;
300
- clearBtn.addEventListener("click", () => this.config.onClear());
301
- actionsSection.appendChild(clearBtn);
302
- toolbar.appendChild(toolsSection);
303
- toolbar.appendChild(separator1);
304
- toolbar.appendChild(styleSection);
305
- toolbar.appendChild(separator2);
306
- toolbar.appendChild(actionsSection);
307
- return toolbar;
308
- }
309
- createToolButton(tool) {
310
- const button = document.createElement("button");
311
- button.className = `cf-toolbar-btn cf-tool-btn ${tool === this.config.activeTool ? "active" : ""}`;
312
- button.dataset.tool = tool;
313
- button.title = TOOL_LABELS[tool];
314
- button.innerHTML = TOOL_ICONS[tool];
315
- button.addEventListener("click", () => {
316
- this.config.onToolChange(tool);
317
- this.setActiveTool(tool);
318
- });
319
- return button;
320
- }
321
- updateActiveState() {
322
- const buttons = this.element.querySelectorAll(".cf-tool-btn");
323
- buttons.forEach((btn) => {
324
- const button = btn;
325
- button.classList.toggle("active", button.dataset.tool === this.config.activeTool);
326
- });
327
- }
328
- updateStyleDisplay() {
329
- const colorPreview = this.element.querySelector(".cf-color-preview");
330
- if (colorPreview) {
331
- colorPreview.style.background = this.config.style.strokeColor;
332
- }
333
- const strokePreview = this.element.querySelector(".cf-stroke-preview");
334
- if (strokePreview) {
335
- strokePreview.textContent = `${this.config.style.strokeWidth}px`;
336
- }
337
- }
338
- toggleColorPicker() {
339
- if (this.colorPicker) {
340
- this.colorPicker.remove();
341
- this.colorPicker = null;
342
- return;
343
- }
344
- if (this.strokePicker) {
345
- this.strokePicker.remove();
346
- this.strokePicker = null;
347
- }
348
- this.colorPicker = document.createElement("div");
349
- this.colorPicker.className = "cf-picker cf-color-picker";
350
- for (const color of COLOR_PALETTE) {
351
- const swatch = document.createElement("button");
352
- swatch.className = `cf-color-swatch ${color === this.config.style.strokeColor ? "active" : ""}`;
353
- swatch.style.background = color;
354
- swatch.addEventListener("click", () => {
355
- this.config.onStyleChange({ strokeColor: color });
356
- this.setStyle({ ...this.config.style, strokeColor: color });
357
- this.colorPicker?.remove();
358
- this.colorPicker = null;
359
- });
360
- this.colorPicker.appendChild(swatch);
361
- }
362
- const colorBtn = this.element.querySelector(".cf-color-btn");
363
- colorBtn?.appendChild(this.colorPicker);
364
- }
365
- toggleStrokePicker() {
366
- if (this.strokePicker) {
367
- this.strokePicker.remove();
368
- this.strokePicker = null;
369
- return;
370
- }
371
- if (this.colorPicker) {
372
- this.colorPicker.remove();
373
- this.colorPicker = null;
374
- }
375
- this.strokePicker = document.createElement("div");
376
- this.strokePicker.className = "cf-picker cf-stroke-picker";
377
- for (const width of STROKE_WIDTHS) {
378
- const option = document.createElement("button");
379
- option.className = `cf-stroke-option ${width === this.config.style.strokeWidth ? "active" : ""}`;
380
- option.innerHTML = `<span style="height: ${width}px"></span> ${width}px`;
381
- option.addEventListener("click", () => {
382
- this.config.onStyleChange({ strokeWidth: width });
383
- this.setStyle({ ...this.config.style, strokeWidth: width });
384
- this.strokePicker?.remove();
385
- this.strokePicker = null;
386
- });
387
- this.strokePicker.appendChild(option);
388
- }
389
- const strokeBtn = this.element.querySelector(".cf-stroke-btn");
390
- strokeBtn?.appendChild(this.strokePicker);
391
- }
392
- };
393
-
394
- // src/annotation/styles.ts
395
- var STYLES = `
396
- /* Annotation Overlay */
397
- .cf-annotation-overlay {
398
- position: fixed;
399
- top: 0;
400
- left: 0;
401
- right: 0;
402
- bottom: 0;
403
- z-index: 999999;
404
- background: rgba(0, 0, 0, 0.85);
405
- display: flex;
406
- align-items: center;
407
- justify-content: center;
408
- animation: cf-fade-in 0.2s ease-out;
409
- }
410
-
411
- @keyframes cf-fade-in {
412
- from { opacity: 0; }
413
- to { opacity: 1; }
414
- }
415
-
416
- /* Editor Wrapper */
417
- .cf-annotation-wrapper {
418
- display: flex;
419
- flex-direction: column;
420
- max-width: 95vw;
421
- max-height: 95vh;
422
- background: #1a1a1a;
423
- border-radius: 12px;
424
- overflow: hidden;
425
- box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
426
- }
427
-
428
- /* Canvas Container */
429
- .cf-annotation-canvas-container {
430
- flex: 1;
431
- display: flex;
432
- align-items: center;
433
- justify-content: center;
434
- overflow: auto;
435
- padding: 16px;
436
- background: #0d0d0d;
437
- }
438
-
439
- .cf-annotation-canvas {
440
- max-width: 100%;
441
- max-height: calc(95vh - 140px);
442
- border-radius: 4px;
443
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
444
- cursor: crosshair;
445
- }
446
-
447
- /* Toolbar */
448
- .cf-toolbar {
449
- display: flex;
450
- align-items: center;
451
- gap: 8px;
452
- padding: 12px 16px;
453
- background: #2a2a2a;
454
- border-bottom: 1px solid #3a3a3a;
455
- }
456
-
457
- .cf-toolbar-section {
458
- display: flex;
459
- align-items: center;
460
- gap: 4px;
461
- }
462
-
463
- .cf-toolbar-separator {
464
- width: 1px;
465
- height: 24px;
466
- background: #4a4a4a;
467
- margin: 0 8px;
468
- }
469
-
470
- .cf-toolbar-btn {
471
- display: flex;
472
- align-items: center;
473
- justify-content: center;
474
- width: 36px;
475
- height: 36px;
476
- padding: 0;
477
- border: none;
478
- border-radius: 8px;
479
- background: transparent;
480
- color: #999;
481
- cursor: pointer;
482
- transition: all 0.15s ease;
483
- }
484
-
485
- .cf-toolbar-btn:hover {
486
- background: #3a3a3a;
487
- color: #fff;
488
- }
489
-
490
- .cf-toolbar-btn.active {
491
- background: #007AFF;
492
- color: #fff;
493
- }
494
-
495
- .cf-toolbar-btn svg {
496
- width: 20px;
497
- height: 20px;
498
- }
499
-
500
- /* Color Button */
501
- .cf-color-btn {
502
- position: relative;
503
- }
504
-
505
- .cf-color-preview {
506
- width: 20px;
507
- height: 20px;
508
- border-radius: 50%;
509
- border: 2px solid #fff;
510
- box-shadow: 0 0 0 1px rgba(0,0,0,0.2);
511
- }
512
-
513
- /* Stroke Button */
514
- .cf-stroke-btn {
515
- width: auto;
516
- padding: 0 12px;
517
- }
518
-
519
- .cf-stroke-preview {
520
- font-size: 12px;
521
- font-weight: 500;
522
- color: inherit;
523
- }
524
-
525
- /* Pickers */
526
- .cf-picker {
527
- position: absolute;
528
- top: 100%;
529
- left: 50%;
530
- transform: translateX(-50%);
531
- margin-top: 8px;
532
- padding: 8px;
533
- background: #2a2a2a;
534
- border-radius: 8px;
535
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
536
- z-index: 10;
537
- }
538
-
539
- .cf-color-picker {
540
- display: grid;
541
- grid-template-columns: repeat(5, 1fr);
542
- gap: 4px;
543
- width: 140px;
544
- }
545
-
546
- .cf-color-swatch {
547
- width: 24px;
548
- height: 24px;
549
- border-radius: 50%;
550
- border: 2px solid transparent;
551
- cursor: pointer;
552
- transition: transform 0.15s ease;
553
- }
554
-
555
- .cf-color-swatch:hover {
556
- transform: scale(1.15);
557
- }
558
-
559
- .cf-color-swatch.active {
560
- border-color: #fff;
561
- }
562
-
563
- .cf-stroke-picker {
564
- display: flex;
565
- flex-direction: column;
566
- gap: 4px;
567
- min-width: 80px;
568
- }
569
-
570
- .cf-stroke-option {
571
- display: flex;
572
- align-items: center;
573
- gap: 8px;
574
- padding: 6px 10px;
575
- border: none;
576
- border-radius: 4px;
577
- background: transparent;
578
- color: #999;
579
- font-size: 12px;
580
- cursor: pointer;
581
- transition: all 0.15s ease;
582
- }
583
-
584
- .cf-stroke-option:hover {
585
- background: #3a3a3a;
586
- color: #fff;
587
- }
588
-
589
- .cf-stroke-option.active {
590
- background: #007AFF;
591
- color: #fff;
592
- }
593
-
594
- .cf-stroke-option span {
595
- width: 24px;
596
- background: currentColor;
597
- border-radius: 2px;
598
- }
599
-
600
- /* Action Buttons */
601
- .cf-annotation-actions {
602
- display: flex;
603
- justify-content: flex-end;
604
- gap: 12px;
605
- padding: 12px 16px;
606
- background: #2a2a2a;
607
- border-top: 1px solid #3a3a3a;
608
- }
609
-
610
- .cf-btn {
611
- display: flex;
612
- align-items: center;
613
- justify-content: center;
614
- padding: 10px 20px;
615
- border: none;
616
- border-radius: 8px;
617
- font-size: 14px;
618
- font-weight: 500;
619
- cursor: pointer;
620
- transition: all 0.15s ease;
621
- }
622
-
623
- .cf-btn-primary {
624
- background: #007AFF;
625
- color: #fff;
626
- }
627
-
628
- .cf-btn-primary:hover {
629
- background: #0066DD;
630
- }
631
-
632
- .cf-btn-secondary {
633
- background: #3a3a3a;
634
- color: #fff;
635
- }
636
-
637
- .cf-btn-secondary:hover {
638
- background: #4a4a4a;
639
- }
640
-
641
- /* Text Input */
642
- .cf-annotation-text-input {
643
- position: fixed;
644
- z-index: 1000000;
645
- padding: 4px 8px;
646
- border: 2px solid #007AFF;
647
- border-radius: 4px;
648
- background: rgba(255, 255, 255, 0.95);
649
- font-size: 16px;
650
- font-family: system-ui, -apple-system, sans-serif;
651
- outline: none;
652
- min-width: 150px;
653
- }
654
-
655
- /* Tool-specific cursors */
656
- .cf-annotation-canvas[data-tool="select"] { cursor: default; }
657
- .cf-annotation-canvas[data-tool="rectangle"] { cursor: crosshair; }
658
- .cf-annotation-canvas[data-tool="ellipse"] { cursor: crosshair; }
659
- .cf-annotation-canvas[data-tool="arrow"] { cursor: crosshair; }
660
- .cf-annotation-canvas[data-tool="line"] { cursor: crosshair; }
661
- .cf-annotation-canvas[data-tool="highlight"] { cursor: crosshair; }
662
- .cf-annotation-canvas[data-tool="blur"] { cursor: crosshair; }
663
- .cf-annotation-canvas[data-tool="text"] { cursor: text; }
664
- .cf-annotation-canvas[data-tool="freehand"] { cursor: crosshair; }
665
-
666
- /* Responsive */
667
- @media (max-width: 768px) {
668
- .cf-toolbar {
669
- flex-wrap: wrap;
670
- justify-content: center;
671
- }
672
-
673
- .cf-toolbar-separator {
674
- display: none;
675
- }
676
-
677
- .cf-annotation-actions {
678
- justify-content: stretch;
679
- }
680
-
681
- .cf-annotation-actions .cf-btn {
682
- flex: 1;
683
- }
684
- }
685
- `;
686
- var stylesInjected = false;
687
- function injectAnnotationStyles() {
688
- if (stylesInjected) return;
689
- const styleElement = document.createElement("style");
690
- styleElement.id = "cf-annotation-styles";
691
- styleElement.textContent = STYLES;
692
- document.head.appendChild(styleElement);
693
- stylesInjected = true;
694
- }
695
-
696
- // src/annotation/editor.ts
697
- var AnnotationEditor = class {
698
- constructor(config2) {
699
- this.container = null;
700
- this.canvas = null;
701
- this.ctx = null;
702
- this.toolbar = null;
703
- this.annotations = [];
704
- this.backgroundImage = null;
705
- this.state = {
706
- activeTool: "rectangle",
707
- style: { ...DEFAULT_STYLE },
708
- isDrawing: false,
709
- currentAnnotation: null
710
- };
711
- this.startPoint = null;
712
- this.freehandPoints = [];
713
- this.textInput = null;
714
- this.config = {
715
- ...config2,
716
- tools: config2.tools || ["select", "rectangle", "arrow", "highlight", "blur", "text", "freehand"],
717
- defaultStyle: config2.defaultStyle || DEFAULT_STYLE
718
- };
719
- this.state.style = { ...this.config.defaultStyle };
720
- }
721
- /**
722
- * Open the annotation editor with a screenshot
723
- */
724
- async open(screenshotDataUrl2) {
725
- injectAnnotationStyles();
726
- this.backgroundImage = await this.loadImage(screenshotDataUrl2);
727
- this.createEditorUI();
728
- this.setupEventListeners();
729
- this.render();
730
- }
731
- /**
732
- * Close the editor
733
- */
734
- close() {
735
- if (this.container) {
736
- this.container.remove();
737
- this.container = null;
738
- }
739
- this.canvas = null;
740
- this.ctx = null;
741
- this.toolbar = null;
742
- this.annotations = [];
743
- this.backgroundImage = null;
744
- }
745
- /**
746
- * Get the annotated image as data URL
747
- */
748
- getAnnotatedImage() {
749
- if (!this.canvas || !this.ctx) return "";
750
- const tempCanvas = document.createElement("canvas");
751
- tempCanvas.width = this.canvas.width;
752
- tempCanvas.height = this.canvas.height;
753
- const tempCtx = tempCanvas.getContext("2d");
754
- if (this.backgroundImage) {
755
- tempCtx.drawImage(this.backgroundImage, 0, 0);
756
- }
757
- this.drawAnnotations(tempCtx);
758
- return tempCanvas.toDataURL("image/png");
759
- }
760
- /**
761
- * Get annotations data
762
- */
763
- getAnnotations() {
764
- return [...this.annotations];
765
- }
766
- // Private methods
767
- loadImage(src) {
768
- return new Promise((resolve, reject) => {
769
- const img = new Image();
770
- img.onload = () => resolve(img);
771
- img.onerror = reject;
772
- img.src = src;
773
- });
774
- }
775
- createEditorUI() {
776
- this.container = document.createElement("div");
777
- this.container.className = "cf-annotation-overlay";
778
- const wrapper = document.createElement("div");
779
- wrapper.className = "cf-annotation-wrapper";
780
- const canvasContainer = document.createElement("div");
781
- canvasContainer.className = "cf-annotation-canvas-container";
782
- this.canvas = document.createElement("canvas");
783
- this.canvas.className = "cf-annotation-canvas";
784
- this.canvas.width = this.backgroundImage?.width || 1920;
785
- this.canvas.height = this.backgroundImage?.height || 1080;
786
- this.ctx = this.canvas.getContext("2d");
787
- canvasContainer.appendChild(this.canvas);
788
- this.toolbar = new AnnotationToolbar({
789
- tools: this.config.tools,
790
- activeTool: this.state.activeTool,
791
- style: this.state.style,
792
- onToolChange: (tool) => this.setActiveTool(tool),
793
- onStyleChange: (style) => this.setStyle(style),
794
- onUndo: () => this.undo(),
795
- onClear: () => this.clearAll(),
796
- onSave: () => this.save(),
797
- onCancel: () => this.cancel()
798
- });
799
- const actions = document.createElement("div");
800
- actions.className = "cf-annotation-actions";
801
- actions.innerHTML = `
802
- <button class="cf-btn cf-btn-secondary" data-action="cancel">Annuler</button>
803
- <button class="cf-btn cf-btn-primary" data-action="save">Enregistrer</button>
804
- `;
805
- wrapper.appendChild(this.toolbar.getElement());
806
- wrapper.appendChild(canvasContainer);
807
- wrapper.appendChild(actions);
808
- this.container.appendChild(wrapper);
809
- document.body.appendChild(this.container);
810
- actions.querySelector('[data-action="cancel"]')?.addEventListener("click", () => this.cancel());
811
- actions.querySelector('[data-action="save"]')?.addEventListener("click", () => this.save());
812
- }
813
- setupEventListeners() {
814
- if (!this.canvas) return;
815
- this.canvas.addEventListener("mousedown", this.handleMouseDown.bind(this));
816
- this.canvas.addEventListener("mousemove", this.handleMouseMove.bind(this));
817
- this.canvas.addEventListener("mouseup", this.handleMouseUp.bind(this));
818
- this.canvas.addEventListener("mouseleave", this.handleMouseUp.bind(this));
819
- this.canvas.addEventListener("touchstart", this.handleTouchStart.bind(this));
820
- this.canvas.addEventListener("touchmove", this.handleTouchMove.bind(this));
821
- this.canvas.addEventListener("touchend", this.handleTouchEnd.bind(this));
822
- document.addEventListener("keydown", this.handleKeyDown.bind(this));
823
- }
824
- handleMouseDown(e) {
825
- const point = this.getCanvasPoint(e);
826
- this.startDrawing(point);
827
- }
828
- handleMouseMove(e) {
829
- const point = this.getCanvasPoint(e);
830
- this.continueDrawing(point);
831
- }
832
- handleMouseUp(_e) {
833
- this.finishDrawing();
834
- }
835
- handleTouchStart(e) {
836
- e.preventDefault();
837
- const touch = e.touches[0];
838
- const point = this.getCanvasPointFromTouch(touch);
839
- this.startDrawing(point);
840
- }
841
- handleTouchMove(e) {
842
- e.preventDefault();
843
- const touch = e.touches[0];
844
- const point = this.getCanvasPointFromTouch(touch);
845
- this.continueDrawing(point);
846
- }
847
- handleTouchEnd(e) {
848
- e.preventDefault();
849
- this.finishDrawing();
850
- }
851
- handleKeyDown(e) {
852
- if ((e.ctrlKey || e.metaKey) && e.key === "z") {
853
- e.preventDefault();
854
- this.undo();
855
- }
856
- if (e.key === "Escape") {
857
- if (this.state.isDrawing) {
858
- this.state.isDrawing = false;
859
- this.state.currentAnnotation = null;
860
- this.render();
861
- } else {
862
- this.cancel();
863
- }
864
- }
865
- if (e.key === "Enter" && !this.state.isDrawing) {
866
- this.save();
867
- }
868
- }
869
- getCanvasPoint(e) {
870
- const rect = this.canvas.getBoundingClientRect();
871
- const scaleX = this.canvas.width / rect.width;
872
- const scaleY = this.canvas.height / rect.height;
873
- return {
874
- x: (e.clientX - rect.left) * scaleX,
875
- y: (e.clientY - rect.top) * scaleY
876
- };
877
- }
878
- getCanvasPointFromTouch(touch) {
879
- const rect = this.canvas.getBoundingClientRect();
880
- const scaleX = this.canvas.width / rect.width;
881
- const scaleY = this.canvas.height / rect.height;
882
- return {
883
- x: (touch.clientX - rect.left) * scaleX,
884
- y: (touch.clientY - rect.top) * scaleY
885
- };
886
- }
887
- startDrawing(point) {
888
- if (this.state.activeTool === "select") return;
889
- this.state.isDrawing = true;
890
- this.startPoint = point;
891
- if (this.state.activeTool === "text") {
892
- this.showTextInput(point);
893
- return;
894
- }
895
- if (this.state.activeTool === "freehand") {
896
- this.freehandPoints = [point];
897
- }
898
- }
899
- continueDrawing(point) {
900
- if (!this.state.isDrawing || !this.startPoint) return;
901
- if (this.state.activeTool === "freehand") {
902
- this.freehandPoints.push(point);
903
- }
904
- this.state.currentAnnotation = this.createAnnotation(this.startPoint, point);
905
- this.render();
906
- }
907
- finishDrawing() {
908
- if (!this.state.isDrawing || !this.state.currentAnnotation) {
909
- this.state.isDrawing = false;
910
- return;
911
- }
912
- if (this.isValidAnnotation(this.state.currentAnnotation)) {
913
- this.annotations.push(this.state.currentAnnotation);
914
- }
915
- this.state.isDrawing = false;
916
- this.state.currentAnnotation = null;
917
- this.startPoint = null;
918
- this.freehandPoints = [];
919
- this.render();
920
- }
921
- isValidAnnotation(annotation) {
922
- switch (annotation.type) {
923
- case "rectangle":
924
- case "ellipse":
925
- case "highlight":
926
- case "blur":
927
- const bounds = annotation.bounds;
928
- return Math.abs(bounds.width) > 5 && Math.abs(bounds.height) > 5;
929
- case "arrow":
930
- case "line":
931
- const start = annotation.start;
932
- const end = annotation.end;
933
- const dist = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
934
- return dist > 10;
935
- case "freehand":
936
- return annotation.points.length > 2;
937
- case "text":
938
- return !!annotation.text?.trim();
939
- default:
940
- return true;
941
- }
942
- }
943
- createAnnotation(start, end) {
944
- const id = this.generateId();
945
- const baseStyle = { ...this.state.style };
946
- switch (this.state.activeTool) {
947
- case "rectangle":
948
- return {
949
- id,
950
- type: "rectangle",
951
- bounds: this.createBounds(start, end),
952
- style: baseStyle,
953
- timestamp: Date.now()
954
- };
955
- case "ellipse":
956
- return {
957
- id,
958
- type: "ellipse",
959
- bounds: this.createBounds(start, end),
960
- style: baseStyle,
961
- timestamp: Date.now()
962
- };
963
- case "arrow":
964
- return {
965
- id,
966
- type: "arrow",
967
- start: { ...start },
968
- end: { ...end },
969
- style: baseStyle,
970
- timestamp: Date.now()
971
- };
972
- case "line":
973
- return {
974
- id,
975
- type: "line",
976
- start: { ...start },
977
- end: { ...end },
978
- style: baseStyle,
979
- timestamp: Date.now()
980
- };
981
- case "highlight":
982
- return {
983
- id,
984
- type: "highlight",
985
- bounds: this.createBounds(start, end),
986
- style: { ...baseStyle, ...HIGHLIGHT_STYLE },
987
- timestamp: Date.now()
988
- };
989
- case "blur":
990
- return {
991
- id,
992
- type: "blur",
993
- bounds: this.createBounds(start, end),
994
- style: { ...baseStyle, ...BLUR_STYLE },
995
- blurAmount: 10,
996
- timestamp: Date.now()
997
- };
998
- case "freehand":
999
- return {
1000
- id,
1001
- type: "freehand",
1002
- points: [...this.freehandPoints],
1003
- style: baseStyle,
1004
- timestamp: Date.now()
1005
- };
1006
- default:
1007
- return {
1008
- id,
1009
- type: "rectangle",
1010
- bounds: this.createBounds(start, end),
1011
- style: baseStyle,
1012
- timestamp: Date.now()
1013
- };
1014
- }
1015
- }
1016
- createBounds(start, end) {
1017
- return {
1018
- x: Math.min(start.x, end.x),
1019
- y: Math.min(start.y, end.y),
1020
- width: Math.abs(end.x - start.x),
1021
- height: Math.abs(end.y - start.y)
1022
- };
1023
- }
1024
- showTextInput(point) {
1025
- if (this.textInput) {
1026
- this.textInput.remove();
1027
- }
1028
- const rect = this.canvas.getBoundingClientRect();
1029
- const scaleX = rect.width / this.canvas.width;
1030
- const scaleY = rect.height / this.canvas.height;
1031
- this.textInput = document.createElement("input");
1032
- this.textInput.type = "text";
1033
- this.textInput.className = "cf-annotation-text-input";
1034
- this.textInput.style.left = `${rect.left + point.x * scaleX}px`;
1035
- this.textInput.style.top = `${rect.top + point.y * scaleY}px`;
1036
- this.textInput.style.color = this.state.style.strokeColor;
1037
- this.textInput.style.fontSize = `${(this.state.style.fontSize || 16) * scaleY}px`;
1038
- this.textInput.placeholder = "Tapez votre texte...";
1039
- document.body.appendChild(this.textInput);
1040
- this.textInput.focus();
1041
- const handleTextSubmit = () => {
1042
- if (this.textInput && this.textInput.value.trim()) {
1043
- const textAnnotation = {
1044
- id: this.generateId(),
1045
- type: "text",
1046
- position: point,
1047
- text: this.textInput.value.trim(),
1048
- style: { ...this.state.style },
1049
- timestamp: Date.now()
1050
- };
1051
- this.annotations.push(textAnnotation);
1052
- this.render();
1053
- }
1054
- this.textInput?.remove();
1055
- this.textInput = null;
1056
- this.state.isDrawing = false;
1057
- };
1058
- this.textInput.addEventListener("blur", handleTextSubmit);
1059
- this.textInput.addEventListener("keydown", (e) => {
1060
- if (e.key === "Enter") {
1061
- handleTextSubmit();
1062
- } else if (e.key === "Escape") {
1063
- this.textInput?.remove();
1064
- this.textInput = null;
1065
- this.state.isDrawing = false;
1066
- }
1067
- });
1068
- }
1069
- generateId() {
1070
- return `ann_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1071
- }
1072
- setActiveTool(tool) {
1073
- this.state.activeTool = tool;
1074
- this.toolbar?.setActiveTool(tool);
1075
- if (this.canvas) {
1076
- this.canvas.style.cursor = tool === "select" ? "default" : "crosshair";
1077
- }
1078
- }
1079
- setStyle(style) {
1080
- this.state.style = { ...this.state.style, ...style };
1081
- this.toolbar?.setStyle(this.state.style);
1082
- }
1083
- undo() {
1084
- if (this.annotations.length > 0) {
1085
- this.annotations.pop();
1086
- this.render();
1087
- }
1088
- }
1089
- clearAll() {
1090
- this.annotations = [];
1091
- this.render();
1092
- }
1093
- save() {
1094
- const imageData = this.getAnnotatedImage();
1095
- this.config.onSave?.(this.annotations, imageData);
1096
- this.close();
1097
- }
1098
- cancel() {
1099
- this.config.onCancel?.();
1100
- this.close();
1101
- }
1102
- render() {
1103
- if (!this.ctx || !this.canvas) return;
1104
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
1105
- if (this.backgroundImage) {
1106
- this.ctx.drawImage(this.backgroundImage, 0, 0);
1107
- }
1108
- this.drawAnnotations(this.ctx);
1109
- if (this.state.currentAnnotation) {
1110
- this.drawAnnotation(this.ctx, this.state.currentAnnotation);
1111
- }
1112
- }
1113
- drawAnnotations(ctx) {
1114
- for (const annotation of this.annotations) {
1115
- this.drawAnnotation(ctx, annotation);
1116
- }
1117
- }
1118
- drawAnnotation(ctx, annotation) {
1119
- ctx.save();
1120
- ctx.globalAlpha = annotation.style.opacity;
1121
- ctx.strokeStyle = annotation.style.strokeColor;
1122
- ctx.fillStyle = annotation.style.fillColor;
1123
- ctx.lineWidth = annotation.style.strokeWidth;
1124
- ctx.lineCap = "round";
1125
- ctx.lineJoin = "round";
1126
- switch (annotation.type) {
1127
- case "rectangle":
1128
- this.drawRectangle(ctx, annotation.bounds, annotation.style);
1129
- break;
1130
- case "ellipse":
1131
- this.drawEllipse(ctx, annotation.bounds, annotation.style);
1132
- break;
1133
- case "arrow":
1134
- this.drawArrow(ctx, annotation.start, annotation.end, annotation.style);
1135
- break;
1136
- case "line":
1137
- this.drawLine(ctx, annotation.start, annotation.end);
1138
- break;
1139
- case "highlight":
1140
- this.drawHighlight(ctx, annotation.bounds, annotation.style);
1141
- break;
1142
- case "blur":
1143
- this.drawBlur(ctx, annotation.bounds, annotation.blurAmount);
1144
- break;
1145
- case "text":
1146
- this.drawText(ctx, annotation.position, annotation.text, annotation.style);
1147
- break;
1148
- case "freehand":
1149
- this.drawFreehand(ctx, annotation.points);
1150
- break;
1151
- }
1152
- ctx.restore();
1153
- }
1154
- drawRectangle(ctx, bounds, style) {
1155
- if (style.fillColor !== "transparent") {
1156
- ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1157
- }
1158
- ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1159
- }
1160
- drawEllipse(ctx, bounds, style) {
1161
- const centerX = bounds.x + bounds.width / 2;
1162
- const centerY = bounds.y + bounds.height / 2;
1163
- const radiusX = bounds.width / 2;
1164
- const radiusY = bounds.height / 2;
1165
- ctx.beginPath();
1166
- ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
1167
- if (style.fillColor !== "transparent") {
1168
- ctx.fill();
1169
- }
1170
- ctx.stroke();
1171
- }
1172
- drawArrow(ctx, start, end, style) {
1173
- const headLength = 15 + style.strokeWidth * 2;
1174
- const angle = Math.atan2(end.y - start.y, end.x - start.x);
1175
- ctx.beginPath();
1176
- ctx.moveTo(start.x, start.y);
1177
- ctx.lineTo(end.x, end.y);
1178
- ctx.stroke();
1179
- ctx.beginPath();
1180
- ctx.moveTo(end.x, end.y);
1181
- ctx.lineTo(
1182
- end.x - headLength * Math.cos(angle - Math.PI / 6),
1183
- end.y - headLength * Math.sin(angle - Math.PI / 6)
1184
- );
1185
- ctx.lineTo(
1186
- end.x - headLength * Math.cos(angle + Math.PI / 6),
1187
- end.y - headLength * Math.sin(angle + Math.PI / 6)
1188
- );
1189
- ctx.closePath();
1190
- ctx.fillStyle = style.strokeColor;
1191
- ctx.fill();
1192
- }
1193
- drawLine(ctx, start, end) {
1194
- ctx.beginPath();
1195
- ctx.moveTo(start.x, start.y);
1196
- ctx.lineTo(end.x, end.y);
1197
- ctx.stroke();
1198
- }
1199
- drawHighlight(ctx, bounds, style) {
1200
- ctx.fillStyle = style.fillColor;
1201
- ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1202
- }
1203
- drawBlur(ctx, bounds, blurAmount) {
1204
- const pixelSize = Math.max(blurAmount, 5);
1205
- if (this.backgroundImage) {
1206
- const tempCanvas = document.createElement("canvas");
1207
- tempCanvas.width = bounds.width;
1208
- tempCanvas.height = bounds.height;
1209
- const tempCtx = tempCanvas.getContext("2d");
1210
- tempCtx.drawImage(
1211
- this.backgroundImage,
1212
- bounds.x,
1213
- bounds.y,
1214
- bounds.width,
1215
- bounds.height,
1216
- 0,
1217
- 0,
1218
- bounds.width,
1219
- bounds.height
1220
- );
1221
- const w = tempCanvas.width;
1222
- const h = tempCanvas.height;
1223
- tempCtx.imageSmoothingEnabled = false;
1224
- tempCtx.drawImage(tempCanvas, 0, 0, w, h, 0, 0, w / pixelSize, h / pixelSize);
1225
- tempCtx.drawImage(tempCanvas, 0, 0, w / pixelSize, h / pixelSize, 0, 0, w, h);
1226
- ctx.drawImage(tempCanvas, bounds.x, bounds.y);
1227
- }
1228
- ctx.strokeStyle = "#999";
1229
- ctx.lineWidth = 1;
1230
- ctx.setLineDash([5, 5]);
1231
- ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1232
- ctx.setLineDash([]);
1233
- }
1234
- drawText(ctx, position, text, style) {
1235
- ctx.font = `${style.fontSize || 16}px ${style.fontFamily || "system-ui"}`;
1236
- ctx.fillStyle = style.strokeColor;
1237
- ctx.textBaseline = "top";
1238
- const metrics = ctx.measureText(text);
1239
- const padding = 4;
1240
- ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
1241
- ctx.fillRect(
1242
- position.x - padding,
1243
- position.y - padding,
1244
- metrics.width + padding * 2,
1245
- (style.fontSize || 16) + padding * 2
1246
- );
1247
- ctx.fillStyle = style.strokeColor;
1248
- ctx.fillText(text, position.x, position.y);
1249
- }
1250
- drawFreehand(ctx, points) {
1251
- if (points.length < 2) return;
1252
- ctx.beginPath();
1253
- ctx.moveTo(points[0].x, points[0].y);
1254
- for (let i = 1; i < points.length; i++) {
1255
- ctx.lineTo(points[i].x, points[i].y);
1256
- }
1257
- ctx.stroke();
1258
- }
1259
- };
1260
-
1261
- // src/widget.ts
1262
- var DEFAULT_CONFIG = {
1263
- position: "bottom-right",
1264
- color: "#1e3a5f",
1265
- text: "Report Bug",
1266
- showOnInit: true
1267
- };
1268
- var container = null;
1269
- var onSubmitCallback = null;
1270
- var screenshotDataUrl = null;
1271
- var annotatedImageUrl = null;
1272
- var annotationsData = [];
1273
- var isExpanded = false;
1274
- var currentCfg = { ...DEFAULT_CONFIG };
1275
- function mountWidget(config2 = {}, onSubmit, opts) {
1276
- if (container) return;
1277
- onSubmitCallback = onSubmit;
1278
- const cfg = { ...DEFAULT_CONFIG, ...config2 };
1279
- currentCfg = cfg;
1280
- container = document.createElement("div");
1281
- container.id = "checkflow-widget";
1282
- injectStyles(cfg);
1283
- container.innerHTML = getTriggerHTML(cfg);
1284
- document.body.appendChild(container);
1285
- const btn = container.querySelector("#cf-trigger");
1286
- btn?.addEventListener("click", () => openModal(cfg, opts));
1287
- }
1288
- function getBackdropClass(cfg, expanded) {
1289
- if (expanded) return "cf-backdrop--center";
1290
- if (cfg.position === "center") return "cf-backdrop--center";
1291
- return "cf-backdrop--corner cf-pos-" + cfg.position;
1292
- }
1293
- function openModal(cfg, opts) {
1294
- if (!container) return;
1295
- const existing = document.getElementById("cf-modal-backdrop");
1296
- if (existing) existing.remove();
1297
- isExpanded = false;
1298
- screenshotDataUrl = null;
1299
- annotatedImageUrl = null;
1300
- annotationsData = [];
1301
- const backdrop = document.createElement("div");
1302
- backdrop.id = "cf-modal-backdrop";
1303
- backdrop.className = getBackdropClass(cfg, false);
1304
- backdrop.innerHTML = getModalHTML(cfg, false);
1305
- document.body.appendChild(backdrop);
1306
- backdrop.addEventListener("click", (e) => {
1307
- if (e.target === backdrop) closeModal();
1308
- });
1309
- bindModalEvents(cfg, opts);
1310
- }
1311
- function closeModal() {
1312
- const backdrop = document.getElementById("cf-modal-backdrop");
1313
- if (backdrop) backdrop.remove();
1314
- isExpanded = false;
1315
- screenshotDataUrl = null;
1316
- annotatedImageUrl = null;
1317
- annotationsData = [];
1318
- }
1319
- function expandModal(cfg, opts) {
1320
- const backdrop = document.getElementById("cf-modal-backdrop");
1321
- if (!backdrop) return;
1322
- const name = backdrop.querySelector("#cf-name")?.value || "";
1323
- const email = backdrop.querySelector("#cf-email")?.value || "";
1324
- const desc = backdrop.querySelector("#cf-desc")?.value || "";
1325
- isExpanded = true;
1326
- backdrop.className = getBackdropClass(cfg, true);
1327
- backdrop.innerHTML = getModalHTML(cfg, true);
1328
- bindModalEvents(cfg, opts);
1329
- const nameEl = backdrop.querySelector("#cf-name");
1330
- const emailEl = backdrop.querySelector("#cf-email");
1331
- const descEl = backdrop.querySelector("#cf-desc");
1332
- if (nameEl) nameEl.value = name;
1333
- if (emailEl) emailEl.value = email;
1334
- if (descEl) descEl.value = desc;
1335
- }
1336
- function collapseModal(cfg, opts) {
1337
- const backdrop = document.getElementById("cf-modal-backdrop");
1338
- if (!backdrop) return;
1339
- const name = backdrop.querySelector("#cf-name")?.value || "";
1340
- const email = backdrop.querySelector("#cf-email")?.value || "";
1341
- const desc = backdrop.querySelector("#cf-desc")?.value || "";
1342
- isExpanded = false;
1343
- screenshotDataUrl = null;
1344
- annotatedImageUrl = null;
1345
- annotationsData = [];
1346
- backdrop.className = getBackdropClass(cfg, false);
1347
- backdrop.innerHTML = getModalHTML(cfg, false);
1348
- bindModalEvents(cfg, opts);
1349
- const nameEl = backdrop.querySelector("#cf-name");
1350
- const emailEl = backdrop.querySelector("#cf-email");
1351
- const descEl = backdrop.querySelector("#cf-desc");
1352
- if (nameEl) nameEl.value = name;
1353
- if (emailEl) emailEl.value = email;
1354
- if (descEl) descEl.value = desc;
1355
- }
1356
- function openAnnotationEditor(cfg, opts) {
1357
- if (!screenshotDataUrl) return;
1358
- const backdrop = document.getElementById("cf-modal-backdrop");
1359
- if (backdrop) backdrop.style.display = "none";
1360
- const editor = new AnnotationEditor({
1361
- tools: ["rectangle", "ellipse", "arrow", "highlight", "blur", "text", "freehand"],
1362
- defaultStyle: { strokeColor: "#FF3B30", strokeWidth: 3, fillColor: "transparent", opacity: 1, fontSize: 16, fontFamily: "system-ui, -apple-system, sans-serif" },
1363
- onSave: (annotations, imageData) => {
1364
- annotationsData = annotations;
1365
- annotatedImageUrl = imageData;
1366
- if (backdrop) {
1367
- backdrop.style.display = "flex";
1368
- const img = backdrop.querySelector("#cf-screenshot-img");
1369
- if (img) img.src = annotatedImageUrl;
1370
- const annotBtn = backdrop.querySelector("#cf-annotate-btn");
1371
- if (annotBtn) annotBtn.textContent = `Annoter (${annotationsData.length})`;
1372
- }
1373
- },
1374
- onCancel: () => {
1375
- if (backdrop) backdrop.style.display = "flex";
1376
- }
1377
- });
1378
- editor.open(screenshotDataUrl);
1379
- }
1380
- function bindModalEvents(cfg, opts) {
1381
- const backdrop = document.getElementById("cf-modal-backdrop");
1382
- if (!backdrop) return;
1383
- backdrop.querySelector("#cf-cancel")?.addEventListener("click", closeModal);
1384
- backdrop.querySelector("#cf-screenshot-btn")?.addEventListener("click", async () => {
1385
- if (!opts?.onScreenshot) return;
1386
- backdrop.style.display = "none";
1387
- try {
1388
- const data = await opts.onScreenshot();
1389
- if (data) {
1390
- screenshotDataUrl = data;
1391
- backdrop.style.display = "flex";
1392
- expandModal(cfg, opts);
1393
- return;
1394
- }
1395
- } catch {
1396
- }
1397
- backdrop.style.display = "flex";
1398
- });
1399
- backdrop.querySelector("#cf-remove-screenshot")?.addEventListener("click", () => {
1400
- collapseModal(cfg, opts);
1401
- });
1402
- backdrop.querySelector("#cf-annotate-btn")?.addEventListener("click", () => {
1403
- openAnnotationEditor(cfg, opts);
1404
- });
1405
- backdrop.querySelector("#cf-submit")?.addEventListener("click", () => {
1406
- const desc = backdrop.querySelector("#cf-desc")?.value;
1407
- const name = backdrop.querySelector("#cf-name")?.value;
1408
- const email = backdrop.querySelector("#cf-email")?.value;
1409
- if (!desc?.trim()) {
1410
- const descEl = backdrop.querySelector("#cf-desc");
1411
- if (descEl) {
1412
- descEl.style.borderColor = "#e74c3c";
1413
- descEl.focus();
1414
- }
1415
- return;
1416
- }
1417
- onSubmitCallback?.({
1418
- title: desc.trim().slice(0, 100),
1419
- description: desc.trim(),
1420
- type: "BUG",
1421
- priority: "MEDIUM",
1422
- screenshot: annotatedImageUrl || screenshotDataUrl || void 0,
1423
- annotations: annotationsData.length > 0 ? annotationsData : void 0,
1424
- reporter_name: name?.trim() || void 0,
1425
- reporter_email: email?.trim() || void 0
1426
- });
1427
- closeModal();
1428
- showToast("Rapport envoy\xE9 avec succ\xE8s !");
1429
- });
1430
- }
1431
- function unmountWidget() {
1432
- closeModal();
1433
- if (container) {
1434
- container.remove();
1435
- container = null;
1436
- }
1437
- }
1438
- function showToast(msg) {
1439
- const toast = document.createElement("div");
1440
- toast.textContent = msg;
1441
- toast.style.cssText = 'position:fixed;bottom:80px;right:20px;background:#10b981;color:#fff;padding:10px 20px;border-radius:10px;font-size:14px;z-index:100010;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;box-shadow:0 4px 16px rgba(0,0,0,.18);animation:cf-toast-in .3s ease;';
1442
- document.body.appendChild(toast);
1443
- setTimeout(() => {
1444
- toast.style.opacity = "0";
1445
- toast.style.transition = "opacity .3s";
1446
- }, 2500);
1447
- setTimeout(() => toast.remove(), 3e3);
1448
- }
1449
- function injectStyles(cfg) {
1450
- const style = document.createElement("style");
1451
- style.id = "cf-widget-styles";
1452
- style.textContent = `
1453
- @keyframes cf-toast-in { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
1454
- @keyframes cf-modal-in { from { opacity:0; transform:scale(.96) translateY(8px); } to { opacity:1; transform:scale(1) translateY(0); } }
1455
-
1456
- #cf-trigger {
1457
- position:fixed;
1458
- ${getPositionCSS(cfg.position)}
1459
- z-index:100000;
1460
- background:${cfg.color};
1461
- color:#fff;
1462
- border:none;
1463
- border-radius:50px;
1464
- padding:11px 20px;
1465
- font-size:14px;
1466
- font-weight:500;
1467
- font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
1468
- cursor:pointer;
1469
- box-shadow:0 4px 16px rgba(0,0,0,.18);
1470
- display:flex;
1471
- align-items:center;
1472
- gap:8px;
1473
- transition:transform .15s,box-shadow .15s;
1474
- }
1475
- #cf-trigger:hover { transform:scale(1.04); box-shadow:0 6px 24px rgba(0,0,0,.22); }
1476
- #cf-trigger svg { width:18px; height:18px; }
1477
-
1478
- #cf-modal-backdrop {
1479
- position:fixed;
1480
- top:0;left:0;right:0;bottom:0;
1481
- background:rgba(0,0,0,.45);
1482
- z-index:100001;
1483
- display:flex;
1484
- font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
1485
- }
1486
- #cf-modal-backdrop.cf-backdrop--center {
1487
- align-items:center;
1488
- justify-content:center;
1489
- }
1490
- #cf-modal-backdrop.cf-backdrop--corner {
1491
- padding:20px;
1492
- }
1493
- #cf-modal-backdrop.cf-pos-bottom-right {
1494
- align-items:flex-end;
1495
- justify-content:flex-end;
1496
- }
1497
- #cf-modal-backdrop.cf-pos-bottom-left {
1498
- align-items:flex-end;
1499
- justify-content:flex-start;
1500
- }
1501
- #cf-modal-backdrop.cf-pos-top-right {
1502
- align-items:flex-start;
1503
- justify-content:flex-end;
1504
- }
1505
- #cf-modal-backdrop.cf-pos-top-left {
1506
- align-items:flex-start;
1507
- justify-content:flex-start;
1508
- }
1509
-
1510
- .cf-modal {
1511
- background:#fff;
1512
- border-radius:16px;
1513
- box-shadow:0 24px 80px rgba(0,0,0,.25);
1514
- overflow:hidden;
1515
- animation:cf-modal-in .25s ease;
1516
- display:flex;
1517
- flex-direction:column;
1518
- max-height:90vh;
1519
- }
1520
- .cf-modal--compact { width:420px; }
1521
- .cf-modal--expanded { width:min(1100px,92vw); flex-direction:column; }
1522
-
1523
- .cf-modal-header {
1524
- display:flex;
1525
- align-items:center;
1526
- justify-content:space-between;
1527
- padding:20px 24px 16px;
1528
- border-bottom:1px solid #f0f0f0;
1529
- }
1530
- .cf-modal-header h2 {
1531
- margin:0;
1532
- font-size:20px;
1533
- font-weight:700;
1534
- color:#1a1a2e;
1535
- }
1536
- .cf-modal-header .cf-logo {
1537
- width:28px;
1538
- height:28px;
1539
- color:${cfg.color};
1540
- }
1541
-
1542
- .cf-modal-body { display:flex; flex:1; overflow:hidden; }
1543
-
1544
- .cf-screenshot-panel {
1545
- flex:1;
1546
- min-width:0;
1547
- background:#1a1a2e;
1548
- display:flex;
1549
- flex-direction:column;
1550
- position:relative;
1551
- }
1552
- .cf-screenshot-panel img {
1553
- width:100%;
1554
- height:100%;
1555
- object-fit:contain;
1556
- max-height:60vh;
1557
- }
1558
- .cf-screenshot-tools {
1559
- position:absolute;
1560
- bottom:16px;
1561
- left:50%;
1562
- transform:translateX(-50%);
1563
- display:flex;
1564
- gap:8px;
1565
- }
1566
- .cf-screenshot-tools button {
1567
- padding:8px 18px;
1568
- border-radius:8px;
1569
- font-size:13px;
1570
- font-weight:500;
1571
- cursor:pointer;
1572
- font-family:inherit;
1573
- border:none;
1574
- transition:all .15s;
1575
- }
1576
- .cf-tool-highlight {
1577
- background:${cfg.color};
1578
- color:#fff;
1579
- }
1580
- .cf-tool-highlight:hover { opacity:.9; }
1581
- .cf-tool-mask {
1582
- background:#fff;
1583
- color:#1a1a2e;
1584
- border:1px solid #e0e0e0 !important;
1585
- }
1586
- .cf-tool-mask:hover { background:#f5f5f5; }
1587
-
1588
- .cf-form-panel {
1589
- width:100%;
1590
- padding:20px 24px;
1591
- display:flex;
1592
- flex-direction:column;
1593
- gap:14px;
1594
- overflow-y:auto;
1595
- }
1596
- .cf-modal--expanded .cf-form-panel {
1597
- width:340px;
1598
- flex-shrink:0;
1599
- border-left:1px solid #f0f0f0;
1600
- }
1601
-
1602
- .cf-field label {
1603
- display:block;
1604
- font-size:13px;
1605
- font-weight:500;
1606
- color:#1a1a2e;
1607
- margin-bottom:6px;
1608
- }
1609
- .cf-field label span { font-weight:400; color:#999; }
1610
- .cf-field input, .cf-field textarea {
1611
- width:100%;
1612
- padding:10px 14px;
1613
- border:1px solid #e0e0e0;
1614
- border-radius:10px;
1615
- font-size:14px;
1616
- font-family:inherit;
1617
- box-sizing:border-box;
1618
- outline:none;
1619
- transition:border-color .15s,box-shadow .15s;
1620
- color:#1a1a2e;
1621
- background:#fff;
1622
- }
1623
- .cf-field input::placeholder, .cf-field textarea::placeholder { color:#bbb; }
1624
- .cf-field input:focus, .cf-field textarea:focus {
1625
- border-color:${cfg.color};
1626
- box-shadow:0 0 0 3px ${cfg.color}18;
1627
- }
1628
- .cf-field textarea { resize:vertical; min-height:100px; }
1629
-
1630
- .cf-screenshot-action {
1631
- width:100%;
1632
- padding:12px;
1633
- border:1px solid #e0e0e0;
1634
- border-radius:10px;
1635
- background:#fff;
1636
- font-size:14px;
1637
- color:#1a1a2e;
1638
- cursor:pointer;
1639
- font-family:inherit;
1640
- transition:all .15s;
1641
- text-align:center;
1642
- }
1643
- .cf-screenshot-action:hover { background:#f8f8f8; border-color:#ccc; }
1644
-
1645
- .cf-remove-screenshot {
1646
- width:100%;
1647
- padding:10px;
1648
- border:1px solid #e0e0e0;
1649
- border-radius:10px;
1650
- background:#fff;
1651
- font-size:13px;
1652
- color:#666;
1653
- cursor:pointer;
1654
- font-family:inherit;
1655
- transition:all .15s;
1656
- text-align:center;
1657
- }
1658
- .cf-remove-screenshot:hover { background:#fff0f0; color:#e74c3c; border-color:#e74c3c; }
1659
-
1660
- .cf-submit-btn {
1661
- width:100%;
1662
- padding:13px;
1663
- border:none;
1664
- border-radius:10px;
1665
- background:${cfg.color};
1666
- color:#fff;
1667
- font-size:15px;
1668
- font-weight:600;
1669
- cursor:pointer;
1670
- font-family:inherit;
1671
- transition:all .15s;
1672
- }
1673
- .cf-submit-btn:hover { opacity:.92; transform:translateY(-1px); box-shadow:0 4px 12px ${cfg.color}40; }
1674
- .cf-submit-btn:disabled { opacity:.5; cursor:not-allowed; transform:none; }
1675
-
1676
- .cf-cancel-btn {
1677
- width:100%;
1678
- padding:11px;
1679
- border:1px solid #e0e0e0;
1680
- border-radius:10px;
1681
- background:#fff;
1682
- color:#1a1a2e;
1683
- font-size:14px;
1684
- font-weight:500;
1685
- cursor:pointer;
1686
- font-family:inherit;
1687
- transition:all .15s;
1688
- text-align:center;
1689
- }
1690
- .cf-cancel-btn:hover { background:#f8f8f8; }
1691
-
1692
- .cf-footer {
1693
- text-align:center;
1694
- padding:12px;
1695
- border-top:1px solid #f0f0f0;
1696
- font-size:11px;
1697
- color:#bbb;
1698
- }
1699
- .cf-footer a {
1700
- color:#999;
1701
- text-decoration:none;
1702
- font-weight:500;
1703
- }
1704
- .cf-footer a:hover { color:${cfg.color}; }
1705
- `;
1706
- document.head.appendChild(style);
1707
- }
1708
- function getPositionCSS(pos) {
1709
- switch (pos) {
1710
- case "bottom-left":
1711
- return "bottom:20px;left:20px;";
1712
- case "top-right":
1713
- return "top:20px;right:20px;";
1714
- case "top-left":
1715
- return "top:20px;left:20px;";
1716
- case "center":
1717
- return "bottom:20px;left:50%;transform:translateX(-50%);";
1718
- default:
1719
- return "bottom:20px;right:20px;";
1720
- }
1721
- }
1722
- function getTriggerHTML(cfg) {
1723
- return `
1724
- <button id="cf-trigger">
1725
- <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>
1726
- ${cfg.text}
1727
- </button>
1728
- `;
1729
- }
1730
- function getCheckflowLogo() {
1731
- return `<svg class="cf-logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4"/><path d="M5 19.5C5.5 18 6 15 6 12c0-.7.12-1.37.34-2"/><path d="M17.29 21.02c.12-.6.43-2.3.5-3.02"/><path d="M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4"/><path d="M8.65 22c.21-.66.45-1.32.57-2"/><path d="M14 13.12c0 2.38 0 6.38-1 8.88"/><path d="M2 16h.01"/><path d="M21.8 16c.2-2 .131-5.354 0-6"/><path d="M9 6.8a6 6 0 0 1 9 5.2v2"/></svg>`;
1732
- }
1733
- function getModalHTML(cfg, expanded) {
1734
- const modalClass = expanded ? "cf-modal cf-modal--expanded" : "cf-modal cf-modal--compact";
1735
- const formFields = `
1736
- <div class="cf-field">
1737
- <label>Nom <span>(obligatoire)</span></label>
1738
- <input id="cf-name" type="text" placeholder="Votre nom" />
1739
- </div>
1740
- <div class="cf-field">
1741
- <label>Email <span>(obligatoire)</span></label>
1742
- <input id="cf-email" type="email" placeholder="votre.email@exemple.com" />
1743
- </div>
1744
- <div class="cf-field">
1745
- <label>Description <span>(obligatoire)</span></label>
1746
- <textarea id="cf-desc" placeholder="Quel est le probl\xE8me ? Que vous attendiez-vous \xE0 voir ?"></textarea>
1747
- </div>
1748
- `;
1749
- if (expanded && screenshotDataUrl) {
1750
- const displayImg = annotatedImageUrl || screenshotDataUrl;
1751
- const annotLabel = annotationsData.length > 0 ? `Annoter (${annotationsData.length})` : "Annoter";
1752
- return `
1753
- <div class="${modalClass}">
1754
- <div class="cf-modal-header">
1755
- <h2>Envoyer le rapport</h2>
1756
- ${getCheckflowLogo()}
1757
- </div>
1758
- <div class="cf-modal-body">
1759
- <div class="cf-screenshot-panel">
1760
- <img id="cf-screenshot-img" src="${displayImg}" alt="Capture d'\xE9cran" />
1761
- <div class="cf-screenshot-tools">
1762
- <button class="cf-tool-highlight" id="cf-annotate-btn">${annotLabel}</button>
1763
- </div>
1764
- </div>
1765
- <div class="cf-form-panel">
1766
- ${formFields}
1767
- <button class="cf-remove-screenshot" id="cf-remove-screenshot">Supprimer la capture d'\xE9cran</button>
1768
- <button class="cf-submit-btn" id="cf-submit">Envoyer le rapport</button>
1769
- <button class="cf-cancel-btn" id="cf-cancel">Annuler</button>
1770
- </div>
1771
- </div>
1772
- <div class="cf-footer">Powered by <a href="https://checkflow.space" target="_blank" rel="noopener">Checkflow</a></div>
1773
- </div>
1774
- `;
1775
- }
1776
- return `
1777
- <div class="${modalClass}">
1778
- <div class="cf-modal-header">
1779
- <h2>Envoyer le rapport</h2>
1780
- ${getCheckflowLogo()}
1781
- </div>
1782
- <div class="cf-form-panel">
1783
- ${formFields}
1784
- <button class="cf-screenshot-action" id="cf-screenshot-btn">Ajouter une capture d'\xE9cran</button>
1785
- <button class="cf-submit-btn" id="cf-submit">Envoyer le rapport</button>
1786
- <button class="cf-cancel-btn" id="cf-cancel">Annuler</button>
1787
- </div>
1788
- <div class="cf-footer">Powered by <a href="https://checkflow.space" target="_blank" rel="noopener">Checkflow</a></div>
1789
- </div>
1790
- `;
1791
- }
1792
-
1793
183
  // src/index.ts
1794
184
  var SDK_VERSION = "1.1.0";
1795
185
  var DEFAULT_ENDPOINT = "https://api.checkflow.space/api/v1";
@@ -1820,7 +210,8 @@ function init(cfg) {
1820
210
  });
1821
211
  },
1822
212
  {
1823
- onScreenshot: () => captureScreenshot()
213
+ onScreenshot: () => captureScreenshot(),
214
+ user: config.user
1824
215
  }
1825
216
  );
1826
217
  }
@@ -1850,12 +241,22 @@ async function sendFeedback(data) {
1850
241
  if (data.annotations && data.annotations.length > 0) {
1851
242
  payload.annotations = data.annotations;
1852
243
  }
1853
- if (data.reporter_name) {
244
+ if (config.user?.name) {
245
+ payload.reporter_name = config.user.name;
246
+ } else if (data.reporter_name) {
1854
247
  payload.reporter_name = data.reporter_name;
1855
248
  }
1856
- if (data.reporter_email) {
249
+ if (config.user?.email) {
250
+ payload.reporter_email = config.user.email;
251
+ } else if (data.reporter_email) {
1857
252
  payload.reporter_email = data.reporter_email;
1858
253
  }
254
+ if (config.user?.id) {
255
+ payload.user_id = config.user.id;
256
+ }
257
+ if (config.user?.avatar) {
258
+ payload.user_avatar = config.user.avatar;
259
+ }
1859
260
  try {
1860
261
  const res = await fetch(`${config.endpoint}/sdk/feedback`, {
1861
262
  method: "POST",
@@ -1891,10 +292,45 @@ function showWidget() {
1891
292
  });
1892
293
  },
1893
294
  {
1894
- onScreenshot: () => captureScreenshot()
295
+ onScreenshot: () => captureScreenshot(),
296
+ user: config.user
1895
297
  }
1896
298
  );
1897
299
  }
300
+ function setUser(user) {
301
+ if (config) {
302
+ config.user = user;
303
+ }
304
+ }
305
+ function clearUser() {
306
+ if (config) {
307
+ config.user = void 0;
308
+ }
309
+ }
310
+ function openFeedbackModal() {
311
+ if (!config) return;
312
+ import("./widget-LD24NDDL.mjs").then(({ openFeedbackModal: openModal }) => {
313
+ openModal(
314
+ config.widget,
315
+ (data) => {
316
+ sendFeedback({
317
+ title: data.title,
318
+ description: data.description,
319
+ type: data.type,
320
+ priority: data.priority,
321
+ screenshot_data: data.screenshot,
322
+ annotations: data.annotations,
323
+ reporter_name: data.reporter_name,
324
+ reporter_email: data.reporter_email
325
+ });
326
+ },
327
+ {
328
+ onScreenshot: () => captureScreenshot(),
329
+ user: config.user
330
+ }
331
+ );
332
+ });
333
+ }
1898
334
  function hideWidget() {
1899
335
  unmountWidget();
1900
336
  }
@@ -1907,9 +343,12 @@ function destroy() {
1907
343
  }
1908
344
  export {
1909
345
  captureScreenshot,
346
+ clearUser,
1910
347
  destroy,
1911
348
  hideWidget,
1912
349
  init,
350
+ openFeedbackModal,
1913
351
  sendFeedback,
352
+ setUser,
1914
353
  showWidget
1915
354
  };