pushfeedback 0.1.67 → 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.
- package/dist/cjs/feedback-button_2.cjs.entry.js +724 -186
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/pushfeedback.cjs.js +1 -1
- package/dist/collection/components/feedback-button/feedback-button.js +60 -0
- package/dist/collection/components/feedback-modal/feedback-modal.css +419 -11
- package/dist/collection/components/feedback-modal/feedback-modal.js +747 -158
- package/dist/components/feedback-button.js +9 -0
- package/dist/components/feedback-modal2.js +739 -186
- package/dist/esm/feedback-button_2.entry.js +724 -186
- package/dist/esm/loader.js +1 -1
- package/dist/esm/pushfeedback.js +1 -1
- package/dist/pushfeedback/p-7406f7be.entry.js +7 -0
- package/dist/pushfeedback/pushfeedback.css +1 -1
- package/dist/pushfeedback/pushfeedback.esm.js +1 -1
- package/dist/types/components/feedback-button/feedback-button.d.ts +3 -0
- package/dist/types/components/feedback-modal/feedback-modal.d.ts +77 -21
- package/dist/types/components.d.ts +12 -0
- package/package.json +1 -1
- package/dist/pushfeedback/p-51e790c7.entry.js +0 -22
|
@@ -86,144 +86,634 @@ export class FeedbackModal {
|
|
|
86
86
|
document.querySelectorAll('.feedback-modal-element-selected').forEach(el => {
|
|
87
87
|
el.classList.remove('feedback-modal-element-selected');
|
|
88
88
|
});
|
|
89
|
+
// Reset canvas editor states
|
|
89
90
|
this.takingScreenshot = false;
|
|
90
|
-
this.
|
|
91
|
-
this.
|
|
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
|
|
92
106
|
this.formSuccess = false;
|
|
93
107
|
this.formError = false;
|
|
94
108
|
this.formErrorStatus = 500;
|
|
95
109
|
this.formMessage = '';
|
|
96
110
|
this.formEmail = '';
|
|
97
|
-
this.showPreviewModal = false;
|
|
98
111
|
this.resetOverflow();
|
|
99
112
|
}, 200);
|
|
100
113
|
};
|
|
101
|
-
this.openScreenShot = () => {
|
|
102
|
-
|
|
103
|
-
this.showModal = false;
|
|
104
|
-
this.showScreenshotMode = true;
|
|
105
|
-
this.showScreenshotTopBar = true;
|
|
106
|
-
// Clear previous screenshot and selection data
|
|
107
|
-
this.encodedScreenshot = null;
|
|
108
|
-
this.originalElement = null;
|
|
109
|
-
this.selectedElementBounds = null;
|
|
110
|
-
this.hoveredElement = null;
|
|
111
|
-
this.hoveredElementBounds = null;
|
|
112
|
-
// NO CSS CLASSES - they cause scroll jumping
|
|
113
|
-
};
|
|
114
|
-
this.closeScreenShot = () => {
|
|
115
|
-
// Remove highlight from ALL selected elements
|
|
116
|
-
document.querySelectorAll('.feedback-modal-element-selected').forEach(el => {
|
|
117
|
-
el.classList.remove('feedback-modal-element-selected');
|
|
118
|
-
});
|
|
119
|
-
// Reset loading state
|
|
120
|
-
this.takingScreenshot = false;
|
|
121
|
-
this.showModal = false;
|
|
122
|
-
this.showScreenshotMode = false;
|
|
123
|
-
this.showScreenshotTopBar = false;
|
|
124
|
-
};
|
|
125
|
-
this.openPreviewModal = (event) => {
|
|
126
|
-
event.stopPropagation(); // Prevent button click from firing
|
|
127
|
-
this.showPreviewModal = true;
|
|
128
|
-
};
|
|
129
|
-
this.closePreviewModal = () => {
|
|
130
|
-
this.showPreviewModal = false;
|
|
131
|
-
};
|
|
132
|
-
this.handleMouseOverScreenShot = (event) => {
|
|
133
|
-
event.preventDefault();
|
|
134
|
-
if (this.hasSelectedElement)
|
|
135
|
-
return;
|
|
136
|
-
const borderOffset = 2;
|
|
137
|
-
this.screenshotModal.style.display = 'none';
|
|
138
|
-
const elementUnder = document.elementFromPoint(event.clientX, event.clientY);
|
|
139
|
-
const rect = elementUnder.getBoundingClientRect();
|
|
140
|
-
this.screenshotModal.style.display = '';
|
|
141
|
-
// Store the hovered element and its bounds for later use
|
|
142
|
-
this.hoveredElement = elementUnder;
|
|
143
|
-
this.hoveredElementBounds = rect;
|
|
144
|
-
// Get the bounding box of the element selected
|
|
145
|
-
this.elementSelected.style.position = 'absolute';
|
|
146
|
-
this.elementSelected.style.left = `${rect.left}px`;
|
|
147
|
-
this.elementSelected.style.top = `${rect.top}px`;
|
|
148
|
-
this.elementSelected.style.width = `${rect.width}px`;
|
|
149
|
-
this.elementSelected.style.height = `${rect.height}px`;
|
|
150
|
-
this.elementSelected.classList.add('feedback-modal-element-hover');
|
|
151
|
-
// Set the background color of nonselected areas
|
|
152
|
-
// Top
|
|
153
|
-
this.topSide.style.position = 'absolute';
|
|
154
|
-
this.topSide.style.left = `${rect.left}px`;
|
|
155
|
-
this.topSide.style.top = '0px';
|
|
156
|
-
this.topSide.style.width = `${rect.width + borderOffset}px`;
|
|
157
|
-
this.topSide.style.height = `${rect.top}px`;
|
|
158
|
-
this.topSide.style.backgroundColor = 'rgba(0, 0, 0, 0.4)';
|
|
159
|
-
// Left
|
|
160
|
-
this.leftSide.style.position = 'absolute';
|
|
161
|
-
this.leftSide.style.left = '0px';
|
|
162
|
-
this.leftSide.style.top = '0px';
|
|
163
|
-
this.leftSide.style.width = `${rect.left}px`;
|
|
164
|
-
this.leftSide.style.height = '100vh';
|
|
165
|
-
this.leftSide.style.backgroundColor = 'rgba(0, 0, 0, 0.4)';
|
|
166
|
-
// Bottom
|
|
167
|
-
this.bottomSide.style.position = 'absolute';
|
|
168
|
-
this.bottomSide.style.left = `${rect.left}px`;
|
|
169
|
-
this.bottomSide.style.top = `${rect.bottom + borderOffset}px`;
|
|
170
|
-
this.bottomSide.style.width = `${rect.width + borderOffset}px`;
|
|
171
|
-
this.bottomSide.style.height = '100vh';
|
|
172
|
-
this.bottomSide.style.backgroundColor = 'rgba(0, 0, 0, 0.4)';
|
|
173
|
-
// Right
|
|
174
|
-
this.rightSide.style.position = 'absolute';
|
|
175
|
-
this.rightSide.style.left = `${rect.right + borderOffset}px`;
|
|
176
|
-
this.rightSide.style.top = '0px';
|
|
177
|
-
this.rightSide.style.width = '100%';
|
|
178
|
-
this.rightSide.style.height = '100vh';
|
|
179
|
-
this.rightSide.style.backgroundColor = 'rgba(0, 0, 0, 0.4)';
|
|
180
|
-
// Restore the visibility of the screenshot-modal
|
|
181
|
-
this.screenshotModal.style.backgroundColor = 'transparent';
|
|
182
|
-
};
|
|
183
|
-
this.handleMouseClickedSelectedElement = async (event) => {
|
|
184
|
-
event.preventDefault();
|
|
185
|
-
if (!this.elementSelected || !this.hoveredElement) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
this.hasSelectedElement = true;
|
|
189
|
-
// Remove highlight from ALL previously selected elements
|
|
190
|
-
document.querySelectorAll('.feedback-modal-element-selected').forEach(el => {
|
|
191
|
-
el.classList.remove('feedback-modal-element-selected');
|
|
192
|
-
});
|
|
193
|
-
// Add highlight to newly selected element
|
|
194
|
-
this.hoveredElement.classList.add('feedback-modal-element-selected');
|
|
195
|
-
// Store element bounds in viewport coordinates
|
|
196
|
-
this.selectedElementBounds = this.hoveredElementBounds;
|
|
197
|
-
this.originalElement = this.hoveredElement;
|
|
198
|
-
// Show loading state in top bar
|
|
114
|
+
this.openScreenShot = async () => {
|
|
115
|
+
// Show loading state immediately
|
|
199
116
|
this.takingScreenshot = true;
|
|
200
|
-
// Take screenshot FIRST while highlight is still visible
|
|
201
117
|
try {
|
|
202
|
-
|
|
203
|
-
|
|
118
|
+
// Capture viewport screenshot immediately
|
|
119
|
+
const dataUrl = await this.captureViewportScreenshot();
|
|
204
120
|
this.encodedScreenshot = dataUrl;
|
|
121
|
+
this.originalImageData = dataUrl;
|
|
205
122
|
// Reset loading state
|
|
206
123
|
this.takingScreenshot = false;
|
|
207
|
-
//
|
|
208
|
-
this.
|
|
209
|
-
this.
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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);
|
|
214
131
|
}
|
|
215
132
|
catch (error) {
|
|
216
133
|
console.error('Failed to capture screenshot:', error);
|
|
217
|
-
this.hasSelectedElement = false;
|
|
218
134
|
// Reset loading state on error
|
|
219
135
|
this.takingScreenshot = false;
|
|
220
|
-
//
|
|
221
|
-
this.showScreenshotTopBar = false;
|
|
222
|
-
this.showScreenshotMode = false;
|
|
223
|
-
this.resetOverflow();
|
|
136
|
+
// Show modal anyway
|
|
224
137
|
this.showModal = true;
|
|
225
138
|
}
|
|
226
139
|
};
|
|
140
|
+
this.openCanvasEditor = (event) => {
|
|
141
|
+
if (event) {
|
|
142
|
+
event.stopPropagation();
|
|
143
|
+
}
|
|
144
|
+
this.showModal = false;
|
|
145
|
+
this.showCanvasEditor = true;
|
|
146
|
+
// Initialize canvas after a short delay to ensure DOM is ready
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
this.initializeCanvas();
|
|
149
|
+
}, 100);
|
|
150
|
+
};
|
|
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)
|
|
166
|
+
return;
|
|
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;
|
|
205
|
+
};
|
|
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)
|
|
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;
|
|
260
|
+
}
|
|
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;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
// Start editing this color slot
|
|
308
|
+
this.editingColorIndex = colorIndex;
|
|
309
|
+
this.showColorPicker = true;
|
|
310
|
+
this.canvasDrawingColor = this.defaultColors[colorIndex];
|
|
311
|
+
}
|
|
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
|
+
}
|
|
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();
|
|
716
|
+
};
|
|
227
717
|
this.sending = false;
|
|
228
718
|
this.formMessage = '';
|
|
229
719
|
this.formEmail = '';
|
|
@@ -237,6 +727,26 @@ export class FeedbackModal {
|
|
|
237
727
|
this.selectedRating = -1;
|
|
238
728
|
this.overlayVisible = false;
|
|
239
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'];
|
|
240
750
|
this.customFont = false;
|
|
241
751
|
this.emailAddress = '';
|
|
242
752
|
this.hideEmail = false;
|
|
@@ -272,8 +782,9 @@ export class FeedbackModal {
|
|
|
272
782
|
this.screenshotTakingText = 'Taking screenshot...';
|
|
273
783
|
this.screenshotTopbarText = 'Select an element on this page';
|
|
274
784
|
this.successMessage = '';
|
|
275
|
-
this.
|
|
276
|
-
this.
|
|
785
|
+
this.canvasEditorTitle = 'Edit screenshot';
|
|
786
|
+
this.canvasEditorCancelText = 'Cancel';
|
|
787
|
+
this.canvasEditorSaveText = 'Save';
|
|
277
788
|
}
|
|
278
789
|
componentWillLoad() {
|
|
279
790
|
if (this.fetchData)
|
|
@@ -308,46 +819,51 @@ export class FeedbackModal {
|
|
|
308
819
|
handleEmailInput(event) {
|
|
309
820
|
this.formEmail = event.target.value;
|
|
310
821
|
}
|
|
311
|
-
|
|
822
|
+
captureViewportScreenshot() {
|
|
312
823
|
return new Promise((resolve, reject) => {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
824
|
+
requestAnimationFrame(() => {
|
|
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,
|
|
839
|
+
useCORS: true,
|
|
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';
|
|
319
855
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
useCORS: true,
|
|
330
|
-
scale: 1,
|
|
331
|
-
backgroundColor: '#ffffff',
|
|
332
|
-
logging: true,
|
|
333
|
-
foreignObjectRendering: false,
|
|
334
|
-
imageTimeout: 10000,
|
|
335
|
-
ignoreElements: (element) => {
|
|
336
|
-
// Only ignore screenshot UI, keep everything else including highlights
|
|
337
|
-
return element.classList.contains('feedback-modal-screenshot-header') ||
|
|
338
|
-
element.classList.contains('feedback-overlay');
|
|
339
|
-
}
|
|
340
|
-
})
|
|
341
|
-
.then((canvas) => {
|
|
342
|
-
const dataUrl = canvas.toDataURL();
|
|
343
|
-
resolve(dataUrl);
|
|
344
|
-
})
|
|
345
|
-
.catch((error) => {
|
|
346
|
-
console.error('Failed to capture screenshot:', error);
|
|
347
|
-
reject(error);
|
|
348
|
-
});
|
|
856
|
+
})
|
|
857
|
+
.then((canvas) => {
|
|
858
|
+
const dataUrl = canvas.toDataURL('image/png');
|
|
859
|
+
console.log('Screenshot captured successfully, size:', canvas.width, 'x', canvas.height);
|
|
860
|
+
resolve(dataUrl);
|
|
861
|
+
})
|
|
862
|
+
.catch((error) => {
|
|
863
|
+
console.error('Failed to capture viewport screenshot:', error);
|
|
864
|
+
reject(error);
|
|
349
865
|
});
|
|
350
|
-
}
|
|
866
|
+
});
|
|
351
867
|
});
|
|
352
868
|
}
|
|
353
869
|
handleCheckboxChange(event) {
|
|
@@ -360,7 +876,7 @@ export class FeedbackModal {
|
|
|
360
876
|
this.selectedRating = newRating;
|
|
361
877
|
}
|
|
362
878
|
render() {
|
|
363
|
-
return (h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''}` }, this.
|
|
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
|
|
364
880
|
? 'feedback-modal-rating-button--selected'
|
|
365
881
|
: ''}`, onClick: (event) => {
|
|
366
882
|
event.preventDefault();
|
|
@@ -375,7 +891,8 @@ export class FeedbackModal {
|
|
|
375
891
|
: ''}`, onClick: (event) => {
|
|
376
892
|
event.preventDefault();
|
|
377
893
|
this.handleRatingChange(rating);
|
|
378
|
-
} }, 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", onClick: this.
|
|
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 }))))))));
|
|
379
896
|
}
|
|
380
897
|
componentDidRender() {
|
|
381
898
|
if (this.showModal) {
|
|
@@ -1033,6 +1550,60 @@ export class FeedbackModal {
|
|
|
1033
1550
|
"attribute": "success-message",
|
|
1034
1551
|
"reflect": false,
|
|
1035
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'"
|
|
1036
1607
|
}
|
|
1037
1608
|
};
|
|
1038
1609
|
}
|
|
@@ -1052,7 +1623,25 @@ export class FeedbackModal {
|
|
|
1052
1623
|
"overlayVisible": {},
|
|
1053
1624
|
"isAnimating": {},
|
|
1054
1625
|
"takingScreenshot": {},
|
|
1055
|
-
"showPreviewModal": {}
|
|
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": {}
|
|
1056
1645
|
};
|
|
1057
1646
|
}
|
|
1058
1647
|
static get events() {
|