pushfeedback 0.1.66 → 0.1.68

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.
@@ -82,8 +82,27 @@ export class FeedbackModal {
82
82
  this.showScreenshotTopBar = false;
83
83
  this.hasSelectedElement = false;
84
84
  this.encodedScreenshot = null;
85
- this.originalElement = null;
86
- this.selectedElementBounds = null;
85
+ // Remove highlight from ALL selected elements
86
+ document.querySelectorAll('.feedback-modal-element-selected').forEach(el => {
87
+ el.classList.remove('feedback-modal-element-selected');
88
+ });
89
+ // Reset canvas editor states
90
+ this.takingScreenshot = false;
91
+ this.showPreviewModal = false;
92
+ this.showCanvasEditor = false;
93
+ this.annotations = [];
94
+ this.currentAnnotation = null;
95
+ this.isDrawing = false;
96
+ this.canvasRef = null;
97
+ this.canvasContext = null;
98
+ this.originalImageData = null;
99
+ // Reset resizing states
100
+ this.isResizing = false;
101
+ this.resizingAnnotation = null;
102
+ this.resizeStartSize = 16;
103
+ this.hoveredAnnotation = null;
104
+ this.resizeHandle = false;
105
+ // Reset form states
87
106
  this.formSuccess = false;
88
107
  this.formError = false;
89
108
  this.formErrorStatus = 500;
@@ -92,108 +111,608 @@ export class FeedbackModal {
92
111
  this.resetOverflow();
93
112
  }, 200);
94
113
  };
95
- this.openScreenShot = () => {
96
- this.hasSelectedElement = false;
97
- this.showModal = false;
98
- this.showScreenshotMode = true;
99
- this.showScreenshotTopBar = true;
100
- this.encodedScreenshot = null;
101
- this.originalElement = null;
102
- this.selectedElementBounds = null;
103
- if (window.innerWidth > document.documentElement.clientWidth) {
104
- document.documentElement.classList.add('feedback-modal-screenshot-open--scroll');
114
+ this.openScreenShot = async () => {
115
+ // Show loading state immediately
116
+ this.takingScreenshot = true;
117
+ try {
118
+ // Capture viewport screenshot immediately
119
+ const dataUrl = await this.captureViewportScreenshot();
120
+ this.encodedScreenshot = dataUrl;
121
+ this.originalImageData = dataUrl;
122
+ // Reset loading state
123
+ this.takingScreenshot = false;
124
+ // Skip preview modal and go directly to canvas editor
125
+ this.showModal = false;
126
+ this.showCanvasEditor = true;
127
+ // Initialize canvas after a short delay to ensure DOM is ready
128
+ setTimeout(() => {
129
+ this.initializeCanvas();
130
+ }, 100);
131
+ }
132
+ catch (error) {
133
+ console.error('Failed to capture screenshot:', error);
134
+ // Reset loading state on error
135
+ this.takingScreenshot = false;
136
+ // Show modal anyway
137
+ this.showModal = true;
105
138
  }
106
- const scrollY = window.scrollY;
107
- document.documentElement.style.top = `-${scrollY}px`;
108
- window.scrollTo(0, parseInt(document.documentElement.style.top || '0') * -1);
109
- document.documentElement.classList.add('feedback-modal-screenshot-open');
110
139
  };
111
- this.closeScreenShot = () => {
140
+ this.openCanvasEditor = (event) => {
141
+ if (event) {
142
+ event.stopPropagation();
143
+ }
112
144
  this.showModal = false;
113
- this.showScreenshotMode = false;
114
- this.showScreenshotTopBar = false;
115
- this.hasSelectedElement = false;
116
- this.encodedScreenshot = null;
117
- this.originalElement = null;
118
- this.selectedElementBounds = null;
119
- this.resetOverflow();
145
+ this.showCanvasEditor = true;
146
+ // Initialize canvas after a short delay to ensure DOM is ready
147
+ setTimeout(() => {
148
+ this.initializeCanvas();
149
+ }, 100);
120
150
  };
121
- this.handleMouseOverScreenShot = (event) => {
122
- event.preventDefault();
123
- if (this.hasSelectedElement)
151
+ this.closeCanvasEditor = () => {
152
+ this.showCanvasEditor = false;
153
+ this.showModal = true;
154
+ };
155
+ this.saveAnnotations = () => {
156
+ if (this.canvasRef) {
157
+ // Create final image with annotations
158
+ const finalDataUrl = this.canvasRef.toDataURL('image/png');
159
+ this.encodedScreenshot = finalDataUrl;
160
+ }
161
+ this.showCanvasEditor = false;
162
+ this.showModal = true;
163
+ };
164
+ this.initializeCanvas = () => {
165
+ if (!this.canvasRef || !this.originalImageData)
124
166
  return;
125
- const borderOffset = 2;
126
- this.screenshotModal.style.display = 'none';
127
- const elementUnder = document.elementFromPoint(event.clientX, event.clientY);
128
- const rect = elementUnder.getBoundingClientRect();
129
- this.screenshotModal.style.display = '';
130
- // Get the bounding box of the element selected
131
- this.elementSelected.style.position = 'absolute';
132
- this.elementSelected.style.left = `${rect.left}px`;
133
- this.elementSelected.style.top = `${rect.top}px`;
134
- this.elementSelected.style.width = `${rect.width}px`;
135
- this.elementSelected.style.height = `${rect.height}px`;
136
- this.elementSelected.classList.add('feedback-modal-element-hover');
137
- // Set the background color of nonselected areas
138
- // Top
139
- this.topSide.style.position = 'absolute';
140
- this.topSide.style.left = `${rect.left}px`;
141
- this.topSide.style.top = '0px';
142
- this.topSide.style.width = `${rect.width + borderOffset}px`;
143
- this.topSide.style.height = `${rect.top}px`;
144
- this.topSide.style.backgroundColor = 'rgba(0, 0, 0, 0.4)';
145
- // Left
146
- this.leftSide.style.position = 'absolute';
147
- this.leftSide.style.left = '0px';
148
- this.leftSide.style.top = '0px';
149
- this.leftSide.style.width = `${rect.left}px`;
150
- this.leftSide.style.height = '100vh';
151
- this.leftSide.style.backgroundColor = 'rgba(0, 0, 0, 0.4)';
152
- // Bottom
153
- this.bottomSide.style.position = 'absolute';
154
- this.bottomSide.style.left = `${rect.left}px`;
155
- this.bottomSide.style.top = `${rect.bottom + borderOffset}px`;
156
- this.bottomSide.style.width = `${rect.width + borderOffset}px`;
157
- this.bottomSide.style.height = '100vh';
158
- this.bottomSide.style.backgroundColor = 'rgba(0, 0, 0, 0.4)';
159
- // Right
160
- this.rightSide.style.position = 'absolute';
161
- this.rightSide.style.left = `${rect.right + borderOffset}px`;
162
- this.rightSide.style.top = '0px';
163
- this.rightSide.style.width = '100%';
164
- this.rightSide.style.height = '100vh';
165
- this.rightSide.style.backgroundColor = 'rgba(0, 0, 0, 0.4)';
166
- // Restore the visibility of the screenshot-modal
167
- this.screenshotModal.style.backgroundColor = 'transparent';
167
+ this.canvasContext = this.canvasRef.getContext('2d');
168
+ const img = new Image();
169
+ img.onload = () => {
170
+ // Set canvas to original image dimensions
171
+ this.canvasRef.width = img.width;
172
+ this.canvasRef.height = img.height;
173
+ // Get available container dimensions
174
+ const containerWidth = this.canvasRef.parentElement.clientWidth - 32; // Account for reduced padding (16px * 2)
175
+ const containerHeight = this.canvasRef.parentElement.clientHeight - 32;
176
+ // Calculate scale factors for both dimensions
177
+ const scaleX = containerWidth / img.width;
178
+ const scaleY = containerHeight / img.height;
179
+ // Use the smaller scale to ensure complete image fits
180
+ const scale = Math.min(scaleX, scaleY, 1); // Never scale up, only down
181
+ // Calculate final display dimensions
182
+ const displayWidth = img.width * scale;
183
+ const displayHeight = img.height * scale;
184
+ // Set CSS size for display (this scales the canvas visually)
185
+ this.canvasRef.style.width = `${displayWidth}px`;
186
+ this.canvasRef.style.height = `${displayHeight}px`;
187
+ console.log('Canvas initialized with complete image fit:', {
188
+ originalWidth: img.width,
189
+ originalHeight: img.height,
190
+ displayWidth,
191
+ displayHeight,
192
+ scale,
193
+ scaleX,
194
+ scaleY,
195
+ containerWidth,
196
+ containerHeight,
197
+ usingScale: scale === scaleX ? 'width-limited' : 'height-limited'
198
+ });
199
+ // Draw the original image at full resolution
200
+ this.canvasContext.drawImage(img, 0, 0);
201
+ // Redraw existing annotations
202
+ this.redrawAnnotations();
203
+ };
204
+ img.src = this.originalImageData;
168
205
  };
169
- this.handleMouseClickedSelectedElement = async (event) => {
170
- event.preventDefault();
171
- if (!this.elementSelected) {
206
+ this.redrawAnnotations = () => {
207
+ if (!this.canvasContext)
208
+ return;
209
+ // Clear and redraw background image
210
+ const img = new Image();
211
+ img.onload = () => {
212
+ this.canvasContext.clearRect(0, 0, this.canvasRef.width, this.canvasRef.height);
213
+ this.canvasContext.drawImage(img, 0, 0);
214
+ // Draw all annotations
215
+ this.annotations.forEach(annotation => {
216
+ this.drawAnnotation(annotation);
217
+ });
218
+ };
219
+ img.src = this.originalImageData;
220
+ };
221
+ this.drawAnnotation = (annotation) => {
222
+ if (!this.canvasContext)
172
223
  return;
224
+ this.canvasContext.strokeStyle = annotation.color;
225
+ this.canvasContext.lineWidth = annotation.lineWidth;
226
+ this.canvasContext.lineCap = 'round';
227
+ this.canvasContext.lineJoin = 'round';
228
+ switch (annotation.type) {
229
+ case 'rectangle':
230
+ this.canvasContext.strokeRect(annotation.startX, annotation.startY, annotation.width, annotation.height);
231
+ // Rectangle resize handles disabled for now
232
+ break;
233
+ case 'line':
234
+ this.canvasContext.beginPath();
235
+ this.canvasContext.moveTo(annotation.startX, annotation.startY);
236
+ this.canvasContext.lineTo(annotation.endX, annotation.endY);
237
+ this.canvasContext.stroke();
238
+ // Draw resize handles if this annotation is hovered
239
+ if (this.hoveredAnnotation === annotation) {
240
+ this.drawLineResizeHandles(annotation);
241
+ }
242
+ break;
243
+ case 'arrow':
244
+ this.drawArrow(annotation.startX, annotation.startY, annotation.endX, annotation.endY);
245
+ // Draw resize handles if this annotation is hovered
246
+ if (this.hoveredAnnotation === annotation) {
247
+ this.drawLineResizeHandles(annotation); // Same as line
248
+ }
249
+ break;
250
+ case 'text':
251
+ const fontSize = annotation.fontSize || 16;
252
+ this.canvasContext.fillStyle = annotation.color;
253
+ this.canvasContext.font = `${fontSize}px Arial`;
254
+ this.canvasContext.fillText(annotation.text, annotation.x, annotation.y);
255
+ // Draw resize handle if this annotation is hovered
256
+ if (this.hoveredAnnotation === annotation) {
257
+ this.drawTextResizeHandle(annotation);
258
+ }
259
+ break;
173
260
  }
174
- this.hasSelectedElement = true;
175
- this.elementSelected.classList.add('feedback-modal-element-selected');
176
- // Store the original element that was under the mouse
177
- this.screenshotModal.style.display = 'none';
178
- this.originalElement = document.elementFromPoint(event.clientX, event.clientY);
179
- this.selectedElementBounds = this.originalElement.getBoundingClientRect();
180
- this.screenshotModal.style.display = '';
181
- // Hide the screenshot interface
182
- this.showScreenshotTopBar = false;
183
- this.showModal = false;
184
- try {
185
- const dataUrl = await this.captureScreenshot();
186
- console.log('Screenshot captured');
187
- this.encodedScreenshot = dataUrl;
261
+ };
262
+ // Draw resize handle for text annotation
263
+ this.drawTextResizeHandle = (annotation) => {
264
+ if (!this.canvasContext || annotation.type !== 'text')
265
+ return;
266
+ const fontSize = annotation.fontSize || 16;
267
+ const textWidth = this.getTextWidth(annotation.text, fontSize);
268
+ const handleSize = 8;
269
+ const handleX = annotation.x + textWidth;
270
+ const handleY = annotation.y;
271
+ // Draw resize handle (small square) - using widget primary color
272
+ this.canvasContext.fillStyle = '#0070F4'; // var(--feedback-primary-color)
273
+ this.canvasContext.strokeStyle = '#ffffff';
274
+ this.canvasContext.lineWidth = 2;
275
+ this.canvasContext.fillRect(handleX - handleSize / 2, handleY - handleSize / 2, handleSize, handleSize);
276
+ this.canvasContext.strokeRect(handleX - handleSize / 2, handleY - handleSize / 2, handleSize, handleSize);
277
+ };
278
+ this.drawArrow = (fromX, fromY, toX, toY) => {
279
+ const headlen = 15; // Arrow head length
280
+ const angle = Math.atan2(toY - fromY, toX - fromX);
281
+ // Draw line
282
+ this.canvasContext.beginPath();
283
+ this.canvasContext.moveTo(fromX, fromY);
284
+ this.canvasContext.lineTo(toX, toY);
285
+ this.canvasContext.stroke();
286
+ // Draw arrow head
287
+ this.canvasContext.beginPath();
288
+ this.canvasContext.moveTo(toX, toY);
289
+ this.canvasContext.lineTo(toX - headlen * Math.cos(angle - Math.PI / 6), toY - headlen * Math.sin(angle - Math.PI / 6));
290
+ this.canvasContext.moveTo(toX, toY);
291
+ this.canvasContext.lineTo(toX - headlen * Math.cos(angle + Math.PI / 6), toY - headlen * Math.sin(angle + Math.PI / 6));
292
+ this.canvasContext.stroke();
293
+ };
294
+ this.undoLastAnnotation = () => {
295
+ this.annotations = this.annotations.slice(0, -1);
296
+ this.redrawAnnotations();
297
+ };
298
+ // Handle color slot editing
299
+ this.handleColorSlotClick = (colorIndex) => {
300
+ if (this.editingColorIndex === colorIndex) {
301
+ // If already editing this slot, just select the color
302
+ this.canvasDrawingColor = this.defaultColors[colorIndex];
303
+ this.showColorPicker = false;
304
+ this.editingColorIndex = -1;
188
305
  }
189
- catch (error) {
190
- console.error('Failed to capture screenshot:', error);
191
- this.hasSelectedElement = false;
306
+ else {
307
+ // Start editing this color slot
308
+ this.editingColorIndex = colorIndex;
309
+ this.showColorPicker = true;
310
+ this.canvasDrawingColor = this.defaultColors[colorIndex];
192
311
  }
193
- finally {
194
- // Show the modal again
195
- this.showModal = true;
312
+ };
313
+ // Update color in slot
314
+ this.updateColorSlot = (newColor) => {
315
+ if (this.editingColorIndex >= 0 && this.editingColorIndex < this.defaultColors.length) {
316
+ this.defaultColors[this.editingColorIndex] = newColor;
317
+ this.canvasDrawingColor = newColor;
318
+ this.showColorPicker = false;
319
+ this.editingColorIndex = -1;
320
+ // Force reactivity
321
+ this.defaultColors = [...this.defaultColors];
322
+ }
323
+ };
324
+ // Handle color picker input without closing
325
+ this.handleColorPickerInput = (event) => {
326
+ event.stopPropagation();
327
+ const newColor = event.target.value;
328
+ if (this.editingColorIndex >= 0 && this.editingColorIndex < this.defaultColors.length) {
329
+ this.defaultColors[this.editingColorIndex] = newColor;
330
+ this.canvasDrawingColor = newColor;
331
+ // Force reactivity
332
+ this.defaultColors = [...this.defaultColors];
333
+ }
334
+ };
335
+ // Handle color picker click to prevent closing
336
+ this.handleColorPickerClick = (event) => {
337
+ event.stopPropagation();
338
+ };
339
+ // Close color picker
340
+ this.closeColorPicker = () => {
341
+ this.showColorPicker = false;
342
+ this.editingColorIndex = -1;
343
+ };
344
+ // Check if point is in resize handle for any annotation type
345
+ this.isPointInResizeHandle = (x, y, annotation) => {
346
+ const handleSize = 8;
347
+ switch (annotation.type) {
348
+ case 'text':
349
+ const textWidth = this.getTextWidth(annotation.text, annotation.fontSize || 16);
350
+ const handleX = annotation.x + textWidth;
351
+ const handleY = annotation.y;
352
+ return x >= handleX - handleSize / 2 && x <= handleX + handleSize / 2 &&
353
+ y >= handleY - handleSize / 2 && y <= handleY + handleSize / 2;
354
+ case 'rectangle':
355
+ // Rectangle resizing disabled for now
356
+ return false;
357
+ case 'line':
358
+ case 'arrow':
359
+ // Check both endpoint handles
360
+ const lineHandles = [
361
+ { x: annotation.startX, y: annotation.startY, point: 'start' },
362
+ { x: annotation.endX, y: annotation.endY, point: 'end' }
363
+ ];
364
+ for (const handle of lineHandles) {
365
+ if (x >= handle.x - handleSize / 2 && x <= handle.x + handleSize / 2 &&
366
+ y >= handle.y - handleSize / 2 && y <= handle.y + handleSize / 2) {
367
+ return handle.point; // Return which endpoint was clicked
368
+ }
369
+ }
370
+ return false;
371
+ default:
372
+ return false;
373
+ }
374
+ };
375
+ // Get text width for resize handle positioning
376
+ this.getTextWidth = (text, fontSize) => {
377
+ // Approximate text width calculation
378
+ return text.length * fontSize * 0.6;
379
+ };
380
+ // Start text resize
381
+ this.startTextResize = (annotation, startPos) => {
382
+ this.isResizing = true;
383
+ this.resizingAnnotation = annotation;
384
+ this.resizeStartSize = annotation.fontSize || 16;
385
+ this.dragStartPos = startPos;
386
+ };
387
+ // Handle text resize
388
+ this.handleTextResize = (currentPos) => {
389
+ if (!this.resizingAnnotation || !this.dragStartPos)
390
+ return;
391
+ const deltaX = currentPos.x - this.dragStartPos.x;
392
+ const deltaY = currentPos.y - this.dragStartPos.y;
393
+ const avgDelta = (deltaX + deltaY) / 2;
394
+ // Calculate new font size (minimum 8px, maximum 72px)
395
+ const newSize = Math.max(8, Math.min(72, this.resizeStartSize + avgDelta * 0.5));
396
+ // Update annotation font size
397
+ const index = this.annotations.findIndex(a => a === this.resizingAnnotation);
398
+ if (index !== -1) {
399
+ this.annotations[index] = Object.assign(Object.assign({}, this.resizingAnnotation), { fontSize: Math.round(newSize) });
400
+ this.resizingAnnotation = this.annotations[index];
401
+ }
402
+ this.redrawAnnotations();
403
+ };
404
+ // Start resize for any annotation type
405
+ this.startResize = (annotation, handle, startPos) => {
406
+ this.isResizing = true;
407
+ this.resizingAnnotation = annotation;
408
+ this.resizeHandle = handle;
409
+ this.dragStartPos = startPos;
410
+ // Store original values for different annotation types
411
+ if (annotation.type === 'text') {
412
+ this.resizeStartSize = annotation.fontSize || 16;
413
+ }
414
+ };
415
+ // Enhanced mouse down handler with resize detection for all annotation types
416
+ this.handleCanvasMouseDown = (event) => {
417
+ if (!this.canvasRef)
418
+ return;
419
+ // Close color picker if open
420
+ if (this.showColorPicker) {
421
+ this.closeColorPicker();
422
+ }
423
+ const coords = this.getCanvasCoordinates(event);
424
+ // Check if clicking on existing annotation first
425
+ const found = this.findAnnotationAt(coords.x, coords.y);
426
+ if (found) {
427
+ // Check if clicking on resize handle for any annotation type
428
+ const handle = this.isPointInResizeHandle(coords.x, coords.y, found.annotation);
429
+ if (handle) {
430
+ this.startResize(found.annotation, handle, coords);
431
+ this.canvasRef.style.cursor = 'nw-resize';
432
+ return;
433
+ }
434
+ // Start dragging existing annotation
435
+ if (!this.isDrawing) {
436
+ this.isDragging = true;
437
+ this.draggedAnnotation = found.annotation;
438
+ this.dragStartPos = coords;
439
+ this.canvasRef.style.cursor = 'grabbing';
440
+ return;
441
+ }
442
+ }
443
+ // Original drawing logic
444
+ this.isDrawing = true;
445
+ if (this.canvasDrawingTool === 'text') {
446
+ const text = prompt('Enter text:');
447
+ if (text) {
448
+ const annotation = {
449
+ type: 'text',
450
+ x: coords.x,
451
+ y: coords.y,
452
+ text,
453
+ color: this.canvasDrawingColor,
454
+ fontSize: 16
455
+ };
456
+ this.annotations = [...this.annotations, annotation];
457
+ this.redrawAnnotations();
458
+ }
459
+ this.isDrawing = false;
460
+ }
461
+ else {
462
+ this.currentAnnotation = {
463
+ type: this.canvasDrawingTool,
464
+ startX: coords.x,
465
+ startY: coords.y,
466
+ color: this.canvasDrawingColor,
467
+ lineWidth: this.canvasLineWidth
468
+ };
469
+ }
470
+ };
471
+ this.handleCanvasMouseMove = (event) => {
472
+ if (!this.canvasRef)
473
+ return;
474
+ const coords = this.getCanvasCoordinates(event);
475
+ // Handle resizing for any annotation type
476
+ if (this.isResizing && this.resizingAnnotation) {
477
+ this.handleResize(coords);
478
+ return;
479
+ }
480
+ // Handle dragging existing annotation
481
+ if (this.isDragging && this.draggedAnnotation && this.dragStartPos) {
482
+ const deltaX = coords.x - this.dragStartPos.x;
483
+ const deltaY = coords.y - this.dragStartPos.y;
484
+ // Update annotation position
485
+ const updatedAnnotation = Object.assign({}, this.draggedAnnotation);
486
+ switch (updatedAnnotation.type) {
487
+ case 'rectangle':
488
+ updatedAnnotation.startX += deltaX;
489
+ updatedAnnotation.startY += deltaY;
490
+ break;
491
+ case 'line':
492
+ case 'arrow':
493
+ updatedAnnotation.startX += deltaX;
494
+ updatedAnnotation.startY += deltaY;
495
+ updatedAnnotation.endX += deltaX;
496
+ updatedAnnotation.endY += deltaY;
497
+ break;
498
+ case 'text':
499
+ updatedAnnotation.x += deltaX;
500
+ updatedAnnotation.y += deltaY;
501
+ break;
502
+ }
503
+ // Update annotation in array
504
+ const index = this.annotations.findIndex(a => a === this.draggedAnnotation);
505
+ if (index !== -1) {
506
+ this.annotations[index] = updatedAnnotation;
507
+ this.draggedAnnotation = updatedAnnotation;
508
+ }
509
+ this.dragStartPos = coords;
510
+ this.redrawAnnotations();
511
+ return;
512
+ }
513
+ // Handle drawing new annotation
514
+ if (this.isDrawing && this.currentAnnotation) {
515
+ if (this.canvasDrawingTool === 'rectangle') {
516
+ this.currentAnnotation.width = coords.x - this.currentAnnotation.startX;
517
+ this.currentAnnotation.height = coords.y - this.currentAnnotation.startY;
518
+ }
519
+ else {
520
+ this.currentAnnotation.endX = coords.x;
521
+ this.currentAnnotation.endY = coords.y;
522
+ }
523
+ this.redrawAnnotations();
524
+ this.drawAnnotation(this.currentAnnotation);
525
+ return;
526
+ }
527
+ // Handle hover states and cursor changes
528
+ const found = this.findAnnotationAt(coords.x, coords.y);
529
+ if (found) {
530
+ // Check if hovering over resize handle for any annotation type
531
+ const handle = this.isPointInResizeHandle(coords.x, coords.y, found.annotation);
532
+ if (handle) {
533
+ this.canvasRef.style.cursor = 'nw-resize';
534
+ this.hoveredAnnotation = found.annotation;
535
+ this.redrawAnnotations();
536
+ return;
537
+ }
538
+ // Regular hover over annotation
539
+ this.canvasRef.style.cursor = 'grab';
540
+ if (this.hoveredAnnotation !== found.annotation) {
541
+ this.hoveredAnnotation = found.annotation;
542
+ this.redrawAnnotations();
543
+ }
544
+ }
545
+ else {
546
+ // No annotation under cursor
547
+ this.canvasRef.style.cursor = 'crosshair';
548
+ if (this.hoveredAnnotation) {
549
+ this.hoveredAnnotation = null;
550
+ this.redrawAnnotations();
551
+ }
552
+ }
553
+ };
554
+ this.handleCanvasMouseUp = () => {
555
+ // Handle end of text resizing
556
+ if (this.isResizing) {
557
+ this.isResizing = false;
558
+ this.resizingAnnotation = null;
559
+ this.dragStartPos = null;
560
+ this.resizeHandle = false;
561
+ if (this.canvasRef) {
562
+ this.canvasRef.style.cursor = 'crosshair';
563
+ }
564
+ return;
565
+ }
566
+ // Handle end of dragging
567
+ if (this.isDragging) {
568
+ this.isDragging = false;
569
+ this.draggedAnnotation = null;
570
+ this.dragStartPos = null;
571
+ if (this.canvasRef) {
572
+ this.canvasRef.style.cursor = 'crosshair';
573
+ }
574
+ return;
575
+ }
576
+ // Handle end of drawing
577
+ if (!this.isDrawing || !this.currentAnnotation)
578
+ return;
579
+ this.isDrawing = false;
580
+ this.annotations = [...this.annotations, this.currentAnnotation];
581
+ this.currentAnnotation = null;
582
+ this.redrawAnnotations();
583
+ };
584
+ // Draw resize handles for rectangle annotation
585
+ this.drawRectangleResizeHandles = (annotation) => {
586
+ if (!this.canvasContext || annotation.type !== 'rectangle')
587
+ return;
588
+ const handleSize = 8;
589
+ const left = annotation.startX;
590
+ const top = annotation.startY;
591
+ const right = annotation.startX + annotation.width;
592
+ const bottom = annotation.startY + annotation.height;
593
+ // Define handle positions (4 corners)
594
+ const handles = [
595
+ { x: left, y: top },
596
+ { x: right, y: top },
597
+ { x: right, y: bottom },
598
+ { x: left, y: bottom } // Bottom-left
599
+ ];
600
+ // Draw each handle
601
+ this.canvasContext.fillStyle = '#0070F4'; // Primary color
602
+ this.canvasContext.strokeStyle = '#ffffff';
603
+ this.canvasContext.lineWidth = 2;
604
+ handles.forEach(handle => {
605
+ this.canvasContext.fillRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
606
+ this.canvasContext.strokeRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
607
+ });
608
+ };
609
+ // Draw resize handles for line/arrow annotation
610
+ this.drawLineResizeHandles = (annotation) => {
611
+ if (!this.canvasContext || (annotation.type !== 'line' && annotation.type !== 'arrow'))
612
+ return;
613
+ const handleSize = 8;
614
+ // Define handle positions (2 endpoints)
615
+ const handles = [
616
+ { x: annotation.startX, y: annotation.startY },
617
+ { x: annotation.endX, y: annotation.endY } // End point
618
+ ];
619
+ // Draw each handle
620
+ this.canvasContext.fillStyle = '#0070F4'; // Primary color
621
+ this.canvasContext.strokeStyle = '#ffffff';
622
+ this.canvasContext.lineWidth = 2;
623
+ handles.forEach(handle => {
624
+ this.canvasContext.fillRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
625
+ this.canvasContext.strokeRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
626
+ });
627
+ };
628
+ // Convert screen coordinates to canvas coordinates
629
+ this.getCanvasCoordinates = (event) => {
630
+ if (!this.canvasRef)
631
+ return { x: 0, y: 0 };
632
+ const rect = this.canvasRef.getBoundingClientRect();
633
+ // Calculate the scale factor between display size and actual canvas size
634
+ const scaleX = this.canvasRef.width / rect.width;
635
+ const scaleY = this.canvasRef.height / rect.height;
636
+ const x = (event.clientX - rect.left) * scaleX;
637
+ const y = (event.clientY - rect.top) * scaleY;
638
+ return { x, y };
639
+ };
640
+ // Find annotation under mouse cursor
641
+ this.findAnnotationAt = (x, y) => {
642
+ // Check in reverse order (top to bottom)
643
+ for (let i = this.annotations.length - 1; i >= 0; i--) {
644
+ const annotation = this.annotations[i];
645
+ if (this.isPointInAnnotation(x, y, annotation)) {
646
+ return { annotation, index: i };
647
+ }
196
648
  }
649
+ return null;
650
+ };
651
+ // Check if point is within annotation bounds
652
+ this.isPointInAnnotation = (x, y, annotation) => {
653
+ const tolerance = 10; // Click tolerance
654
+ switch (annotation.type) {
655
+ case 'rectangle':
656
+ const left = Math.min(annotation.startX, annotation.startX + annotation.width);
657
+ const right = Math.max(annotation.startX, annotation.startX + annotation.width);
658
+ const top = Math.min(annotation.startY, annotation.startY + annotation.height);
659
+ const bottom = Math.max(annotation.startY, annotation.startY + annotation.height);
660
+ return x >= left - tolerance && x <= right + tolerance &&
661
+ y >= top - tolerance && y <= bottom + tolerance;
662
+ case 'line':
663
+ case 'arrow':
664
+ // Distance from point to line
665
+ const A = annotation.endY - annotation.startY;
666
+ const B = annotation.startX - annotation.endX;
667
+ const C = annotation.endX * annotation.startY - annotation.startX * annotation.endY;
668
+ const distance = Math.abs(A * x + B * y + C) / Math.sqrt(A * A + B * B);
669
+ return distance <= tolerance;
670
+ case 'text':
671
+ // Simple bounding box for text
672
+ return x >= annotation.x - tolerance && x <= annotation.x + 100 &&
673
+ y >= annotation.y - 20 && y <= annotation.y + tolerance;
674
+ default:
675
+ return false;
676
+ }
677
+ };
678
+ // Handle resize for different annotation types
679
+ this.handleResize = (currentPos) => {
680
+ if (!this.resizingAnnotation || !this.dragStartPos)
681
+ return;
682
+ const annotation = this.resizingAnnotation;
683
+ const index = this.annotations.findIndex(a => a === annotation);
684
+ if (index === -1)
685
+ return;
686
+ let updatedAnnotation = Object.assign({}, annotation);
687
+ switch (annotation.type) {
688
+ case 'text':
689
+ // Text resize logic (existing)
690
+ const deltaX = currentPos.x - this.dragStartPos.x;
691
+ const deltaY = currentPos.y - this.dragStartPos.y;
692
+ const avgDelta = (deltaX + deltaY) / 2;
693
+ const newSize = Math.max(8, Math.min(72, this.resizeStartSize + avgDelta * 0.5));
694
+ updatedAnnotation.fontSize = Math.round(newSize);
695
+ break;
696
+ case 'rectangle':
697
+ // Rectangle resizing disabled for now
698
+ return;
699
+ case 'line':
700
+ case 'arrow':
701
+ // Line/arrow resize logic - move endpoints
702
+ if (this.resizeHandle === 'start') {
703
+ updatedAnnotation.startX = currentPos.x;
704
+ updatedAnnotation.startY = currentPos.y;
705
+ }
706
+ else if (this.resizeHandle === 'end') {
707
+ updatedAnnotation.endX = currentPos.x;
708
+ updatedAnnotation.endY = currentPos.y;
709
+ }
710
+ break;
711
+ }
712
+ // Update annotation in array
713
+ this.annotations[index] = updatedAnnotation;
714
+ this.resizingAnnotation = updatedAnnotation;
715
+ this.redrawAnnotations();
197
716
  };
198
717
  this.sending = false;
199
718
  this.formMessage = '';
@@ -208,6 +727,26 @@ export class FeedbackModal {
208
727
  this.selectedRating = -1;
209
728
  this.overlayVisible = false;
210
729
  this.isAnimating = false;
730
+ this.takingScreenshot = false;
731
+ this.showPreviewModal = false;
732
+ this.showCanvasEditor = false;
733
+ this.canvasDrawingTool = 'rectangle';
734
+ this.canvasDrawingColor = '#ff0000';
735
+ this.canvasLineWidth = 3;
736
+ this.isDrawing = false;
737
+ this.annotations = [];
738
+ this.currentAnnotation = null;
739
+ this.isDragging = false;
740
+ this.draggedAnnotation = null;
741
+ this.dragStartPos = null;
742
+ this.showColorPicker = false;
743
+ this.editingColorIndex = -1;
744
+ this.isResizing = false;
745
+ this.resizingAnnotation = null;
746
+ this.resizeStartSize = 16;
747
+ this.hoveredAnnotation = null;
748
+ this.resizeHandle = false;
749
+ this.defaultColors = ['#ff0000', '#00ff00', '#0000ff', '#000000'];
211
750
  this.customFont = false;
212
751
  this.emailAddress = '';
213
752
  this.hideEmail = false;
@@ -238,10 +777,14 @@ export class FeedbackModal {
238
777
  this.ratingPlaceholder = 'Was this page helpful?';
239
778
  this.ratingStarsPlaceholder = 'How would you rate this page?';
240
779
  this.sendButtonText = 'Send';
241
- this.screenshotButtonText = 'Add a screenshot';
242
780
  this.screenshotAttachedText = 'Screenshot attached';
781
+ this.screenshotButtonText = 'Add a screenshot';
782
+ this.screenshotTakingText = 'Taking screenshot...';
243
783
  this.screenshotTopbarText = 'Select an element on this page';
244
784
  this.successMessage = '';
785
+ this.canvasEditorTitle = 'Edit screenshot';
786
+ this.canvasEditorCancelText = 'Cancel';
787
+ this.canvasEditorSaveText = 'Save';
245
788
  }
246
789
  componentWillLoad() {
247
790
  if (this.fetchData)
@@ -265,15 +808,10 @@ export class FeedbackModal {
265
808
  }
266
809
  }
267
810
  resetOverflow() {
811
+ // Just clean up any stray classes, don't add/remove during screenshot
268
812
  document.documentElement.classList.remove('feedback-modal-screenshot-open');
269
813
  document.documentElement.classList.remove('feedback-modal-screenshot-open--scroll');
270
- document.documentElement.classList.add('feedback-modal-screenshot-closing');
271
- // Only restore scroll position if we previously modified it
272
- if (document.documentElement.style.top) {
273
- window.scrollTo(0, parseInt(document.documentElement.style.top || '0') * -1);
274
- document.documentElement.style.top = '';
275
- }
276
- window.addEventListener('scroll', this.onScrollDebounced);
814
+ document.documentElement.classList.remove('feedback-modal-screenshot-closing');
277
815
  }
278
816
  handleMessageInput(event) {
279
817
  this.formMessage = event.target.value;
@@ -281,31 +819,48 @@ export class FeedbackModal {
281
819
  handleEmailInput(event) {
282
820
  this.formEmail = event.target.value;
283
821
  }
284
- captureScreenshot() {
822
+ captureViewportScreenshot() {
285
823
  return new Promise((resolve, reject) => {
286
824
  requestAnimationFrame(() => {
287
- html2canvas(document.body, {
288
- x: window.scrollX,
289
- y: window.scrollY,
290
- width: window.innerWidth,
291
- height: window.innerHeight,
292
- allowTaint: true,
825
+ // Get viewport dimensions and scroll position
826
+ const viewportWidth = window.innerWidth;
827
+ const viewportHeight = window.innerHeight;
828
+ const scrollX = window.scrollX || window.pageXOffset || 0;
829
+ const scrollY = window.scrollY || window.pageYOffset || 0;
830
+ // Capture exactly what the user sees in their viewport
831
+ html2canvas(document.documentElement, {
832
+ x: scrollX,
833
+ y: scrollY,
834
+ width: viewportWidth,
835
+ height: viewportHeight,
836
+ scrollX: 0,
837
+ scrollY: 0,
838
+ allowTaint: false,
293
839
  useCORS: true,
294
840
  scale: 1,
841
+ backgroundColor: '#ffffff',
842
+ logging: false,
843
+ foreignObjectRendering: false,
844
+ imageTimeout: 15000,
845
+ windowWidth: viewportWidth,
846
+ windowHeight: viewportHeight,
847
+ removeContainer: true,
848
+ ignoreElements: (element) => {
849
+ // Ignore all feedback modal elements
850
+ return element.closest('feedback-modal') !== null ||
851
+ element.classList.contains('feedback-overlay') ||
852
+ element.classList.contains('feedback-modal-screenshot-header') ||
853
+ element.tagName === 'FEEDBACK-MODAL' ||
854
+ element.tagName === 'FEEDBACK-BUTTON';
855
+ }
295
856
  })
296
857
  .then((canvas) => {
297
- const context = canvas.getContext('2d');
298
- if (context && this.selectedElementBounds) {
299
- // Use the same color as HTML highlight
300
- context.strokeStyle = 'rgba(0, 123, 255, 0.8)'; // Example color
301
- context.lineWidth = 3;
302
- context.strokeRect(this.selectedElementBounds.left + window.scrollX, this.selectedElementBounds.top + window.scrollY, this.selectedElementBounds.width, this.selectedElementBounds.height);
303
- }
304
- const dataUrl = canvas.toDataURL();
858
+ const dataUrl = canvas.toDataURL('image/png');
859
+ console.log('Screenshot captured successfully, size:', canvas.width, 'x', canvas.height);
305
860
  resolve(dataUrl);
306
861
  })
307
862
  .catch((error) => {
308
- console.error('Failed to capture screenshot:', error);
863
+ console.error('Failed to capture viewport screenshot:', error);
309
864
  reject(error);
310
865
  });
311
866
  });
@@ -320,12 +875,8 @@ export class FeedbackModal {
320
875
  handleRatingChange(newRating) {
321
876
  this.selectedRating = newRating;
322
877
  }
323
- // Remove the preview modal toggle function
324
- // togglePreviewModal() {
325
- // this.showPreviewModal = !this.showPreviewModal;
326
- // }
327
878
  render() {
328
- return (h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''}` }, this.showScreenshotMode && (h("div", { class: "feedback-modal-screenshot", ref: (el) => (this.screenshotModal = el), onMouseMove: this.handleMouseOverScreenShot }, h("div", { class: "feedback-modal-screenshot-element-selected", ref: (el) => (this.elementSelected = el), onClick: this.handleMouseClickedSelectedElement }), h("div", { class: "top-side", ref: (el) => (this.topSide = el) }), h("div", { class: "left-side", ref: (el) => (this.leftSide = el) }), h("div", { class: "bottom-side", ref: (el) => (this.bottomSide = el) }), h("div", { class: "right-side", ref: (el) => (this.rightSide = el) }), this.showScreenshotTopBar && (h("div", { class: "feedback-modal-screenshot-header", onClick: this.closeScreenShot }, h("span", null, this.screenshotTopbarText), h("span", { class: "feedback-modal-screenshot-close" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "#191919", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather feather-x" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" }))))))), this.showModal && (h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}` })), this.showModal && (h("div", { class: `feedback-modal-content feedback-modal-content--${this.modalPosition} ${this.isAnimating ? 'feedback-modal-content--open' : ''}`, ref: (el) => (this.modalContent = el) }, h("div", { class: "feedback-modal-header" }, !this.formSuccess && !this.formError ? (h("span", null, this.modalTitle)) : this.formSuccess ? (h("span", null, this.modalTitleSuccess)) : (h("span", null, this.modalTitleError)), h("button", { class: "feedback-modal-close", onClick: this.close }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "#191919", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather feather-x" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))), h("div", { class: "feedback-modal-body" }, !this.formSuccess && !this.formError ? (h("form", { onSubmit: this.handleSubmit }, !this.hideRating && (h("div", { class: "feedback-modal-rating" }, this.ratingMode === 'thumbs' ? (h("div", { class: "feedback-modal-rating-content" }, h("span", { class: "feedback-modal-input-heading" }, this.ratingPlaceholder), h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs" }, h("button", { title: "Yes", class: `feedback-modal-rating-button ${this.selectedRating === 1
879
+ return (h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''}` }, this.showModal && (h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}` })), this.showModal && (h("div", { class: `feedback-modal-content feedback-modal-content--${this.modalPosition} ${this.isAnimating ? 'feedback-modal-content--open' : ''}`, ref: (el) => (this.modalContent = el) }, h("div", { class: "feedback-modal-header" }, !this.formSuccess && !this.formError ? (h("span", null, this.modalTitle)) : this.formSuccess ? (h("span", null, this.modalTitleSuccess)) : (h("span", null, this.modalTitleError)), h("button", { class: "feedback-modal-close", onClick: this.close }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "#191919", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather feather-x" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))), h("div", { class: "feedback-modal-body" }, !this.formSuccess && !this.formError ? (h("form", { onSubmit: this.handleSubmit }, !this.hideRating && (h("div", { class: "feedback-modal-rating" }, this.ratingMode === 'thumbs' ? (h("div", { class: "feedback-modal-rating-content" }, h("span", { class: "feedback-modal-input-heading" }, this.ratingPlaceholder), h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs" }, h("button", { title: "Yes", class: `feedback-modal-rating-button ${this.selectedRating === 1
329
880
  ? 'feedback-modal-rating-button--selected'
330
881
  : ''}`, onClick: (event) => {
331
882
  event.preventDefault();
@@ -340,7 +891,8 @@ export class FeedbackModal {
340
891
  : ''}`, onClick: (event) => {
341
892
  event.preventDefault();
342
893
  this.handleRatingChange(rating);
343
- } }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" })))))))))), h("div", { class: "feedback-modal-text" }, h("textarea", { placeholder: this.messagePlaceholder, value: this.formMessage, onInput: (event) => this.handleMessageInput(event) })), !this.hideEmail && (h("div", { class: "feedback-modal-email" }, h("input", { placeholder: this.emailPlaceholder, type: "email", onInput: (event) => this.handleEmailInput(event), value: this.formEmail, required: this.isEmailRequired }))), h("div", { class: "feedback-verification" }, h("input", { type: "text", name: "verification", style: { display: 'none' }, onInput: (event) => this.handleVerification(event), value: this.formVerification })), !this.hidePrivacyPolicy && (h("div", { class: "feedback-modal-privacy" }, h("input", { type: "checkbox", id: "privacyPolicy", onChange: (ev) => this.handleCheckboxChange(ev), required: true }), h("span", { innerHTML: this.privacyPolicyText }))), h("div", { class: `feedback-modal-buttons ${this.hideScreenshotButton ? 'single' : ''}` }, !this.hideScreenshotButton && (h("button", { type: "button", class: `feedback-modal-button feedback-modal-button--screenshot ${this.encodedScreenshot ? 'feedback-modal-button--active' : ''}`, onClick: this.openScreenShot, disabled: this.sending }, this.encodedScreenshot && (h("div", { class: "screenshot-preview" }, h("img", { src: this.encodedScreenshot, alt: "Screenshot Preview" }))), !this.encodedScreenshot && (h("svg", { xmlns: "http://www.w3.org/2000/svg", height: "24", viewBox: "0 -960 960 960", width: "24" }, h("path", { d: "M680-80v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-200v-200h80v120h120v80H200Zm0-360v-200h200v80H280v120h-80Zm480 0v-120H560v-80h200v200h-80Z" }))), this.encodedScreenshot ? this.screenshotAttachedText : this.screenshotButtonText)), h("button", { class: "feedback-modal-button feedback-modal-button--submit", type: "submit", disabled: this.sending }, this.sendButtonText)))) : this.formSuccess && !this.formError ? (h("div", { class: "feedback-modal-success" }, h("p", { class: "feedback-modal-message" }, this.successMessage))) : this.formError && this.formErrorStatus == 404 ? (h("p", { class: "feedback-modal-message" }, this.errorMessage404)) : this.formError && this.formErrorStatus == 403 ? (h("p", { class: "feedback-modal-message" }, this.errorMessage403)) : this.formError ? (h("p", { class: "feedback-modal-message" }, this.errorMessage)) : (h("span", null))), h("div", { class: "feedback-modal-footer" }, h("div", { class: "feedback-logo", style: { display: this.whitelabel ? 'none' : 'block' } }, "Powered by", ' ', h("a", { target: "_blank", href: "https://pushfeedback.com" }, "PushFeedback.com")), this.footerText && (h("div", { class: "feedback-footer-text" }, h("span", { innerHTML: this.footerText }))))))));
894
+ } }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" })))))))))), h("div", { class: "feedback-modal-text" }, h("textarea", { placeholder: this.messagePlaceholder, value: this.formMessage, onInput: (event) => this.handleMessageInput(event) })), !this.hideEmail && (h("div", { class: "feedback-modal-email" }, h("input", { placeholder: this.emailPlaceholder, type: "email", onInput: (event) => this.handleEmailInput(event), value: this.formEmail, required: this.isEmailRequired }))), h("div", { class: "feedback-verification" }, h("input", { type: "text", name: "verification", style: { display: 'none' }, onInput: (event) => this.handleVerification(event), value: this.formVerification })), !this.hidePrivacyPolicy && (h("div", { class: "feedback-modal-privacy" }, h("input", { type: "checkbox", id: "privacyPolicy", onChange: (ev) => this.handleCheckboxChange(ev), required: true }), h("span", { innerHTML: this.privacyPolicyText }))), h("div", { class: `feedback-modal-buttons ${this.hideScreenshotButton ? 'single' : ''}` }, !this.hideScreenshotButton && (h("button", { type: "button", class: `feedback-modal-button feedback-modal-button--screenshot ${this.encodedScreenshot ? 'feedback-modal-button--active' : ''}`, onClick: this.openScreenShot, disabled: this.sending || this.takingScreenshot }, this.encodedScreenshot && (h("div", { class: "screenshot-preview", onClick: this.openCanvasEditor }, h("img", { src: this.encodedScreenshot, alt: "Screenshot Preview" }))), !this.encodedScreenshot && !this.takingScreenshot && (h("svg", { xmlns: "http://www.w3.org/2000/svg", height: "24", viewBox: "0 -960 960 960", width: "24" }, h("path", { d: "M680-80v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-200v-200h80v120h120v80H200Zm0-360v-200h200v80H280v120h-80Zm480 0v-120H560v-80h200v200h-80Z" }))), this.takingScreenshot && (h("div", { class: "screenshot-loading" }, h("svg", { width: "16", height: "16", viewBox: "0 0 16 16" }, h("circle", { cx: "8", cy: "8", r: "6", fill: "none", stroke: "#666", "stroke-width": "2", "stroke-dasharray": "6 6", "transform-origin": "8 8" }, h("animateTransform", { attributeName: "transform", type: "rotate", values: "0 8 8;360 8 8", dur: "1s", repeatCount: "indefinite" }))))), this.takingScreenshot ? this.screenshotTakingText :
895
+ this.encodedScreenshot ? this.screenshotAttachedText : this.screenshotButtonText)), h("button", { class: "feedback-modal-button feedback-modal-button--submit", type: "submit", disabled: this.sending }, this.sendButtonText)))) : this.formSuccess && !this.formError ? (h("div", { class: "feedback-modal-success" }, h("p", { class: "feedback-modal-message" }, this.successMessage))) : this.formError && this.formErrorStatus == 404 ? (h("p", { class: "feedback-modal-message" }, this.errorMessage404)) : this.formError && this.formErrorStatus == 403 ? (h("p", { class: "feedback-modal-message" }, this.errorMessage403)) : this.formError ? (h("p", { class: "feedback-modal-message" }, this.errorMessage)) : (h("span", null))), h("div", { class: "feedback-modal-footer" }, h("div", { class: "feedback-logo", style: { display: this.whitelabel ? 'none' : 'block' } }, "Powered by", ' ', h("a", { target: "_blank", href: "https://pushfeedback.com" }, "PushFeedback.com")), this.footerText && (h("div", { class: "feedback-footer-text" }, h("span", { innerHTML: this.footerText })))))), this.showCanvasEditor && (h("div", { class: "canvas-editor-overlay" }, h("div", { class: "canvas-editor-modal" }, h("div", { class: "canvas-editor-header" }, h("div", { class: "canvas-editor-title" }, h("h3", null, this.canvasEditorTitle)), h("div", { class: "canvas-editor-toolbar" }, h("div", { class: "toolbar-section" }, h("div", { class: "tool-group" }, h("button", { class: `tool-btn ${this.canvasDrawingTool === 'rectangle' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'rectangle', title: "Rectangle" }, h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'line' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'line', title: "Line" }, h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("line", { x1: "5", y1: "12", x2: "19", y2: "12" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'arrow' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'arrow', title: "Arrow" }, h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("line", { x1: "7", y1: "17", x2: "17", y2: "7" }), h("polyline", { points: "7,7 17,7 17,17" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'text' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'text', title: "Text" }, h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("polyline", { points: "4,7 4,4 20,4 20,7" }), h("line", { x1: "9", y1: "20", x2: "15", y2: "20" }), h("line", { x1: "12", y1: "4", x2: "12", y2: "20" }))), h("div", { class: "toolbar-divider" }), h("button", { class: "tool-btn undo-btn", onClick: this.undoLastAnnotation, disabled: this.annotations.length === 0, title: "Undo" }, h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("polyline", { points: "1,4 1,10 7,10" }), h("path", { d: "M3.51,15a9,9,0,0,0,14.85-3.36,9,9,0,0,0-9.19-10.15L1.83,10" }))))), h("div", { class: "toolbar-section" }, h("div", { class: "color-palette" }, this.defaultColors.map((color, index) => (h("div", { class: "color-slot-wrapper" }, h("button", { class: `color-btn ${this.canvasDrawingColor === color ? 'active' : ''} ${this.editingColorIndex === index ? 'editing' : ''}`, style: { backgroundColor: color }, onClick: () => this.handleColorSlotClick(index), title: `Color ${index + 1} - Click to customize` }, this.editingColorIndex === index && (h("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "white", "stroke-width": "2" }, h("path", { d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" })))), this.editingColorIndex === index && this.showColorPicker && (h("div", { class: "color-picker-dropdown" }, h("input", { type: "color", value: color, onInput: (e) => this.handleColorPickerInput(e), onClick: (e) => this.handleColorPickerClick(e) })))))))), h("div", { class: "toolbar-section" }, h("div", { class: "size-control" }, h("input", { type: "range", min: "1", max: "10", value: this.canvasLineWidth, onInput: (e) => this.canvasLineWidth = parseInt(e.target.value), class: "size-slider" }), h("span", { class: "size-value" }, this.canvasLineWidth, "px"))), h("div", { class: "toolbar-section" }, h("button", { class: "action-btn secondary", onClick: this.closeCanvasEditor }, this.canvasEditorCancelText), h("button", { class: "action-btn primary", onClick: this.saveAnnotations }, this.canvasEditorSaveText))), h("div", { class: "canvas-editor-content" }, h("canvas", { ref: (el) => this.canvasRef = el, class: "annotation-canvas", onMouseDown: this.handleCanvasMouseDown, onMouseMove: this.handleCanvasMouseMove, onMouseUp: this.handleCanvasMouseUp, onMouseLeave: this.handleCanvasMouseUp }))))))));
344
896
  }
345
897
  componentDidRender() {
346
898
  if (this.showModal) {
@@ -909,6 +1461,24 @@ export class FeedbackModal {
909
1461
  "reflect": false,
910
1462
  "defaultValue": "'Send'"
911
1463
  },
1464
+ "screenshotAttachedText": {
1465
+ "type": "string",
1466
+ "mutable": false,
1467
+ "complexType": {
1468
+ "original": "string",
1469
+ "resolved": "string",
1470
+ "references": {}
1471
+ },
1472
+ "required": false,
1473
+ "optional": false,
1474
+ "docs": {
1475
+ "tags": [],
1476
+ "text": ""
1477
+ },
1478
+ "attribute": "screenshot-attached-text",
1479
+ "reflect": false,
1480
+ "defaultValue": "'Screenshot attached'"
1481
+ },
912
1482
  "screenshotButtonText": {
913
1483
  "type": "string",
914
1484
  "mutable": false,
@@ -927,7 +1497,7 @@ export class FeedbackModal {
927
1497
  "reflect": false,
928
1498
  "defaultValue": "'Add a screenshot'"
929
1499
  },
930
- "screenshotAttachedText": {
1500
+ "screenshotTakingText": {
931
1501
  "type": "string",
932
1502
  "mutable": false,
933
1503
  "complexType": {
@@ -941,9 +1511,9 @@ export class FeedbackModal {
941
1511
  "tags": [],
942
1512
  "text": ""
943
1513
  },
944
- "attribute": "screenshot-attached-text",
1514
+ "attribute": "screenshot-taking-text",
945
1515
  "reflect": false,
946
- "defaultValue": "'Screenshot attached'"
1516
+ "defaultValue": "'Taking screenshot...'"
947
1517
  },
948
1518
  "screenshotTopbarText": {
949
1519
  "type": "string",
@@ -980,6 +1550,60 @@ export class FeedbackModal {
980
1550
  "attribute": "success-message",
981
1551
  "reflect": false,
982
1552
  "defaultValue": "''"
1553
+ },
1554
+ "canvasEditorTitle": {
1555
+ "type": "string",
1556
+ "mutable": false,
1557
+ "complexType": {
1558
+ "original": "string",
1559
+ "resolved": "string",
1560
+ "references": {}
1561
+ },
1562
+ "required": false,
1563
+ "optional": false,
1564
+ "docs": {
1565
+ "tags": [],
1566
+ "text": ""
1567
+ },
1568
+ "attribute": "canvas-editor-title",
1569
+ "reflect": false,
1570
+ "defaultValue": "'Edit screenshot'"
1571
+ },
1572
+ "canvasEditorCancelText": {
1573
+ "type": "string",
1574
+ "mutable": false,
1575
+ "complexType": {
1576
+ "original": "string",
1577
+ "resolved": "string",
1578
+ "references": {}
1579
+ },
1580
+ "required": false,
1581
+ "optional": false,
1582
+ "docs": {
1583
+ "tags": [],
1584
+ "text": ""
1585
+ },
1586
+ "attribute": "canvas-editor-cancel-text",
1587
+ "reflect": false,
1588
+ "defaultValue": "'Cancel'"
1589
+ },
1590
+ "canvasEditorSaveText": {
1591
+ "type": "string",
1592
+ "mutable": false,
1593
+ "complexType": {
1594
+ "original": "string",
1595
+ "resolved": "string",
1596
+ "references": {}
1597
+ },
1598
+ "required": false,
1599
+ "optional": false,
1600
+ "docs": {
1601
+ "tags": [],
1602
+ "text": ""
1603
+ },
1604
+ "attribute": "canvas-editor-save-text",
1605
+ "reflect": false,
1606
+ "defaultValue": "'Save'"
983
1607
  }
984
1608
  };
985
1609
  }
@@ -997,7 +1621,27 @@ export class FeedbackModal {
997
1621
  "whitelabel": {},
998
1622
  "selectedRating": {},
999
1623
  "overlayVisible": {},
1000
- "isAnimating": {}
1624
+ "isAnimating": {},
1625
+ "takingScreenshot": {},
1626
+ "showPreviewModal": {},
1627
+ "showCanvasEditor": {},
1628
+ "canvasDrawingTool": {},
1629
+ "canvasDrawingColor": {},
1630
+ "canvasLineWidth": {},
1631
+ "isDrawing": {},
1632
+ "annotations": {},
1633
+ "currentAnnotation": {},
1634
+ "isDragging": {},
1635
+ "draggedAnnotation": {},
1636
+ "dragStartPos": {},
1637
+ "showColorPicker": {},
1638
+ "editingColorIndex": {},
1639
+ "isResizing": {},
1640
+ "resizingAnnotation": {},
1641
+ "resizeStartSize": {},
1642
+ "hoveredAnnotation": {},
1643
+ "resizeHandle": {},
1644
+ "defaultColors": {}
1001
1645
  };
1002
1646
  }
1003
1647
  static get events() {