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.
- package/dist/cjs/feedback-button_2.cjs.entry.js +728 -153
- 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 +80 -0
- package/dist/collection/components/feedback-modal/feedback-modal.css +439 -12
- package/dist/collection/components/feedback-modal/feedback-modal.js +771 -127
- package/dist/components/feedback-button.js +12 -0
- package/dist/components/feedback-modal2.js +745 -154
- package/dist/esm/feedback-button_2.entry.js +728 -153
- 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 +4 -0
- package/dist/types/components/feedback-modal/feedback-modal.d.ts +79 -14
- package/dist/types/components.d.ts +16 -0
- package/package.json +1 -1
- package/dist/pushfeedback/p-e6c2f829.entry.js +0 -22
|
@@ -82,8 +82,27 @@ export class FeedbackModal {
|
|
|
82
82
|
this.showScreenshotTopBar = false;
|
|
83
83
|
this.hasSelectedElement = false;
|
|
84
84
|
this.encodedScreenshot = null;
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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.
|
|
140
|
+
this.openCanvasEditor = (event) => {
|
|
141
|
+
if (event) {
|
|
142
|
+
event.stopPropagation();
|
|
143
|
+
}
|
|
112
144
|
this.showModal = false;
|
|
113
|
-
this.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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.
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
this.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
this.
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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.
|
|
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
|
-
|
|
822
|
+
captureViewportScreenshot() {
|
|
285
823
|
return new Promise((resolve, reject) => {
|
|
286
824
|
requestAnimationFrame(() => {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
298
|
-
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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-
|
|
1514
|
+
"attribute": "screenshot-taking-text",
|
|
945
1515
|
"reflect": false,
|
|
946
|
-
"defaultValue": "'
|
|
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() {
|