pushfeedback 0.1.69 → 0.1.71
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 → canvas-editor_3.cjs.entry.js} +883 -718
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/pushfeedback.cjs.js +1 -1
- package/dist/collection/collection-manifest.json +1 -0
- package/dist/collection/components/canvas-editor/canvas-editor.css +404 -0
- package/dist/collection/components/canvas-editor/canvas-editor.js +1282 -0
- package/dist/collection/components/feedback-button/feedback-button.js +220 -0
- package/dist/collection/components/feedback-modal/feedback-modal.css +2 -458
- package/dist/collection/components/feedback-modal/feedback-modal.js +247 -782
- package/dist/components/canvas-editor.d.ts +11 -0
- package/dist/components/canvas-editor.js +6 -0
- package/dist/components/canvas-editor2.js +917 -0
- package/dist/components/feedback-button.js +40 -1
- package/dist/components/feedback-modal2.js +68 -784
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/esm/{feedback-button_2.entry.js → canvas-editor_3.entry.js} +883 -719
- package/dist/esm/loader.js +1 -1
- package/dist/esm/pushfeedback.js +1 -1
- package/dist/pushfeedback/p-2c39091c.entry.js +1 -0
- package/dist/pushfeedback/pushfeedback.esm.js +1 -1
- package/dist/types/components/canvas-editor/canvas-editor.d.ts +108 -0
- package/dist/types/components/feedback-button/feedback-button.d.ts +11 -0
- package/dist/types/components/feedback-modal/feedback-modal.d.ts +22 -79
- package/dist/types/components.d.ts +102 -0
- package/package.json +3 -4
- package/dist/pushfeedback/p-e7f48090.entry.js +0 -1
|
@@ -1,419 +1,98 @@
|
|
|
1
1
|
import { r as registerInstance, c as createEvent, h, H as Host, g as getElement } from './index-f65e9124.js';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const canvasEditorCss = ":host{display:block}.canvas-editor-wrapper{position:relative}.canvas-editor-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0, 0, 0, 0.8);display:flex;align-items:center;justify-content:center;z-index:9999}.canvas-editor-modal{width:95vw;max-width:1400px;max-height:900px;background:var(--feedback-canvas-editor-bg-color, #ffffff);border-radius:8px;border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);display:flex;flex-direction:column;overflow:hidden;box-shadow:0 10px 40px rgba(0, 0, 0, 0.2)}.canvas-editor-header{background:var(--feedback-canvas-editor-header-bg-color, #f5f5f5);border-bottom:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);padding:12px 16px;display:flex;flex-direction:column;gap:12px}.canvas-editor-title h3{margin:0;font-size:16px;font-weight:600;color:var(--feedback-canvas-editor-tool-text-color, #333)}.canvas-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:16px}.toolbar-section{display:flex;align-items:center;gap:8px}.tool-group{display:flex;align-items:center;gap:4px}.tool-btn{width:36px;height:36px;display:flex;align-items:center;justify-content:center;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);border-radius:6px;cursor:pointer;transition:all 0.2s ease;padding:0}.tool-btn svg{width:18px;height:18px;color:var(--feedback-canvas-editor-tool-text-color, #333)}.tool-btn:hover:not(:disabled){background:var(--feedback-canvas-editor-tool-bg-hover, #f0f0f0)}.tool-btn.active,.tool-btn.active:hover{background:var(--feedback-canvas-editor-tool-bg-active, #0070f4);color:var(--feedback-canvas-editor-tool-text-active, #ffffff)}.tool-btn.active svg{color:var(--feedback-canvas-editor-tool-text-active, #ffffff)}.tool-btn:disabled{opacity:0.4;cursor:not-allowed;color:var(--feedback-canvas-editor-tool-text-color, #333)}.toolbar-divider{width:1px;height:24px;background:var(--feedback-canvas-editor-divider-color, #e0e0e0);margin:0 4px}.undo-btn{background:var(--feedback-canvas-editor-tool-bg-color, #ffffff) !important;border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0) !important}.undo-btn:hover:not(:disabled){background:var(--feedback-canvas-editor-tool-bg-hover, #f0f0f0) !important}.color-palette{display:flex;align-items:center;gap:6px}.color-slot-wrapper{position:relative;display:flex;align-items:center}.color-btn{width:32px;height:32px;border-radius:6px;border:2px solid transparent;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0)}.color-btn:hover{transform:scale(1.1)}.color-btn.active{border-color:var(--feedback-primary-color, #0070f4);box-shadow:0 0 0 2px rgba(0, 112, 244, 0.2)}.color-btn.editing{border-color:var(--feedback-primary-color, #0070f4)}.color-picker-dropdown{position:absolute;top:100%;left:0;z-index:1000;margin-top:4px;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);border-radius:6px;padding:8px;box-shadow:0 4px 12px rgba(0, 0, 0, 0.1)}.color-picker-dropdown input[type=\"color\"]{width:40px;height:40px;border:none;border-radius:4px;cursor:pointer}.size-control{display:flex;align-items:center;gap:8px;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);border-radius:6px;padding:6px 12px}.size-slider{width:80px;height:20px;-webkit-appearance:none;appearance:none;background:transparent;cursor:pointer}.size-slider::-webkit-slider-track{width:100%;height:4px;background:var(--feedback-canvas-editor-slider-track, #e0e0e0);border-radius:2px}.size-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;background:var(--feedback-primary-color, #0070f4);border-radius:50%;cursor:pointer}.size-slider::-moz-range-track{width:100%;height:4px;background:var(--feedback-canvas-editor-slider-track, #e0e0e0);border-radius:2px;border:none}.size-slider::-moz-range-thumb{width:16px;height:16px;background:var(--feedback-primary-color, #0070f4);border-radius:50%;border:none;cursor:pointer}.size-slider::-ms-track{width:100%;height:4px;background:var(--feedback-canvas-editor-slider-track, #e0e0e0);border-radius:2px;border:none;color:transparent}.size-slider::-ms-thumb{width:16px;height:16px;background:var(--feedback-primary-color, #0070f4);border-radius:50%;border:none;cursor:pointer}.size-value{font-weight:500;color:var(--feedback-canvas-editor-tool-text-color, #333);font-size:12px;min-width:30px}.selected-annotation-controls{border-left:2px solid var(--feedback-canvas-editor-divider-color, #e0e0e0);padding-left:12px;margin-left:8px;min-width:200px}.text-controls{display:flex;flex-direction:row;align-items:center;gap:12px}.font-size-control,.border-width-control{display:flex;align-items:center;gap:6px}.font-size-control label,.border-width-control label{font-size:12px;color:var(--feedback-canvas-editor-tool-text-color, #333);font-weight:500;min-width:35px}.action-btn{display:flex;align-items:center;gap:6px;padding:5px 12px;border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;transition:all 0.2s ease;min-width:65px;justify-content:center;height:36px}.action-btn.secondary{background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);color:var(--feedback-canvas-editor-tool-text-color, #333);border-color:var(--feedback-canvas-editor-border-color, #e0e0e0)}.action-btn.secondary:hover{background:var(--feedback-canvas-editor-tool-bg-hover, #f0f0f0)}.action-btn.primary{background:var(--feedback-primary-color, #0070f4);color:#ffffff;border-color:var(--feedback-primary-color, #0070f4)}.action-btn.primary:hover{background:#0056cc;border-color:#0056cc}.action-btn.small{height:28px;padding:4px 8px;font-size:12px;min-width:65px}.shape-controls{display:flex;flex-direction:column;gap:8px}.canvas-editor-content{flex:1;display:flex;align-items:center;justify-content:center;padding:16px;background:var(--feedback-canvas-editor-content-bg, #f5f5f5);overflow:hidden;min-height:0;min-width:0}.annotation-canvas{max-width:100%;max-height:100%;width:auto;height:auto;cursor:crosshair;border-radius:6px;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);background:#ffffff;transition:box-shadow 0.3s ease;object-fit:contain;display:block}.annotation-canvas:hover{box-shadow:0px 2px 4px 0px rgba(60, 64, 67, .35), 0px 4px 12px 4px rgba(60, 64, 67, .20)}@media screen and (max-width: 768px){.canvas-editor-modal{width:100vw;height:100vh;border-radius:0}.canvas-editor-toolbar{flex-direction:column;align-items:stretch;gap:8px}.toolbar-section{justify-content:center}.selected-annotation-controls{border-left:none;border-top:2px solid var(--feedback-canvas-editor-divider-color, #e0e0e0);padding-left:0;padding-top:8px;margin-left:0;margin-top:8px;min-width:auto}}";
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const CanvasEditor = class {
|
|
6
6
|
constructor(hostRef) {
|
|
7
7
|
registerInstance(this, hostRef);
|
|
8
|
-
this.
|
|
9
|
-
this.
|
|
10
|
-
this.
|
|
11
|
-
this.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.messagePlaceholder = 'Comments';
|
|
38
|
-
this.modalTitle = 'Share your feedback';
|
|
39
|
-
this.modalTitleError = 'Oops!';
|
|
40
|
-
this.modalTitleSuccess = 'Thanks for your feedback!';
|
|
41
|
-
this.privacyPolicyText = "I have read and expressly consent to the terms of the <a href='https://pushfeedback.com/privacy'>Privacy Policy</a>.";
|
|
42
|
-
this.ratingPlaceholder = 'Was this page helpful?';
|
|
43
|
-
this.ratingStarsPlaceholder = 'How would you rate this page?';
|
|
44
|
-
this.screenshotAttachedText = 'Screenshot attached';
|
|
45
|
-
this.screenshotButtonText = 'Add a screenshot';
|
|
46
|
-
this.screenshotTakingText = 'Taking screenshot...';
|
|
47
|
-
this.screenshotTopbarText = 'Select an element on this page';
|
|
48
|
-
this.sendButtonText = 'Send';
|
|
49
|
-
this.successMessage = '';
|
|
50
|
-
}
|
|
51
|
-
componentWillLoad() {
|
|
52
|
-
if (!this.sessionId) {
|
|
53
|
-
let storedSessionId = localStorage.getItem('pushfeedback_sessionid');
|
|
54
|
-
if (!storedSessionId) {
|
|
55
|
-
storedSessionId = this.generateRandomSessionId();
|
|
56
|
-
localStorage.setItem('pushfeedback_sessionid', storedSessionId);
|
|
57
|
-
this.sessionId = storedSessionId;
|
|
8
|
+
this.screenshotReady = createEvent(this, "screenshotReady", 7);
|
|
9
|
+
this.screenshotCancelled = createEvent(this, "screenshotCancelled", 7);
|
|
10
|
+
this.screenshotFailed = createEvent(this, "screenshotFailed", 7);
|
|
11
|
+
this.openScreenShot = async () => {
|
|
12
|
+
// Show loading state immediately
|
|
13
|
+
this.takingScreenshot = true;
|
|
14
|
+
// Clear any previous annotations when taking a new screenshot
|
|
15
|
+
this.annotations = [];
|
|
16
|
+
this.currentAnnotation = null;
|
|
17
|
+
this.isDrawing = false;
|
|
18
|
+
this.hoveredAnnotation = null;
|
|
19
|
+
// Hide any feedback buttons on the page
|
|
20
|
+
this.hideAllFeedbackElements();
|
|
21
|
+
try {
|
|
22
|
+
// Wait a moment for UI to update before capturing
|
|
23
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
24
|
+
// Capture viewport screenshot using browser API
|
|
25
|
+
const dataUrl = await this.captureViewportScreenshot();
|
|
26
|
+
this.originalImageData = dataUrl;
|
|
27
|
+
// Reset loading state
|
|
28
|
+
this.takingScreenshot = false;
|
|
29
|
+
// Go directly to canvas editor
|
|
30
|
+
this.showCanvasEditor = true;
|
|
31
|
+
// Restore feedback elements visibility
|
|
32
|
+
this.showAllFeedbackElements();
|
|
33
|
+
// Initialize canvas after a short delay to ensure DOM is ready
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
this.initializeCanvas();
|
|
36
|
+
}, 100);
|
|
58
37
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
let adjustement = 0;
|
|
68
|
-
if (this.isSafariBrowser()) {
|
|
69
|
-
adjustement = 5;
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error('Failed to capture screenshot:', error);
|
|
40
|
+
// Reset loading state on error
|
|
41
|
+
this.takingScreenshot = false;
|
|
42
|
+
// Restore feedback elements on error
|
|
43
|
+
this.showAllFeedbackElements();
|
|
44
|
+
// Show error message to user
|
|
45
|
+
this.handleScreenshotError(error);
|
|
70
46
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
connectedCallback() {
|
|
78
|
-
this.feedbackModal = document.createElement('feedback-modal');
|
|
79
|
-
const props = [
|
|
80
|
-
'customFont',
|
|
81
|
-
'emailAddress',
|
|
82
|
-
'fetchData',
|
|
83
|
-
'hideEmail',
|
|
84
|
-
'hidePrivacyPolicy',
|
|
85
|
-
'hideRating',
|
|
86
|
-
'hideScreenshotButton',
|
|
87
|
-
'isEmailRequired',
|
|
88
|
-
'modalPosition',
|
|
89
|
-
'project',
|
|
90
|
-
'rating',
|
|
91
|
-
'ratingMode',
|
|
92
|
-
'canvasEditorTitle',
|
|
93
|
-
'canvasEditorCancelText',
|
|
94
|
-
'canvasEditorSaveText',
|
|
95
|
-
'emailPlaceholder',
|
|
96
|
-
'errorMessage',
|
|
97
|
-
'errorMessage403',
|
|
98
|
-
'errorMessage404',
|
|
99
|
-
'footerText',
|
|
100
|
-
'messagePlaceholder',
|
|
101
|
-
'metadata',
|
|
102
|
-
'modalTitle',
|
|
103
|
-
'modalTitleError',
|
|
104
|
-
'modalTitleSuccess',
|
|
105
|
-
'privacyPolicyText',
|
|
106
|
-
'ratingPlaceholder',
|
|
107
|
-
'ratingStarsPlaceholder',
|
|
108
|
-
'screenshotAttachedText',
|
|
109
|
-
'screenshotButtonText',
|
|
110
|
-
'screenshotTakingText',
|
|
111
|
-
'screenshotTopbarText',
|
|
112
|
-
'sendButtonText',
|
|
113
|
-
'successMessage',
|
|
114
|
-
];
|
|
115
|
-
props.forEach((prop) => {
|
|
116
|
-
this.feedbackModal[prop] = this[prop];
|
|
117
|
-
});
|
|
118
|
-
document.body.appendChild(this.feedbackModal);
|
|
119
|
-
}
|
|
120
|
-
disconnectedCallback() {
|
|
121
|
-
document.body.removeChild(this.feedbackModal);
|
|
122
|
-
}
|
|
123
|
-
generateRandomSessionId(length = 16) {
|
|
124
|
-
return Math.random().toString(36).substr(2, length);
|
|
125
|
-
}
|
|
126
|
-
isSafariBrowser() {
|
|
127
|
-
const isSafari = /safari/i.test(navigator.userAgent) && !/chrome/i.test(navigator.userAgent);
|
|
128
|
-
return isSafari;
|
|
129
|
-
}
|
|
130
|
-
loadInterFont() {
|
|
131
|
-
const link = document.createElement('link');
|
|
132
|
-
link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap';
|
|
133
|
-
link.rel = 'stylesheet';
|
|
134
|
-
document.head.appendChild(link);
|
|
135
|
-
}
|
|
136
|
-
showModal() {
|
|
137
|
-
if (this.submit) {
|
|
138
|
-
this.submitRatingFeedback();
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
this.feedbackModal.openModal();
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
async submitRatingFeedback() {
|
|
145
|
-
try {
|
|
146
|
-
const body = {
|
|
147
|
-
url: window.location.href,
|
|
148
|
-
project: this.project,
|
|
149
|
-
rating: this.rating || -1,
|
|
150
|
-
ratingMode: this.ratingMode,
|
|
151
|
-
message: '',
|
|
152
|
-
metadata: this.metadata,
|
|
153
|
-
session: localStorage.getItem('pushfeedback_sessionid') || '',
|
|
154
|
-
};
|
|
155
|
-
const res = await fetch('https://app.pushfeedback.com/api/feedback/', {
|
|
156
|
-
method: 'POST',
|
|
157
|
-
body: JSON.stringify(body),
|
|
158
|
-
headers: {
|
|
159
|
-
'Content-Type': 'application/json',
|
|
160
|
-
},
|
|
47
|
+
};
|
|
48
|
+
this.hideAllFeedbackElements = () => {
|
|
49
|
+
// Hide all feedback buttons and modals on the page
|
|
50
|
+
const feedbackElements = document.querySelectorAll('feedback-button, feedback-modal');
|
|
51
|
+
feedbackElements.forEach(element => {
|
|
52
|
+
element.style.visibility = 'hidden';
|
|
161
53
|
});
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
54
|
+
};
|
|
55
|
+
this.showAllFeedbackElements = () => {
|
|
56
|
+
// Show all feedback buttons and modals on the page
|
|
57
|
+
const feedbackElements = document.querySelectorAll('feedback-button, feedback-modal');
|
|
58
|
+
feedbackElements.forEach(element => {
|
|
59
|
+
element.style.visibility = 'visible';
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
this.handleScreenshotError = (error) => {
|
|
63
|
+
let errorMessage = this.screenshotErrorGeneral;
|
|
64
|
+
if (error.name === 'NotAllowedError') {
|
|
65
|
+
errorMessage += ' ' + this.screenshotErrorPermission;
|
|
66
|
+
}
|
|
67
|
+
else if (error.name === 'NotSupportedError') {
|
|
68
|
+
errorMessage += ' ' + this.screenshotErrorNotSupported;
|
|
69
|
+
}
|
|
70
|
+
else if (error.name === 'NotFoundError') {
|
|
71
|
+
errorMessage += ' ' + this.screenshotErrorNotFound;
|
|
72
|
+
}
|
|
73
|
+
else if (error.name === 'AbortError') {
|
|
74
|
+
errorMessage += ' ' + this.screenshotErrorCancelled;
|
|
75
|
+
}
|
|
76
|
+
else if (error.message && error.message.includes('not supported')) {
|
|
77
|
+
errorMessage += ' ' + this.screenshotErrorBrowserNotSupported;
|
|
165
78
|
}
|
|
166
79
|
else {
|
|
167
|
-
|
|
168
|
-
const response = {
|
|
169
|
-
status: res.status,
|
|
170
|
-
message: errorText,
|
|
171
|
-
};
|
|
172
|
-
this.feedbackError.emit({ error: response });
|
|
80
|
+
errorMessage += ' ' + this.screenshotErrorUnexpected;
|
|
173
81
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const response = {
|
|
177
|
-
status: 500,
|
|
178
|
-
message: error,
|
|
179
|
-
};
|
|
180
|
-
this.feedbackError.emit({ error: response });
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
render() {
|
|
184
|
-
return (h(Host, null, h("a", { class: `feedback-button-content feedback-button-content--${this.buttonStyle} feedback-button-content--${this.buttonPosition} ${this.customFont ? 'feedback-button-content--custom-font' : ''} ${this.hideMobile ? 'feedback-button-content--hide-mobile' : ''}`, onClick: () => this.showModal() }, !this.hideIcon && this.buttonStyle != 'default' && (h("span", { class: "feedback-button-content-icon" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#fff", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "icon-edit" }, h("path", { d: "M12 20h9" }), h("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })))), h("slot", null))));
|
|
185
|
-
}
|
|
186
|
-
get el() { return getElement(this); }
|
|
187
|
-
};
|
|
188
|
-
FeedbackButton.style = feedbackButtonCss;
|
|
189
|
-
|
|
190
|
-
const feedbackModalCss = ".text-center{flex-grow:1;text-align:center}.feedback-modal-wrapper *{font-family:var(--feedback-font-family)}.feedback-modal-wrapper--custom-font *{font-family:inherit}.feedback-modal-wrapper{position:absolute;z-index:var(--feedback-modal-modal-wrapper-z-index)}.feedback-overlay{background-color:var(--feedback-modal-screenshot-bg-color);height:100%;left:0;opacity:0;position:fixed;top:0;width:100%;z-index:var(--feedback-modal-screnshot-z-index);transition:opacity 0.2s ease-out}.feedback-overlay--visible{opacity:1}.feedback-modal{display:inline-block;position:relative}.feedback-modal-content{background-color:var(--feedback-modal-content-bg-color);border-color:1px solid var(--feedback-modal-header-text-color);border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);box-sizing:border-box;color:var(--feedback-modal-content-text-color);display:flex;flex-direction:column;left:50%;max-width:90%;padding:20px;position:fixed;top:50%;transform:translate(-50%, -50%) scale(0.95);opacity:0;width:100%;z-index:var(--feedback-modal-content-z-index);transition:transform 0.2s ease-out, opacity 0.2s ease-out}.feedback-modal-content--open{transform:translate(-50%, -50%) scale(1);opacity:1}.feedback-modal-header{align-items:center;color:var(--feedback-modal-header-text-color);display:flex;font-size:var(--feedback-header-font-size);font-weight:var(--feedback-modal-header-font-weight);justify-content:space-between;margin-bottom:20px}.feedback-modal-rating-buttons{width:100%;margin-bottom:20px}.feedback-modal-rating-button{padding:0;background-color:transparent;border:transparent;margin-right:5px;cursor:pointer}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button{border:1px solid var(--feedback-modal-button-border-color);border-radius:var(--feedback-modal-button-border-radius);color:var(--feedback-modal-button-text-color);font-size:var(--feedback-modal-button-font-size);font-weight:500;margin-right:10px;justify-content:center;padding:5px 10px}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--selected{background-color:var(--feedback-modal-button-bg-color-active);border:1px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-selected-color)}.feedback-modal-rating-buttons svg{stroke:var(--feedback-modal-rating-button-color);cursor:pointer}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button--selected svg{fill:var(--feedback-modal-rating-button-stars-selected-color);stroke:var(--feedback-modal-rating-button-stars-selected-color)}.feedback-modal-text textarea{background-color:var(--feedback-modal-input-bg-color);border:1px solid var(--feedback-modal-input-border-color);border-radius:var(--feedback-modal-input-border-radius);box-sizing:border-box;color:var(--feedback-modal-input-text-color);font-size:var(--feedback-modal-input-font-size);margin-bottom:20px;height:100px;min-height:100px;padding:10px;resize:vertical;width:100%}.feedback-modal-email input{background-color:var(--feedback-modal-input-bg-color);border:1px solid var(--feedback-modal-input-border-color);border-radius:var(--feedback-modal-input-border-radius);box-sizing:border-box;color:var(--feedback-modal-input-text-color);font-size:var(--feedback-modal-input-font-size);margin-bottom:20px;height:40px;padding:10px;width:100%;margin-bottom:20px}.feedback-modal-privacy{font-size:var(--feedback-modal-input-font-size);margin-bottom:20px}.feedback-modal-text textarea:focus,.feedback-modal-email input:focus{border:1px solid var(--feedback-modal-input-border-color-focused);outline:none}.feedback-modal-buttons{display:flex;flex-direction:column}.feedback-modal-buttons .feedback-modal-button{margin-bottom:20px}.feedback-modal-button{align-items:center;background-color:transparent;border:1px solid var(--feedback-modal-button-border-color);border-radius:var(--feedback-modal-button-border-radius);color:var(--feedback-modal-button-text-color);cursor:pointer;display:flex;font-size:var(--feedback-modal-button-font-size);font-weight:500;justify-content:center;min-height:40px;padding:5px 10px}.feedback-modal-button svg{margin-right:6px}.feedback-modal-button path{fill:var(--feedback-modal-button-icon-color)}.feedback-modal-button:hover path,.feedback-modal-button--active path{fill:var(--feedback-modal-button-icon-color-active)}.feedback-modal-button--submit{background-color:var(--feedback-modal-button-submit-bg-color);border:1px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-submit-text-color)}.feedback-modal-button:hover,.feedback-modal-button--active{background-color:var(--feedback-modal-button-bg-color-active);border:1px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active)}.feedback-modal-button--submit:hover{background-color:var(--feedback-modal-button-submit-bg-color-hover);border:1px solid var(--feedback-modal-button-submit-border-color-hover);color:var(--feedback-modal-button-submit-text-color-hover)}.feedback-modal-input-heading{display:block;font-size:14px;font-weight:300;padding-bottom:10px}.feedback-modal-footer{font-size:12px;text-align:center}.feedback-modal-footer a{color:var(--feedback-modal-footer-link);font-weight:500;text-decoration:none}.feedback-logo,.feedback-footer-text{display:block;text-align:center;margin-top:5px}.feedback-footer-text{margin-top:10px;line-height:1.5}.feedback-modal-close{background-color:var(--feedback-modal-close-bg-color);border:0;border-radius:50%;cursor:pointer;height:22px;margin-left:auto;padding:0;width:22px}.feedback-modal-close svg{stroke:var(--feedback-modal-close-color)}.feedback-modal-screenshot{background-color:var(--feedback-modal-screenshot-bg-color);height:100%;left:0;position:fixed;top:0;width:100%;z-index:var(--feedback-modal-screnshot-z-index)}.feedback-modal-screenshot-header{align-items:center;background-color:var(--feedback-modal-screenshot-header-bg-color);border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);box-sizing:border-box;color:var(--feedback-modal-screenshot-header-text-color);cursor:pointer;display:flex;left:50%;top:20px;transform:translateX(-50%);padding:10px;position:fixed;width:max-content;z-index:var(--feedback-modal-screenshot-header-z-index)}.feedback-modal-screenshot-close{height:24px;padding-left:10px;width:24px}.feedback-modal-screenshot-close svg{stroke:var(--feedback-modal-close-color)}.feedback-modal-message{font-size:var(--feedback-modal-message-font-size);margin-top:0}.feedback-modal-element-hover{background-color:transparent;cursor:pointer;border:1px solid var(--feedback-modal-element-hover-border-color)}.feedback-modal-element-selected{background-color:transparent;border:3px solid var(--feedback-modal-element-selected-border-color) !important;box-shadow:0 0 0 2px rgba(0, 123, 255, 0.3) !important}.screenshot-preview{display:inline-block;width:30px;height:30px;overflow:hidden;border-radius:4px;margin-right:10px;box-shadow:0 2px 4px rgba(0, 0, 0, 0.1);cursor:pointer;transition:transform 0.2s ease}.screenshot-preview:hover{transform:scale(1.1)}.screenshot-preview img{width:100%;height:100%;object-fit:cover}.screenshot-loading{display:inline-flex;align-items:center;margin-right:8px}.canvas-editor-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:var(--feedback-modal-screenshot-bg-color);z-index:10001;display:flex;align-items:center;justify-content:center}.canvas-editor-modal{width:95vw;height:98vh;background:var(--feedback-canvas-editor-bg-color);border-radius:var(--feedback-modal-content-border-radius);display:flex;flex-direction:column;overflow:hidden;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15)}.canvas-editor-header{background:var(--feedback-canvas-editor-header-bg-color);border-bottom:1px solid var(--feedback-canvas-editor-border-color);padding:12px 16px;display:flex;flex-direction:column;gap:12px;flex-shrink:0}.canvas-editor-title h3{margin:0;font-size:var(--feedback-modal-header-font-size);font-weight:var(--feedback-modal-header-font-weight);color:var(--feedback-modal-header-text-color);font-family:var(--feedback-modal-header-font-family)}.canvas-editor-toolbar{display:flex;align-items:center;gap:20px;flex-wrap:wrap}.toolbar-section{display:flex;align-items:center}.toolbar-section:last-child{margin-left:auto;gap:10px}.tool-group{display:flex;align-items:center;background:var(--feedback-canvas-editor-tool-bg-color);border:1px solid var(--feedback-canvas-editor-border-color);border-radius:var(--feedback-modal-button-border-radius);padding:4px;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .10)}.tool-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;background:none;border-radius:var(--feedback-modal-button-border-radius);cursor:pointer;color:var(--feedback-canvas-editor-tool-text-color);transition:all 0.2s ease;position:relative}.tool-btn:hover{background:var(--feedback-canvas-editor-tool-bg-hover);color:var(--feedback-modal-button-text-color-active)}.tool-btn.active{background:var(--feedback-canvas-editor-tool-bg-active);color:var(--feedback-canvas-editor-tool-text-active)}.tool-btn:disabled{opacity:0.4;cursor:not-allowed}.tool-btn:disabled:hover{background:none;color:var(--feedback-canvas-editor-tool-text-color)}.toolbar-divider{width:1px;height:20px;background:var(--feedback-canvas-editor-divider-color);margin:0 6px}.undo-btn{background:var(--feedback-canvas-editor-tool-bg-color) !important;border:1px solid var(--feedback-canvas-editor-border-color) !important}.undo-btn:hover:not(:disabled){background:var(--feedback-canvas-editor-tool-bg-hover) !important}.color-palette{display:flex;align-items:center;gap:6px;background:var(--feedback-canvas-editor-tool-bg-color);border:1px solid var(--feedback-canvas-editor-border-color);border-radius:var(--feedback-modal-button-border-radius);padding:6px;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .10)}.color-slot-wrapper{position:relative}.color-btn{width:28px;height:28px;border-radius:var(--feedback-modal-button-border-radius);border:2px solid transparent;cursor:pointer;transition:all 0.2s ease;position:relative;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .15);display:flex;align-items:center;justify-content:center}.color-btn:hover{transform:scale(1.05);box-shadow:0px 2px 4px 0px rgba(60, 64, 67, .25)}.color-btn.active{border-color:var(--feedback-primary-color);transform:scale(1.1);box-shadow:0 0 0 2px rgba(0, 112, 244, 0.2)}.color-btn.editing{border-color:var(--feedback-highlight-color);box-shadow:0 0 0 2px rgba(255, 180, 34, 0.3)}.color-btn.editing:hover{border-color:var(--feedback-highlight-color);box-shadow:0 0 0 2px rgba(255, 180, 34, 0.4)}.color-picker-dropdown{position:absolute;top:100%;left:50%;transform:translateX(-50%);margin-top:6px;background:var(--feedback-canvas-editor-tool-bg-color);border:1px solid var(--feedback-canvas-editor-border-color);border-radius:var(--feedback-modal-button-border-radius);padding:6px;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);z-index:1000}.color-picker-dropdown input[type=\"color\"]{width:50px;height:32px;border:none;border-radius:var(--feedback-modal-button-border-radius);cursor:pointer}.size-control{display:flex;align-items:center;gap:10px;background:var(--feedback-canvas-editor-tool-bg-color);border:1px solid var(--feedback-canvas-editor-border-color);border-radius:var(--feedback-modal-button-border-radius);padding:6px 12px;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .10)}.size-slider{width:70px;height:4px;border-radius:2px;background:var(--feedback-canvas-editor-slider-track);outline:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer;border:none}.size-slider::-webkit-slider-track{width:100%;height:4px;cursor:pointer;background:var(--feedback-canvas-editor-slider-track);border-radius:2px;border:none;box-shadow:none}.size-slider::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;border-radius:50%;background:var(--feedback-primary-color);cursor:pointer;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .20);border:none;margin-top:-5px;}.size-slider::-moz-range-track{width:100%;height:4px;cursor:pointer;background:var(--feedback-canvas-editor-slider-track);border-radius:2px;border:none;box-shadow:none}.size-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;background:var(--feedback-primary-color);cursor:pointer;border:none;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .20)}.size-slider::-moz-range-progress{background:var(--feedback-canvas-editor-slider-track);height:4px;border-radius:2px}.size-slider::-ms-track{width:100%;height:4px;cursor:pointer;background:transparent;border-color:transparent;color:transparent}.size-slider::-ms-fill-lower{background:var(--feedback-canvas-editor-slider-track);border-radius:2px}.size-slider::-ms-fill-upper{background:var(--feedback-canvas-editor-slider-track);border-radius:2px}.size-slider::-ms-thumb{width:14px;height:14px;border-radius:50%;background:var(--feedback-primary-color);cursor:pointer;border:none}.size-value{font-weight:500;color:var(--feedback-canvas-editor-tool-text-color);font-size:var(--feedback-text-font-size);min-width:30px}.action-btn{display:flex;align-items:center;gap:6px;padding:5px 12px;border:1px solid var(--feedback-canvas-editor-action-secondary-border);border-radius:var(--feedback-modal-button-border-radius);cursor:pointer;font-size:var(--feedback-modal-button-font-size);font-weight:500;transition:all 0.2s ease;min-width:65px;justify-content:center;height:36px;font-family:var(--feedback-font-family)}.action-btn.secondary{background:var(--feedback-canvas-editor-action-secondary-bg);color:var(--feedback-canvas-editor-action-secondary-text);border-color:var(--feedback-canvas-editor-action-secondary-border)}.action-btn.secondary:hover{background:var(--feedback-canvas-editor-tool-bg-hover);color:var(--feedback-modal-button-text-color-active);border-color:var(--feedback-modal-button-border-color-active)}.action-btn.primary{background:var(--feedback-canvas-editor-action-primary-bg);color:var(--feedback-canvas-editor-action-primary-text);border-color:var(--feedback-modal-button-border-color-active)}.action-btn.primary:hover{background:var(--feedback-modal-button-submit-bg-color-hover);border-color:var(--feedback-modal-button-submit-border-color-hover)}.canvas-editor-content{flex:1;display:flex;align-items:center;justify-content:center;padding:16px;background:var(--feedback-canvas-editor-content-bg);overflow:hidden;min-height:0;min-width:0}.annotation-canvas{max-width:100%;max-height:100%;width:auto;height:auto;cursor:crosshair;border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);background:var(--feedback-white-color);transition:box-shadow 0.3s ease;object-fit:contain;display:block}.annotation-canvas:hover{box-shadow:0px 2px 4px 0px rgba(60, 64, 67, .35), 0px 4px 12px 4px rgba(60, 64, 67, .20)}@media screen and (min-width: 768px){.feedback-modal-content{max-width:var(--feedback-modal-content-max-width)}.feedback-modal-content.feedback-modal-content--bottom-right{bottom:var(--feedback-modal-content-position-bottom);left:initial;right:var(--feedback-modal-content-position-right);top:initial;transform:initial}.feedback-modal-content.feedback-modal-content--bottom-left{bottom:var(--feedback-modal-content-position-bottom);left:var(--feedback-modal-content-position-left);top:initial;transform:initial}.feedback-modal-content.feedback-modal-content--top-right{right:var(--feedback-modal-content-position-right);top:var(--feedback-modal-content-position-top);transform:initial}.feedback-modal-content.feedback-modal-content--top-left{left:var(--feedback-modal-content-position-left);top:var(--feedback-modal-content-position-top);transform:initial}.feedback-modal-content.feedback-modal-content--center-left{left:5px;right:auto;top:50%;transform:translateY(-50%)}.feedback-modal-content.feedback-modal-content--center-right{left:auto;right:5px;top:50%;transform:translateY(-50%)}.feedback-modal-content.feedback-modal-content--sidebar-left.feedback-modal-content--open,.feedback-modal-content.feedback-modal-content--sidebar-right.feedback-modal-content--open{transform:translateX(0)}.feedback-modal-content.feedback-modal-content--sidebar-left{max-width:var(--feedback-modal-content-sidebar-max-width);left:0;right:auto;height:100vh;top:0;transform:translateX(-100%);transition:transform 0.5s ease-in-out;border-radius:0}.feedback-modal-content.feedback-modal-content--sidebar-right{max-width:var(--feedback-modal-content-sidebar-max-width);left:auto;right:0;height:100vh;top:0;transform:translateX(100%);transition:transform 0.5s ease-in-out;border-radius:0}.feedback-modal-text textarea{height:150px;min-height:150px}.feedback-modal-content.feedback-modal-content--bottom-right{transform:translateY(20px)}.feedback-modal-content.feedback-modal-content--bottom-right.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--bottom-left{transform:translateY(20px)}.feedback-modal-content.feedback-modal-content--bottom-left.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--top-right{transform:translateY(-20px)}.feedback-modal-content.feedback-modal-content--top-right.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--top-left{transform:translateY(-20px)}.feedback-modal-content.feedback-modal-content--top-left.feedback-modal-content--open{transform:translateY(0)}}@media (max-width: 768px){.canvas-editor-modal{width:100vw;height:100vh;border-radius:0}.canvas-editor-header{padding:8px 12px;gap:8px}.canvas-editor-toolbar{flex-direction:column;align-items:stretch;gap:12px}.toolbar-section{justify-content:center}.toolbar-section:last-child{margin-left:0;justify-content:stretch;gap:8px}.action-btn{flex:1;min-width:auto}.canvas-editor-content{padding:8px}.tool-group{flex-wrap:wrap;justify-content:center}.color-palette{flex-wrap:wrap;justify-content:center}.size-control{flex-direction:column;gap:6px;text-align:center}.size-slider{width:100px}}@keyframes feather-spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.feather-loader{animation:feather-spin 1s linear infinite;display:block}.screenshot-error-notification{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:10001;max-width:500px;width:90%;animation:slideDown 0.3s ease-out}@keyframes slideDown{from{opacity:0;transform:translateX(-50%) translateY(-20px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}.screenshot-error-content{background:#fee;border:1px solid #fcc;border-radius:8px;padding:12px 16px;display:flex;align-items:center;gap:12px;box-shadow:0 4px 12px rgba(0, 0, 0, 0.15);color:#c53030}.screenshot-error-content svg:first-child{color:#e53e3e;flex-shrink:0}.screenshot-error-content span{flex:1;font-size:14px;line-height:1.4;font-weight:500}.error-close-btn{background:none;border:none;cursor:pointer;padding:4px;border-radius:4px;color:#c53030;flex-shrink:0;transition:background-color 0.2s ease}.error-close-btn:hover{background:rgba(197, 48, 48, 0.1)}@media (max-width: 768px){.canvas-editor-toolbar .tool-group,.canvas-editor-toolbar .color-palette,.canvas-editor-toolbar .size-control,.canvas-editor-toolbar .toolbar-divider{display:none !important}.canvas-editor-toolbar{justify-content:center}.canvas-editor-toolbar .toolbar-section:first-child{display:none}.canvas-editor-toolbar .toolbar-section:nth-child(2){display:none}.canvas-editor-toolbar .toolbar-section:nth-child(3){display:none}.canvas-editor-title{display:none}}";
|
|
191
|
-
|
|
192
|
-
const FeedbackModal = class {
|
|
193
|
-
constructor(hostRef) {
|
|
194
|
-
registerInstance(this, hostRef);
|
|
195
|
-
this.feedbackSent = createEvent(this, "feedbackSent", 7);
|
|
196
|
-
this.feedbackError = createEvent(this, "feedbackError", 7);
|
|
197
|
-
this.onScrollDebounced = () => {
|
|
198
|
-
clearTimeout(this.scrollTimeout);
|
|
199
|
-
this.scrollTimeout = setTimeout(() => {
|
|
200
|
-
document.documentElement.classList.remove('feedback-modal-screenshot-closing');
|
|
201
|
-
document.documentElement.style.top = '';
|
|
202
|
-
window.removeEventListener('scroll', this.onScrollDebounced);
|
|
203
|
-
}, 200);
|
|
204
|
-
};
|
|
205
|
-
this.handleSubmit = async (event) => {
|
|
206
|
-
event.preventDefault();
|
|
207
|
-
if (this.isEmailRequired && !this.formEmail) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
this.resetOverflow();
|
|
211
|
-
this.showScreenshotMode = false;
|
|
212
|
-
this.showScreenshotTopBar = false;
|
|
213
|
-
this.showModal = false;
|
|
214
|
-
this.sending = true;
|
|
215
|
-
try {
|
|
216
|
-
const body = {
|
|
217
|
-
url: window.location.href,
|
|
218
|
-
message: this.formMessage,
|
|
219
|
-
email: this.formEmail,
|
|
220
|
-
project: this.project,
|
|
221
|
-
screenshot: this.encodedScreenshot,
|
|
222
|
-
rating: this.selectedRating,
|
|
223
|
-
ratingMode: this.ratingMode,
|
|
224
|
-
metadata: this.metadata,
|
|
225
|
-
verification: this.formVerification,
|
|
226
|
-
session: localStorage.getItem('pushfeedback_sessionid') || '',
|
|
227
|
-
};
|
|
228
|
-
const res = await fetch('https://app.pushfeedback.com/api/feedback/', {
|
|
229
|
-
method: 'POST',
|
|
230
|
-
body: JSON.stringify(body),
|
|
231
|
-
headers: {
|
|
232
|
-
'Content-Type': 'application/json',
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
if (res.status === 201) {
|
|
236
|
-
const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
|
|
237
|
-
this.feedbackSent.emit({ feedback: feedback_with_id });
|
|
238
|
-
this.formSuccess = true;
|
|
239
|
-
this.formError = false;
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
const errorText = await res.text();
|
|
243
|
-
const response = {
|
|
244
|
-
status: res.status,
|
|
245
|
-
message: errorText,
|
|
246
|
-
};
|
|
247
|
-
this.feedbackError.emit({ error: response });
|
|
248
|
-
this.formSuccess = false;
|
|
249
|
-
this.formError = true;
|
|
250
|
-
this.formErrorStatus = res.status;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
catch (error) {
|
|
254
|
-
const response = {
|
|
255
|
-
status: 500,
|
|
256
|
-
message: error,
|
|
257
|
-
};
|
|
258
|
-
this.feedbackError.emit({ error: response });
|
|
259
|
-
this.formSuccess = false;
|
|
260
|
-
this.formError = true;
|
|
261
|
-
this.formErrorStatus = 500;
|
|
262
|
-
}
|
|
263
|
-
finally {
|
|
264
|
-
this.sending = false;
|
|
265
|
-
this.showModal = true;
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
this.close = () => {
|
|
269
|
-
this.isAnimating = false;
|
|
270
|
-
setTimeout(() => {
|
|
271
|
-
this.sending = false;
|
|
272
|
-
this.showModal = false;
|
|
273
|
-
this.showScreenshotMode = false;
|
|
274
|
-
this.showScreenshotTopBar = false;
|
|
275
|
-
this.hasSelectedElement = false;
|
|
276
|
-
this.encodedScreenshot = null;
|
|
277
|
-
// Remove highlight from ALL selected elements
|
|
278
|
-
document.querySelectorAll('.feedback-modal-element-selected').forEach(el => {
|
|
279
|
-
el.classList.remove('feedback-modal-element-selected');
|
|
280
|
-
});
|
|
281
|
-
// Reset canvas editor states
|
|
282
|
-
this.takingScreenshot = false;
|
|
283
|
-
this.showPreviewModal = false;
|
|
284
|
-
this.showCanvasEditor = false;
|
|
285
|
-
this.annotations = [];
|
|
286
|
-
this.currentAnnotation = null;
|
|
287
|
-
this.isDrawing = false;
|
|
288
|
-
this.canvasRef = null;
|
|
289
|
-
this.canvasContext = null;
|
|
290
|
-
this.originalImageData = null;
|
|
291
|
-
// Reset error states
|
|
292
|
-
this.showScreenshotError = false;
|
|
293
|
-
this.screenshotError = '';
|
|
294
|
-
// Reset resizing states
|
|
295
|
-
this.isResizing = false;
|
|
296
|
-
this.resizingAnnotation = null;
|
|
297
|
-
this.resizeStartSize = 16;
|
|
298
|
-
this.resizeStartDimensions = null;
|
|
299
|
-
this.hoveredAnnotation = null;
|
|
300
|
-
this.resizeHandle = false;
|
|
301
|
-
// Reset form states
|
|
302
|
-
this.formSuccess = false;
|
|
303
|
-
this.formError = false;
|
|
304
|
-
this.formErrorStatus = 500;
|
|
305
|
-
this.formMessage = '';
|
|
306
|
-
this.formEmail = '';
|
|
307
|
-
this.resetOverflow();
|
|
308
|
-
}, 200);
|
|
309
|
-
};
|
|
310
|
-
this.openScreenShot = async () => {
|
|
311
|
-
// Show loading state immediately
|
|
312
|
-
this.takingScreenshot = true;
|
|
313
|
-
this.showScreenshotError = false;
|
|
314
|
-
// Clear any previous annotations when taking a new screenshot
|
|
315
|
-
this.annotations = [];
|
|
316
|
-
this.currentAnnotation = null;
|
|
317
|
-
this.isDrawing = false;
|
|
318
|
-
this.hoveredAnnotation = null;
|
|
319
|
-
// Hide the feedback modal temporarily to exclude it from screenshot
|
|
320
|
-
const wasModalVisible = this.showModal;
|
|
321
|
-
this.showModal = false;
|
|
322
|
-
// Also hide any feedback buttons on the page
|
|
323
|
-
this.hideAllFeedbackElements();
|
|
324
|
-
try {
|
|
325
|
-
// Wait a moment for UI to update before capturing
|
|
326
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
327
|
-
// Capture viewport screenshot using browser API
|
|
328
|
-
const dataUrl = await this.captureViewportScreenshot();
|
|
329
|
-
this.encodedScreenshot = dataUrl;
|
|
330
|
-
this.originalImageData = dataUrl;
|
|
331
|
-
// Reset loading state
|
|
332
|
-
this.takingScreenshot = false;
|
|
333
|
-
// Go directly to canvas editor (don't restore modal)
|
|
334
|
-
this.showCanvasEditor = true;
|
|
335
|
-
// Restore feedback elements visibility
|
|
336
|
-
this.showAllFeedbackElements();
|
|
337
|
-
// Initialize canvas after a short delay to ensure DOM is ready
|
|
338
|
-
setTimeout(() => {
|
|
339
|
-
this.initializeCanvas();
|
|
340
|
-
}, 100);
|
|
341
|
-
}
|
|
342
|
-
catch (error) {
|
|
343
|
-
console.error('Failed to capture screenshot:', error);
|
|
344
|
-
// Reset loading state on error
|
|
345
|
-
this.takingScreenshot = false;
|
|
346
|
-
// Restore modal and feedback elements on error
|
|
347
|
-
this.showModal = wasModalVisible;
|
|
348
|
-
this.showAllFeedbackElements();
|
|
349
|
-
// Show error message to user
|
|
350
|
-
this.handleScreenshotError(error);
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
this.hideAllFeedbackElements = () => {
|
|
354
|
-
// Hide all feedback buttons and modals on the page
|
|
355
|
-
const feedbackElements = document.querySelectorAll('feedback-button, feedback-modal');
|
|
356
|
-
feedbackElements.forEach(element => {
|
|
357
|
-
element.style.visibility = 'hidden';
|
|
358
|
-
});
|
|
359
|
-
};
|
|
360
|
-
this.showAllFeedbackElements = () => {
|
|
361
|
-
// Show all feedback buttons and modals on the page
|
|
362
|
-
const feedbackElements = document.querySelectorAll('feedback-button, feedback-modal');
|
|
363
|
-
feedbackElements.forEach(element => {
|
|
364
|
-
element.style.visibility = 'visible';
|
|
365
|
-
});
|
|
366
|
-
};
|
|
367
|
-
this.handleScreenshotError = (error) => {
|
|
368
|
-
let errorMessage = 'Failed to capture screenshot. ';
|
|
369
|
-
if (error.name === 'NotAllowedError') {
|
|
370
|
-
errorMessage += 'Permission denied. Please allow screen sharing to take screenshots.';
|
|
371
|
-
}
|
|
372
|
-
else if (error.name === 'NotSupportedError') {
|
|
373
|
-
errorMessage += 'Screen capture is not supported in this browser.';
|
|
374
|
-
}
|
|
375
|
-
else if (error.name === 'NotFoundError') {
|
|
376
|
-
errorMessage += 'No screen sources available for capture.';
|
|
377
|
-
}
|
|
378
|
-
else if (error.name === 'AbortError') {
|
|
379
|
-
errorMessage += 'Screenshot capture was cancelled.';
|
|
380
|
-
}
|
|
381
|
-
else if (error.message && error.message.includes('not supported')) {
|
|
382
|
-
errorMessage += 'Your browser does not support screen capture. Please use a browser like Chrome, Firefox, or Safari.';
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
errorMessage += 'An unexpected error occurred. Please try again.';
|
|
386
|
-
}
|
|
387
|
-
this.screenshotError = errorMessage;
|
|
388
|
-
this.showScreenshotError = true;
|
|
389
|
-
// Auto-hide error after 5 seconds
|
|
390
|
-
setTimeout(() => {
|
|
391
|
-
this.showScreenshotError = false;
|
|
392
|
-
}, 5000);
|
|
393
|
-
};
|
|
394
|
-
this.openCanvasEditor = (event) => {
|
|
395
|
-
if (event) {
|
|
396
|
-
event.stopPropagation();
|
|
397
|
-
}
|
|
398
|
-
this.showModal = false;
|
|
399
|
-
this.showCanvasEditor = true;
|
|
400
|
-
// Initialize canvas after a short delay to ensure DOM is ready
|
|
401
|
-
setTimeout(() => {
|
|
402
|
-
this.initializeCanvas();
|
|
403
|
-
}, 100);
|
|
82
|
+
// Just emit the error to parent - don't show internal notification
|
|
83
|
+
this.screenshotFailed.emit({ error: errorMessage });
|
|
404
84
|
};
|
|
405
85
|
this.closeCanvasEditor = () => {
|
|
406
86
|
this.showCanvasEditor = false;
|
|
407
|
-
this.
|
|
87
|
+
this.screenshotCancelled.emit();
|
|
408
88
|
};
|
|
409
89
|
this.saveAnnotations = () => {
|
|
410
90
|
if (this.canvasRef) {
|
|
411
91
|
// Create final image with annotations
|
|
412
92
|
const finalDataUrl = this.canvasRef.toDataURL('image/png');
|
|
413
|
-
this.
|
|
93
|
+
this.screenshotReady.emit({ screenshot: finalDataUrl });
|
|
414
94
|
}
|
|
415
95
|
this.showCanvasEditor = false;
|
|
416
|
-
this.showModal = true;
|
|
417
96
|
};
|
|
418
97
|
this.initializeCanvas = () => {
|
|
419
98
|
if (!this.canvasRef || !this.originalImageData)
|
|
@@ -425,31 +104,19 @@ const FeedbackModal = class {
|
|
|
425
104
|
this.canvasRef.width = img.width;
|
|
426
105
|
this.canvasRef.height = img.height;
|
|
427
106
|
// Get available container dimensions
|
|
428
|
-
const containerWidth = this.canvasRef.parentElement.clientWidth - 32;
|
|
107
|
+
const containerWidth = this.canvasRef.parentElement.clientWidth - 32;
|
|
429
108
|
const containerHeight = this.canvasRef.parentElement.clientHeight - 32;
|
|
430
109
|
// Calculate scale factors for both dimensions
|
|
431
110
|
const scaleX = containerWidth / img.width;
|
|
432
111
|
const scaleY = containerHeight / img.height;
|
|
433
112
|
// Use the smaller scale to ensure complete image fits
|
|
434
|
-
const scale = Math.min(scaleX, scaleY, 1);
|
|
113
|
+
const scale = Math.min(scaleX, scaleY, 1);
|
|
435
114
|
// Calculate final display dimensions
|
|
436
115
|
const displayWidth = img.width * scale;
|
|
437
116
|
const displayHeight = img.height * scale;
|
|
438
|
-
// Set CSS size for display
|
|
117
|
+
// Set CSS size for display
|
|
439
118
|
this.canvasRef.style.width = `${displayWidth}px`;
|
|
440
119
|
this.canvasRef.style.height = `${displayHeight}px`;
|
|
441
|
-
console.log('Canvas initialized with complete image fit:', {
|
|
442
|
-
originalWidth: img.width,
|
|
443
|
-
originalHeight: img.height,
|
|
444
|
-
displayWidth,
|
|
445
|
-
displayHeight,
|
|
446
|
-
scale,
|
|
447
|
-
scaleX,
|
|
448
|
-
scaleY,
|
|
449
|
-
containerWidth,
|
|
450
|
-
containerHeight,
|
|
451
|
-
usingScale: scale === scaleX ? 'width-limited' : 'height-limited'
|
|
452
|
-
});
|
|
453
120
|
// Draw the original image at full resolution
|
|
454
121
|
this.canvasContext.drawImage(img, 0, 0);
|
|
455
122
|
// Redraw existing annotations
|
|
@@ -482,7 +149,11 @@ const FeedbackModal = class {
|
|
|
482
149
|
switch (annotation.type) {
|
|
483
150
|
case 'rectangle':
|
|
484
151
|
this.canvasContext.strokeRect(annotation.startX, annotation.startY, annotation.width, annotation.height);
|
|
485
|
-
// Draw
|
|
152
|
+
// Draw selection indicator if this annotation is selected
|
|
153
|
+
if (this.selectedAnnotation === annotation) {
|
|
154
|
+
this.drawSelectionIndicator(annotation);
|
|
155
|
+
}
|
|
156
|
+
// Draw resize handles if this annotation is hovered
|
|
486
157
|
if (this.hoveredAnnotation === annotation) {
|
|
487
158
|
this.drawRectangleResizeHandles(annotation);
|
|
488
159
|
}
|
|
@@ -492,6 +163,10 @@ const FeedbackModal = class {
|
|
|
492
163
|
this.canvasContext.moveTo(annotation.startX, annotation.startY);
|
|
493
164
|
this.canvasContext.lineTo(annotation.endX, annotation.endY);
|
|
494
165
|
this.canvasContext.stroke();
|
|
166
|
+
// Draw selection indicator if this annotation is selected
|
|
167
|
+
if (this.selectedAnnotation === annotation) {
|
|
168
|
+
this.drawSelectionIndicator(annotation);
|
|
169
|
+
}
|
|
495
170
|
// Draw resize handles if this annotation is hovered
|
|
496
171
|
if (this.hoveredAnnotation === annotation) {
|
|
497
172
|
this.drawLineResizeHandles(annotation);
|
|
@@ -499,38 +174,73 @@ const FeedbackModal = class {
|
|
|
499
174
|
break;
|
|
500
175
|
case 'arrow':
|
|
501
176
|
this.drawArrow(annotation.startX, annotation.startY, annotation.endX, annotation.endY);
|
|
177
|
+
// Draw selection indicator if this annotation is selected
|
|
178
|
+
if (this.selectedAnnotation === annotation) {
|
|
179
|
+
this.drawSelectionIndicator(annotation);
|
|
180
|
+
}
|
|
502
181
|
// Draw resize handles if this annotation is hovered
|
|
503
182
|
if (this.hoveredAnnotation === annotation) {
|
|
504
|
-
this.drawLineResizeHandles(annotation);
|
|
183
|
+
this.drawLineResizeHandles(annotation);
|
|
505
184
|
}
|
|
506
185
|
break;
|
|
507
186
|
case 'text':
|
|
508
|
-
const fontSize = annotation.fontSize ||
|
|
187
|
+
const fontSize = annotation.fontSize || 24;
|
|
509
188
|
this.canvasContext.fillStyle = annotation.color;
|
|
510
189
|
this.canvasContext.font = `${fontSize}px Arial`;
|
|
511
190
|
this.canvasContext.fillText(annotation.text, annotation.x, annotation.y);
|
|
512
|
-
// Draw
|
|
513
|
-
if (this.
|
|
514
|
-
this.
|
|
191
|
+
// Draw selection indicator if this annotation is selected
|
|
192
|
+
if (this.selectedAnnotation === annotation) {
|
|
193
|
+
this.drawTextSelectionIndicator(annotation);
|
|
515
194
|
}
|
|
516
195
|
break;
|
|
517
196
|
}
|
|
518
197
|
};
|
|
519
|
-
// Draw
|
|
520
|
-
this.
|
|
521
|
-
if (!this.canvasContext
|
|
198
|
+
// Draw selection indicator for shapes
|
|
199
|
+
this.drawSelectionIndicator = (annotation) => {
|
|
200
|
+
if (!this.canvasContext)
|
|
201
|
+
return;
|
|
202
|
+
// Save current context
|
|
203
|
+
const originalStrokeStyle = this.canvasContext.strokeStyle;
|
|
204
|
+
const originalLineWidth = this.canvasContext.lineWidth;
|
|
205
|
+
// Draw selection outline
|
|
206
|
+
this.canvasContext.strokeStyle = '#0070F4';
|
|
207
|
+
this.canvasContext.lineWidth = 2;
|
|
208
|
+
this.canvasContext.setLineDash([5, 5]);
|
|
209
|
+
switch (annotation.type) {
|
|
210
|
+
case 'rectangle':
|
|
211
|
+
this.canvasContext.strokeRect(annotation.startX - 2, annotation.startY - 2, annotation.width + 4, annotation.height + 4);
|
|
212
|
+
break;
|
|
213
|
+
case 'line':
|
|
214
|
+
case 'arrow':
|
|
215
|
+
this.canvasContext.beginPath();
|
|
216
|
+
this.canvasContext.moveTo(annotation.startX, annotation.startY);
|
|
217
|
+
this.canvasContext.lineTo(annotation.endX, annotation.endY);
|
|
218
|
+
this.canvasContext.stroke();
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
// Restore context
|
|
222
|
+
this.canvasContext.setLineDash([]);
|
|
223
|
+
this.canvasContext.strokeStyle = originalStrokeStyle;
|
|
224
|
+
this.canvasContext.lineWidth = originalLineWidth;
|
|
225
|
+
};
|
|
226
|
+
// Draw selection indicator for text
|
|
227
|
+
this.drawTextSelectionIndicator = (annotation) => {
|
|
228
|
+
if (!this.canvasContext)
|
|
522
229
|
return;
|
|
523
|
-
const fontSize = annotation.fontSize ||
|
|
230
|
+
const fontSize = annotation.fontSize || 24;
|
|
524
231
|
const textWidth = this.getTextWidth(annotation.text, fontSize);
|
|
525
|
-
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
// Draw
|
|
529
|
-
this.canvasContext.
|
|
530
|
-
this.canvasContext.strokeStyle = '#ffffff';
|
|
232
|
+
// Save current context
|
|
233
|
+
const originalStrokeStyle = this.canvasContext.strokeStyle;
|
|
234
|
+
const originalLineWidth = this.canvasContext.lineWidth;
|
|
235
|
+
// Draw selection outline around text
|
|
236
|
+
this.canvasContext.strokeStyle = '#0070F4';
|
|
531
237
|
this.canvasContext.lineWidth = 2;
|
|
532
|
-
this.canvasContext.
|
|
533
|
-
this.canvasContext.strokeRect(
|
|
238
|
+
this.canvasContext.setLineDash([3, 3]);
|
|
239
|
+
this.canvasContext.strokeRect(annotation.x - 4, annotation.y - fontSize - 4, textWidth + 8, fontSize + 8);
|
|
240
|
+
// Restore context
|
|
241
|
+
this.canvasContext.setLineDash([]);
|
|
242
|
+
this.canvasContext.strokeStyle = originalStrokeStyle;
|
|
243
|
+
this.canvasContext.lineWidth = originalLineWidth;
|
|
534
244
|
};
|
|
535
245
|
this.drawArrow = (fromX, fromY, toX, toY) => {
|
|
536
246
|
const headlen = 15; // Arrow head length
|
|
@@ -598,16 +308,23 @@ const FeedbackModal = class {
|
|
|
598
308
|
this.showColorPicker = false;
|
|
599
309
|
this.editingColorIndex = -1;
|
|
600
310
|
};
|
|
601
|
-
//
|
|
311
|
+
// Get text width for resize handle positioning
|
|
312
|
+
this.getTextWidth = (text, fontSize) => {
|
|
313
|
+
// Better text width calculation
|
|
314
|
+
if (!this.canvasContext) {
|
|
315
|
+
return text.length * fontSize * 0.6; // Fallback
|
|
316
|
+
}
|
|
317
|
+
// Use actual canvas measurement for accuracy
|
|
318
|
+
const originalFont = this.canvasContext.font;
|
|
319
|
+
this.canvasContext.font = `${fontSize}px Arial`;
|
|
320
|
+
const width = this.canvasContext.measureText(text).width;
|
|
321
|
+
this.canvasContext.font = originalFont;
|
|
322
|
+
return width;
|
|
323
|
+
};
|
|
324
|
+
// Check if point is in resize handle for shapes (not text)
|
|
602
325
|
this.isPointInResizeHandle = (x, y, annotation) => {
|
|
603
326
|
const handleSize = 8;
|
|
604
327
|
switch (annotation.type) {
|
|
605
|
-
case 'text':
|
|
606
|
-
const textWidth = this.getTextWidth(annotation.text, annotation.fontSize || 16);
|
|
607
|
-
const handleX = annotation.x + textWidth;
|
|
608
|
-
const handleY = annotation.y;
|
|
609
|
-
return x >= handleX - handleSize / 2 && x <= handleX + handleSize / 2 &&
|
|
610
|
-
y >= handleY - handleSize / 2 && y <= handleY + handleSize / 2;
|
|
611
328
|
case 'rectangle':
|
|
612
329
|
const right = annotation.startX + annotation.width;
|
|
613
330
|
const bottom = annotation.startY + annotation.height;
|
|
@@ -632,84 +349,170 @@ const FeedbackModal = class {
|
|
|
632
349
|
return false;
|
|
633
350
|
}
|
|
634
351
|
};
|
|
635
|
-
//
|
|
636
|
-
this.
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
this.
|
|
352
|
+
// Draw resize handles for rectangle annotation (only bottom-right corner)
|
|
353
|
+
this.drawRectangleResizeHandles = (annotation) => {
|
|
354
|
+
if (!this.canvasContext || annotation.type !== 'rectangle')
|
|
355
|
+
return;
|
|
356
|
+
const handleSize = 8;
|
|
357
|
+
const right = annotation.startX + annotation.width;
|
|
358
|
+
const bottom = annotation.startY + annotation.height;
|
|
359
|
+
// Only draw bottom-right corner handle
|
|
360
|
+
const handle = { x: right, y: bottom };
|
|
361
|
+
// Draw the handle
|
|
362
|
+
this.canvasContext.fillStyle = '#0070F4'; // Primary color
|
|
363
|
+
this.canvasContext.strokeStyle = '#ffffff';
|
|
364
|
+
this.canvasContext.lineWidth = 2;
|
|
365
|
+
this.canvasContext.fillRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
|
|
366
|
+
this.canvasContext.strokeRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
|
|
646
367
|
};
|
|
647
|
-
//
|
|
648
|
-
this.
|
|
649
|
-
if (!this.
|
|
368
|
+
// Draw resize handles for line/arrow annotation
|
|
369
|
+
this.drawLineResizeHandles = (annotation) => {
|
|
370
|
+
if (!this.canvasContext || (annotation.type !== 'line' && annotation.type !== 'arrow'))
|
|
650
371
|
return;
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
372
|
+
const handleSize = 8;
|
|
373
|
+
// Define handle positions (2 endpoints)
|
|
374
|
+
const handles = [
|
|
375
|
+
{ x: annotation.startX, y: annotation.startY },
|
|
376
|
+
{ x: annotation.endX, y: annotation.endY } // End point
|
|
377
|
+
];
|
|
378
|
+
// Draw each handle
|
|
379
|
+
this.canvasContext.fillStyle = '#0070F4'; // Primary color
|
|
380
|
+
this.canvasContext.strokeStyle = '#ffffff';
|
|
381
|
+
this.canvasContext.lineWidth = 2;
|
|
382
|
+
handles.forEach(handle => {
|
|
383
|
+
this.canvasContext.fillRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
|
|
384
|
+
this.canvasContext.strokeRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
|
|
385
|
+
});
|
|
663
386
|
};
|
|
664
|
-
// Start resize for
|
|
387
|
+
// Start resize for shapes
|
|
665
388
|
this.startResize = (annotation, handle, startPos) => {
|
|
666
389
|
this.isResizing = true;
|
|
667
390
|
this.resizingAnnotation = annotation;
|
|
668
391
|
this.resizeHandle = handle;
|
|
669
392
|
this.dragStartPos = startPos;
|
|
670
393
|
// Store original values for different annotation types
|
|
671
|
-
if (annotation.type === '
|
|
672
|
-
this.resizeStartSize = annotation.fontSize || 16;
|
|
673
|
-
}
|
|
674
|
-
else if (annotation.type === 'rectangle') {
|
|
394
|
+
if (annotation.type === 'rectangle') {
|
|
675
395
|
this.resizeStartDimensions = { width: annotation.width, height: annotation.height };
|
|
676
396
|
}
|
|
677
397
|
};
|
|
678
|
-
//
|
|
679
|
-
this.
|
|
680
|
-
if (!this.
|
|
398
|
+
// Handle resize for different annotation types
|
|
399
|
+
this.handleResize = (currentPos) => {
|
|
400
|
+
if (!this.resizingAnnotation || !this.dragStartPos)
|
|
681
401
|
return;
|
|
682
|
-
|
|
683
|
-
|
|
402
|
+
const annotation = this.resizingAnnotation;
|
|
403
|
+
const index = this.annotations.findIndex(a => a === annotation);
|
|
404
|
+
if (index === -1)
|
|
684
405
|
return;
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
this.
|
|
703
|
-
|
|
406
|
+
let updatedAnnotation = Object.assign({}, annotation);
|
|
407
|
+
switch (annotation.type) {
|
|
408
|
+
case 'rectangle':
|
|
409
|
+
// Rectangle resize logic - only bottom-right corner
|
|
410
|
+
const rectDeltaX = currentPos.x - this.dragStartPos.x;
|
|
411
|
+
const rectDeltaY = currentPos.y - this.dragStartPos.y;
|
|
412
|
+
// Update width and height based on original dimensions plus delta
|
|
413
|
+
updatedAnnotation.width = Math.max(10, this.resizeStartDimensions.width + rectDeltaX);
|
|
414
|
+
updatedAnnotation.height = Math.max(10, this.resizeStartDimensions.height + rectDeltaY);
|
|
415
|
+
break;
|
|
416
|
+
case 'line':
|
|
417
|
+
case 'arrow':
|
|
418
|
+
// Line/arrow resize logic - move endpoints
|
|
419
|
+
if (this.resizeHandle === 'start') {
|
|
420
|
+
updatedAnnotation.startX = currentPos.x;
|
|
421
|
+
updatedAnnotation.startY = currentPos.y;
|
|
422
|
+
}
|
|
423
|
+
else if (this.resizeHandle === 'end') {
|
|
424
|
+
updatedAnnotation.endX = currentPos.x;
|
|
425
|
+
updatedAnnotation.endY = currentPos.y;
|
|
426
|
+
}
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
// Update annotation in array
|
|
430
|
+
this.annotations[index] = updatedAnnotation;
|
|
431
|
+
this.resizingAnnotation = updatedAnnotation;
|
|
432
|
+
this.redrawAnnotations();
|
|
433
|
+
};
|
|
434
|
+
// Text editing methods
|
|
435
|
+
this.startTextEditing = (annotation) => {
|
|
436
|
+
const newText = prompt(this.editTextPromptText, annotation.text);
|
|
437
|
+
if (newText !== null && newText.trim()) {
|
|
438
|
+
const index = this.annotations.findIndex(a => a === annotation);
|
|
439
|
+
if (index !== -1) {
|
|
440
|
+
this.annotations[index] = Object.assign(Object.assign({}, annotation), { text: newText.trim() });
|
|
441
|
+
this.selectedAnnotation = this.annotations[index];
|
|
442
|
+
this.redrawAnnotations();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
// Update selected annotation font size
|
|
447
|
+
this.updateSelectedTextSize = (newSize) => {
|
|
448
|
+
if (this.selectedAnnotation && this.selectedAnnotation.type === 'text') {
|
|
449
|
+
const index = this.annotations.findIndex(a => a === this.selectedAnnotation);
|
|
450
|
+
if (index !== -1) {
|
|
451
|
+
this.annotations[index] = Object.assign(Object.assign({}, this.selectedAnnotation), { fontSize: Math.max(8, Math.min(72, newSize)) });
|
|
452
|
+
this.selectedAnnotation = this.annotations[index];
|
|
453
|
+
this.redrawAnnotations();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
// Update selected annotation border width
|
|
458
|
+
this.updateSelectedBorderWidth = (newWidth) => {
|
|
459
|
+
if (this.selectedAnnotation && ['rectangle', 'line', 'arrow'].includes(this.selectedAnnotation.type)) {
|
|
460
|
+
const index = this.annotations.findIndex(a => a === this.selectedAnnotation);
|
|
461
|
+
if (index !== -1) {
|
|
462
|
+
this.annotations[index] = Object.assign(Object.assign({}, this.selectedAnnotation), { lineWidth: Math.max(1, Math.min(20, newWidth)) });
|
|
463
|
+
this.selectedAnnotation = this.annotations[index];
|
|
464
|
+
this.redrawAnnotations();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
// Enhanced mouse down handler with resize support
|
|
469
|
+
this.handleCanvasMouseDown = (event) => {
|
|
470
|
+
if (!this.canvasRef)
|
|
471
|
+
return;
|
|
472
|
+
// Disable drawing on mobile devices
|
|
473
|
+
if (window.innerWidth <= 768)
|
|
474
|
+
return;
|
|
475
|
+
// Close color picker if open
|
|
476
|
+
if (this.showColorPicker) {
|
|
477
|
+
this.closeColorPicker();
|
|
478
|
+
}
|
|
479
|
+
const coords = this.getCanvasCoordinates(event);
|
|
480
|
+
// Check if clicking on existing annotation first
|
|
481
|
+
const found = this.findAnnotationAt(coords.x, coords.y);
|
|
482
|
+
if (found) {
|
|
483
|
+
// Select the annotation
|
|
484
|
+
this.selectedAnnotation = found.annotation;
|
|
485
|
+
// Check if clicking on resize handle for shapes (not text)
|
|
486
|
+
if (found.annotation.type !== 'text') {
|
|
487
|
+
const handle = this.isPointInResizeHandle(coords.x, coords.y, found.annotation);
|
|
488
|
+
if (handle) {
|
|
489
|
+
this.startResize(found.annotation, handle, coords);
|
|
490
|
+
this.canvasRef.style.cursor = 'nw-resize';
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
// Check for double-click to edit text
|
|
495
|
+
if (found.annotation.type === 'text' && event.detail === 2) {
|
|
496
|
+
this.startTextEditing(found.annotation);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
// Start dragging existing annotation
|
|
500
|
+
if (!this.isDrawing) {
|
|
501
|
+
this.isDragging = true;
|
|
502
|
+
this.draggedAnnotation = found.annotation;
|
|
704
503
|
this.dragStartPos = coords;
|
|
705
504
|
this.canvasRef.style.cursor = 'grabbing';
|
|
706
505
|
return;
|
|
707
506
|
}
|
|
708
507
|
}
|
|
508
|
+
else {
|
|
509
|
+
// Clear selection if clicking on empty space
|
|
510
|
+
this.selectedAnnotation = null;
|
|
511
|
+
}
|
|
709
512
|
// Original drawing logic
|
|
710
513
|
this.isDrawing = true;
|
|
711
514
|
if (this.canvasDrawingTool === 'text') {
|
|
712
|
-
const text = prompt(
|
|
515
|
+
const text = prompt(this.editTextPromptText);
|
|
713
516
|
if (text) {
|
|
714
517
|
const annotation = {
|
|
715
518
|
type: 'text',
|
|
@@ -717,7 +520,7 @@ const FeedbackModal = class {
|
|
|
717
520
|
y: coords.y,
|
|
718
521
|
text,
|
|
719
522
|
color: this.canvasDrawingColor,
|
|
720
|
-
fontSize:
|
|
523
|
+
fontSize: this.canvasTextSize
|
|
721
524
|
};
|
|
722
525
|
this.annotations = [...this.annotations, annotation];
|
|
723
526
|
this.redrawAnnotations();
|
|
@@ -741,7 +544,7 @@ const FeedbackModal = class {
|
|
|
741
544
|
if (window.innerWidth <= 768)
|
|
742
545
|
return;
|
|
743
546
|
const coords = this.getCanvasCoordinates(event);
|
|
744
|
-
// Handle resizing for
|
|
547
|
+
// Handle resizing for shapes
|
|
745
548
|
if (this.isResizing && this.resizingAnnotation) {
|
|
746
549
|
this.handleResize(coords);
|
|
747
550
|
return;
|
|
@@ -796,13 +599,15 @@ const FeedbackModal = class {
|
|
|
796
599
|
// Handle hover states and cursor changes
|
|
797
600
|
const found = this.findAnnotationAt(coords.x, coords.y);
|
|
798
601
|
if (found) {
|
|
799
|
-
// Check if hovering over resize handle for
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
602
|
+
// Check if hovering over resize handle for shapes (not text)
|
|
603
|
+
if (found.annotation.type !== 'text') {
|
|
604
|
+
const handle = this.isPointInResizeHandle(coords.x, coords.y, found.annotation);
|
|
605
|
+
if (handle) {
|
|
606
|
+
this.canvasRef.style.cursor = 'nw-resize';
|
|
607
|
+
this.hoveredAnnotation = found.annotation;
|
|
608
|
+
this.redrawAnnotations();
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
806
611
|
}
|
|
807
612
|
// Regular hover over annotation
|
|
808
613
|
this.canvasRef.style.cursor = 'grab';
|
|
@@ -834,154 +639,577 @@ const FeedbackModal = class {
|
|
|
834
639
|
if (this.canvasRef) {
|
|
835
640
|
this.canvasRef.style.cursor = 'crosshair';
|
|
836
641
|
}
|
|
837
|
-
return;
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
// Handle end of dragging
|
|
645
|
+
if (this.isDragging) {
|
|
646
|
+
this.isDragging = false;
|
|
647
|
+
this.draggedAnnotation = null;
|
|
648
|
+
this.dragStartPos = null;
|
|
649
|
+
if (this.canvasRef) {
|
|
650
|
+
this.canvasRef.style.cursor = 'crosshair';
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
// Handle end of drawing
|
|
655
|
+
if (!this.isDrawing || !this.currentAnnotation)
|
|
656
|
+
return;
|
|
657
|
+
this.isDrawing = false;
|
|
658
|
+
this.annotations = [...this.annotations, this.currentAnnotation];
|
|
659
|
+
this.currentAnnotation = null;
|
|
660
|
+
this.redrawAnnotations();
|
|
661
|
+
};
|
|
662
|
+
// Convert screen coordinates to canvas coordinates
|
|
663
|
+
this.getCanvasCoordinates = (event) => {
|
|
664
|
+
if (!this.canvasRef)
|
|
665
|
+
return { x: 0, y: 0 };
|
|
666
|
+
const rect = this.canvasRef.getBoundingClientRect();
|
|
667
|
+
// Calculate the scale factor between display size and actual canvas size
|
|
668
|
+
const scaleX = this.canvasRef.width / rect.width;
|
|
669
|
+
const scaleY = this.canvasRef.height / rect.height;
|
|
670
|
+
const x = (event.clientX - rect.left) * scaleX;
|
|
671
|
+
const y = (event.clientY - rect.top) * scaleY;
|
|
672
|
+
return { x, y };
|
|
673
|
+
};
|
|
674
|
+
// Find annotation under mouse cursor
|
|
675
|
+
this.findAnnotationAt = (x, y) => {
|
|
676
|
+
// Check in reverse order (top to bottom)
|
|
677
|
+
for (let i = this.annotations.length - 1; i >= 0; i--) {
|
|
678
|
+
const annotation = this.annotations[i];
|
|
679
|
+
if (this.isPointInAnnotation(x, y, annotation)) {
|
|
680
|
+
return { annotation, index: i };
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return null;
|
|
684
|
+
};
|
|
685
|
+
// Check if point is within annotation bounds
|
|
686
|
+
this.isPointInAnnotation = (x, y, annotation) => {
|
|
687
|
+
const tolerance = 10; // Click tolerance
|
|
688
|
+
switch (annotation.type) {
|
|
689
|
+
case 'rectangle':
|
|
690
|
+
const left = Math.min(annotation.startX, annotation.startX + annotation.width);
|
|
691
|
+
const right = Math.max(annotation.startX, annotation.startX + annotation.width);
|
|
692
|
+
const top = Math.min(annotation.startY, annotation.startY + annotation.height);
|
|
693
|
+
const bottom = Math.max(annotation.startY, annotation.startY + annotation.height);
|
|
694
|
+
return x >= left - tolerance && x <= right + tolerance &&
|
|
695
|
+
y >= top - tolerance && y <= bottom + tolerance;
|
|
696
|
+
case 'line':
|
|
697
|
+
case 'arrow':
|
|
698
|
+
// Distance from point to line
|
|
699
|
+
const A = annotation.endY - annotation.startY;
|
|
700
|
+
const B = annotation.startX - annotation.endX;
|
|
701
|
+
const C = annotation.endX * annotation.startY - annotation.startX * annotation.endY;
|
|
702
|
+
const distance = Math.abs(A * x + B * y + C) / Math.sqrt(A * A + B * B);
|
|
703
|
+
return distance <= tolerance;
|
|
704
|
+
case 'text':
|
|
705
|
+
// Use actual text dimensions for better dragging
|
|
706
|
+
const fontSize = annotation.fontSize || 24;
|
|
707
|
+
const textWidth = this.getTextWidth(annotation.text, fontSize);
|
|
708
|
+
const textHeight = fontSize;
|
|
709
|
+
// Text bounding box (y coordinate is baseline, so subtract font size for top)
|
|
710
|
+
const textLeft = annotation.x - tolerance;
|
|
711
|
+
const textRight = annotation.x + textWidth + tolerance;
|
|
712
|
+
const textTop = annotation.y - textHeight - tolerance;
|
|
713
|
+
const textBottom = annotation.y + tolerance;
|
|
714
|
+
return x >= textLeft && x <= textRight &&
|
|
715
|
+
y >= textTop && y <= textBottom;
|
|
716
|
+
default:
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
this.canvasEditorTitle = 'Edit screenshot';
|
|
721
|
+
this.canvasEditorCancelText = 'Cancel';
|
|
722
|
+
this.canvasEditorSaveText = 'Save';
|
|
723
|
+
this.screenshotTakingText = 'Taking screenshot...';
|
|
724
|
+
this.screenshotAttachedText = 'Screenshot attached';
|
|
725
|
+
this.screenshotButtonText = 'Add a screenshot';
|
|
726
|
+
this.autoStartScreenshot = false;
|
|
727
|
+
this.existingScreenshot = '';
|
|
728
|
+
this.editTextButtonText = 'Edit Text';
|
|
729
|
+
this.sizeLabelText = 'Size:';
|
|
730
|
+
this.borderLabelText = 'Border:';
|
|
731
|
+
this.editTextPromptText = 'Edit text:';
|
|
732
|
+
this.screenshotErrorGeneral = 'Failed to capture screenshot.';
|
|
733
|
+
this.screenshotErrorPermission = 'Permission denied. Please allow screen sharing to take screenshots.';
|
|
734
|
+
this.screenshotErrorNotSupported = 'Screen capture is not supported in this browser.';
|
|
735
|
+
this.screenshotErrorNotFound = 'No screen sources available for capture.';
|
|
736
|
+
this.screenshotErrorCancelled = 'Screenshot capture was cancelled.';
|
|
737
|
+
this.screenshotErrorBrowserNotSupported = 'Your browser does not support screen capture. Please use a browser like Chrome, Firefox, or Safari on desktop.';
|
|
738
|
+
this.screenshotErrorUnexpected = 'An unexpected error occurred. Please try again.';
|
|
739
|
+
this.takingScreenshot = false;
|
|
740
|
+
this.showCanvasEditor = false;
|
|
741
|
+
this.canvasDrawingTool = 'rectangle';
|
|
742
|
+
this.canvasDrawingColor = '#ff0000';
|
|
743
|
+
this.canvasLineWidth = 3;
|
|
744
|
+
this.canvasTextSize = 24;
|
|
745
|
+
this.isDrawing = false;
|
|
746
|
+
this.annotations = [];
|
|
747
|
+
this.currentAnnotation = null;
|
|
748
|
+
this.isDragging = false;
|
|
749
|
+
this.draggedAnnotation = null;
|
|
750
|
+
this.dragStartPos = null;
|
|
751
|
+
this.showColorPicker = false;
|
|
752
|
+
this.editingColorIndex = -1;
|
|
753
|
+
this.selectedAnnotation = null;
|
|
754
|
+
this.isResizing = false;
|
|
755
|
+
this.resizingAnnotation = null;
|
|
756
|
+
this.resizeStartSize = 24;
|
|
757
|
+
this.resizeStartDimensions = null;
|
|
758
|
+
this.hoveredAnnotation = null;
|
|
759
|
+
this.resizeHandle = false;
|
|
760
|
+
this.defaultColors = ['#ff0000', '#00ff00', '#0000ff', '#000000'];
|
|
761
|
+
}
|
|
762
|
+
componentDidLoad() {
|
|
763
|
+
if (this.autoStartScreenshot) {
|
|
764
|
+
// Show the editor UI and start screenshot capture
|
|
765
|
+
this.showCanvasEditor = true;
|
|
766
|
+
setTimeout(() => {
|
|
767
|
+
this.openScreenShot();
|
|
768
|
+
}, 100); // Small delay to ensure component is fully rendered
|
|
769
|
+
}
|
|
770
|
+
else if (this.existingScreenshot) {
|
|
771
|
+
// Show editor with existing screenshot data
|
|
772
|
+
this.originalImageData = this.existingScreenshot;
|
|
773
|
+
this.showCanvasEditor = true;
|
|
774
|
+
setTimeout(() => {
|
|
775
|
+
this.initializeCanvas();
|
|
776
|
+
}, 100);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
async captureViewportScreenshot() {
|
|
780
|
+
try {
|
|
781
|
+
// Check if Screen Capture API is supported
|
|
782
|
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
|
|
783
|
+
throw new Error('Screen Capture API is not supported in this browser');
|
|
784
|
+
}
|
|
785
|
+
// Request screen capture with preference for current tab
|
|
786
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
787
|
+
video: {
|
|
788
|
+
mediaSource: 'screen',
|
|
789
|
+
width: { ideal: window.innerWidth },
|
|
790
|
+
height: { ideal: window.innerHeight }
|
|
791
|
+
},
|
|
792
|
+
audio: false,
|
|
793
|
+
preferCurrentTab: true
|
|
794
|
+
});
|
|
795
|
+
// Create video element to capture frame
|
|
796
|
+
const video = document.createElement('video');
|
|
797
|
+
video.srcObject = stream;
|
|
798
|
+
video.autoplay = true;
|
|
799
|
+
video.muted = true;
|
|
800
|
+
return new Promise((resolve, reject) => {
|
|
801
|
+
video.onloadedmetadata = () => {
|
|
802
|
+
video.play();
|
|
803
|
+
// Wait a moment for video to stabilize
|
|
804
|
+
setTimeout(() => {
|
|
805
|
+
try {
|
|
806
|
+
// Create canvas to capture frame
|
|
807
|
+
const canvas = document.createElement('canvas');
|
|
808
|
+
canvas.width = video.videoWidth;
|
|
809
|
+
canvas.height = video.videoHeight;
|
|
810
|
+
const ctx = canvas.getContext('2d');
|
|
811
|
+
ctx.drawImage(video, 0, 0);
|
|
812
|
+
// Stop the stream
|
|
813
|
+
stream.getTracks().forEach(track => track.stop());
|
|
814
|
+
// Convert to data URL
|
|
815
|
+
const dataUrl = canvas.toDataURL('image/png');
|
|
816
|
+
console.log('Screenshot captured successfully using Screen Capture API');
|
|
817
|
+
resolve(dataUrl);
|
|
818
|
+
}
|
|
819
|
+
catch (error) {
|
|
820
|
+
stream.getTracks().forEach(track => track.stop());
|
|
821
|
+
reject(error);
|
|
822
|
+
}
|
|
823
|
+
}, 100);
|
|
824
|
+
};
|
|
825
|
+
video.onerror = () => {
|
|
826
|
+
stream.getTracks().forEach(track => track.stop());
|
|
827
|
+
reject(new Error('Failed to load video for screenshot capture'));
|
|
828
|
+
};
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
catch (error) {
|
|
832
|
+
console.error('Screen capture failed:', error);
|
|
833
|
+
throw error;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
render() {
|
|
837
|
+
var _a, _b, _c, _d, _e, _f;
|
|
838
|
+
return (h("div", { class: "canvas-editor-wrapper" }, 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) })))))))), (this.selectedAnnotation || this.canvasDrawingTool) && (h("div", { class: "toolbar-section selected-annotation-controls" }, (((_a = this.selectedAnnotation) === null || _a === void 0 ? void 0 : _a.type) === 'text' || (!this.selectedAnnotation && this.canvasDrawingTool === 'text')) && (h("div", { class: "text-controls" }, h("div", { class: "font-size-control" }, h("label", null, this.sizeLabelText), h("input", { type: "range", min: "8", max: "72", value: ((_b = this.selectedAnnotation) === null || _b === void 0 ? void 0 : _b.fontSize) || this.canvasTextSize, onInput: (e) => {
|
|
839
|
+
const newSize = parseInt(e.target.value);
|
|
840
|
+
if (this.selectedAnnotation) {
|
|
841
|
+
this.updateSelectedTextSize(newSize);
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
this.canvasTextSize = newSize;
|
|
845
|
+
}
|
|
846
|
+
}, class: "size-slider" }), h("span", { class: "size-value" }, ((_c = this.selectedAnnotation) === null || _c === void 0 ? void 0 : _c.fontSize) || this.canvasTextSize, "px")), this.selectedAnnotation && (h("button", { class: "action-btn small", onClick: () => this.startTextEditing(this.selectedAnnotation) }, this.editTextButtonText)))), ((['rectangle', 'line', 'arrow'].includes((_d = this.selectedAnnotation) === null || _d === void 0 ? void 0 : _d.type)) ||
|
|
847
|
+
(!this.selectedAnnotation && ['rectangle', 'line', 'arrow'].includes(this.canvasDrawingTool))) && (h("div", { class: "shape-controls" }, h("div", { class: "border-width-control" }, h("label", null, this.borderLabelText), h("input", { type: "range", min: "1", max: "20", value: ((_e = this.selectedAnnotation) === null || _e === void 0 ? void 0 : _e.lineWidth) || this.canvasLineWidth, onInput: (e) => {
|
|
848
|
+
const newWidth = parseInt(e.target.value);
|
|
849
|
+
if (this.selectedAnnotation) {
|
|
850
|
+
this.updateSelectedBorderWidth(newWidth);
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
this.canvasLineWidth = newWidth;
|
|
854
|
+
}
|
|
855
|
+
}, class: "size-slider" }), h("span", { class: "size-value" }, ((_f = this.selectedAnnotation) === null || _f === void 0 ? void 0 : _f.lineWidth) || 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 }))))))));
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
CanvasEditor.style = canvasEditorCss;
|
|
859
|
+
|
|
860
|
+
const feedbackButtonCss = ".feedback-button-content{cursor:pointer;max-width:fit-content;z-index:var(--feedback-button-z-index);font-family:var(--feedback-font-family)}.feedback-button-content--custom-font{font-family:inherit}.feedback-button-content--light{align-items:center;background-color:var(--feedback-button-light-bg-color);border-radius:var(--feedback-button-border-radius);box-shadow:rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;box-sizing:border-box;color:var(--feedback-button-light-text-color);display:flex;font-size:var(--feedback-button-text-font-size);font-weight:var(--feedback-button-text-font-weight);padding:8px 15px}.feedback-button-content--dark{align-items:center;background-color:var(--feedback-button-dark-bg-color);border-radius:var(--feedback-button-border-radius);box-shadow:rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;box-sizing:border-box;color:var(--feedback-button-dark-text-color);display:flex;font-weight:var(--feedback-button-text-font-weight);font-size:var(--feedback-button-text-font-size);padding:8px 15px}.icon-edit{stroke:var(--feedback-button-light-icon-color)}.feedback-button-content--dark .icon-edit{stroke:var(--feedback-button-dark-icon-color)}.feedback-button-content--bottom-right{bottom:10px;position:fixed;right:10px}.feedback-button-content--center-right{position:fixed;transform:rotate(-90deg) translateY(-50%);top:50%}.feedback-button-content--center-right.feedback-button-content--dark,.feedback-button-content--center-right.feedback-button-content--light{border-radius:4px;border-bottom-left-radius:0px;border-bottom-right-radius:0px}.feedback-button-content-icon{height:16px;margin-right:5px;width:16px}.feedback-button-content--center-right .feedback-button-content-icon{rotate:90deg}@media screen and (max-width: 767px){.feedback-button-content--hide-mobile{display:none}}";
|
|
861
|
+
|
|
862
|
+
const FeedbackButton = class {
|
|
863
|
+
constructor(hostRef) {
|
|
864
|
+
registerInstance(this, hostRef);
|
|
865
|
+
this.feedbackSent = createEvent(this, "feedbackSent", 7);
|
|
866
|
+
this.feedbackError = createEvent(this, "feedbackError", 7);
|
|
867
|
+
this.buttonPosition = 'default';
|
|
868
|
+
this.buttonStyle = 'default';
|
|
869
|
+
this.hideIcon = false;
|
|
870
|
+
this.hideMobile = false;
|
|
871
|
+
this.sessionId = '';
|
|
872
|
+
this.metadata = '';
|
|
873
|
+
this.submit = false;
|
|
874
|
+
this.customFont = false;
|
|
875
|
+
this.emailAddress = '';
|
|
876
|
+
this.isEmailRequired = false;
|
|
877
|
+
this.fetchData = true;
|
|
878
|
+
this.hideEmail = false;
|
|
879
|
+
this.hidePrivacyPolicy = true;
|
|
880
|
+
this.hideRating = false;
|
|
881
|
+
this.hideScreenshotButton = false;
|
|
882
|
+
this.modalPosition = 'center';
|
|
883
|
+
this.project = '';
|
|
884
|
+
this.rating = undefined;
|
|
885
|
+
this.ratingMode = 'thumbs';
|
|
886
|
+
this.canvasEditorTitle = 'Edit screenshot';
|
|
887
|
+
this.canvasEditorCancelText = 'Cancel';
|
|
888
|
+
this.canvasEditorSaveText = 'Save';
|
|
889
|
+
this.editTextButtonText = 'Edit Text';
|
|
890
|
+
this.sizeLabelText = 'Size:';
|
|
891
|
+
this.borderLabelText = 'Border:';
|
|
892
|
+
this.editTextPromptText = 'Edit text:';
|
|
893
|
+
this.screenshotErrorGeneral = 'Failed to capture screenshot.';
|
|
894
|
+
this.screenshotErrorPermission = 'Permission denied. Please allow screen sharing to take screenshots.';
|
|
895
|
+
this.screenshotErrorNotSupported = 'Screen capture is not supported in this browser.';
|
|
896
|
+
this.screenshotErrorNotFound = 'No screen sources available for capture.';
|
|
897
|
+
this.screenshotErrorCancelled = 'Screenshot capture was cancelled.';
|
|
898
|
+
this.screenshotErrorBrowserNotSupported = 'Your browser does not support screen capture. Please use a browser like Chrome, Firefox, or Safari.';
|
|
899
|
+
this.screenshotErrorUnexpected = 'An unexpected error occurred. Please try again.';
|
|
900
|
+
this.emailPlaceholder = 'Email address (optional)';
|
|
901
|
+
this.errorMessage = 'Please try again later.';
|
|
902
|
+
this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
|
|
903
|
+
this.errorMessage404 = 'We could not find the provided project id in PushFeedback.';
|
|
904
|
+
this.footerText = '';
|
|
905
|
+
this.messagePlaceholder = 'Comments';
|
|
906
|
+
this.modalTitle = 'Share your feedback';
|
|
907
|
+
this.modalTitleError = 'Oops!';
|
|
908
|
+
this.modalTitleSuccess = 'Thanks for your feedback!';
|
|
909
|
+
this.privacyPolicyText = "I have read and expressly consent to the terms of the <a href='https://pushfeedback.com/privacy'>Privacy Policy</a>.";
|
|
910
|
+
this.ratingPlaceholder = 'Was this page helpful?';
|
|
911
|
+
this.ratingStarsPlaceholder = 'How would you rate this page?';
|
|
912
|
+
this.screenshotAttachedText = 'Screenshot attached';
|
|
913
|
+
this.screenshotButtonText = 'Add a screenshot';
|
|
914
|
+
this.screenshotTakingText = 'Taking screenshot...';
|
|
915
|
+
this.screenshotTopbarText = 'Select an element on this page';
|
|
916
|
+
this.sendButtonText = 'Send';
|
|
917
|
+
this.successMessage = '';
|
|
918
|
+
}
|
|
919
|
+
componentWillLoad() {
|
|
920
|
+
if (!this.sessionId) {
|
|
921
|
+
let storedSessionId = localStorage.getItem('pushfeedback_sessionid');
|
|
922
|
+
if (!storedSessionId) {
|
|
923
|
+
storedSessionId = this.generateRandomSessionId();
|
|
924
|
+
localStorage.setItem('pushfeedback_sessionid', storedSessionId);
|
|
925
|
+
this.sessionId = storedSessionId;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
localStorage.setItem('pushfeedback_sessionid', this.sessionId);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
componentDidLoad() {
|
|
933
|
+
if (this.buttonPosition === 'center-right') {
|
|
934
|
+
const buttonContent = this.el.shadowRoot.querySelector('.feedback-button-content');
|
|
935
|
+
let adjustement = 0;
|
|
936
|
+
if (this.isSafariBrowser()) {
|
|
937
|
+
adjustement = 5;
|
|
938
|
+
}
|
|
939
|
+
buttonContent.style.right = `${((buttonContent.offsetWidth + adjustement) / 2) * -1}px`;
|
|
940
|
+
}
|
|
941
|
+
if (!this.customFont) {
|
|
942
|
+
this.loadInterFont();
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
connectedCallback() {
|
|
946
|
+
this.feedbackModal = document.createElement('feedback-modal');
|
|
947
|
+
const props = [
|
|
948
|
+
'customFont',
|
|
949
|
+
'emailAddress',
|
|
950
|
+
'fetchData',
|
|
951
|
+
'hideEmail',
|
|
952
|
+
'hidePrivacyPolicy',
|
|
953
|
+
'hideRating',
|
|
954
|
+
'hideScreenshotButton',
|
|
955
|
+
'isEmailRequired',
|
|
956
|
+
'modalPosition',
|
|
957
|
+
'project',
|
|
958
|
+
'rating',
|
|
959
|
+
'ratingMode',
|
|
960
|
+
'canvasEditorTitle',
|
|
961
|
+
'canvasEditorCancelText',
|
|
962
|
+
'canvasEditorSaveText',
|
|
963
|
+
'editTextButtonText',
|
|
964
|
+
'sizeLabelText',
|
|
965
|
+
'borderLabelText',
|
|
966
|
+
'editTextPromptText',
|
|
967
|
+
'screenshotErrorGeneral',
|
|
968
|
+
'screenshotErrorPermission',
|
|
969
|
+
'screenshotErrorNotSupported',
|
|
970
|
+
'screenshotErrorNotFound',
|
|
971
|
+
'screenshotErrorCancelled',
|
|
972
|
+
'screenshotErrorBrowserNotSupported',
|
|
973
|
+
'screenshotErrorUnexpected',
|
|
974
|
+
'emailPlaceholder',
|
|
975
|
+
'errorMessage',
|
|
976
|
+
'errorMessage403',
|
|
977
|
+
'errorMessage404',
|
|
978
|
+
'footerText',
|
|
979
|
+
'messagePlaceholder',
|
|
980
|
+
'metadata',
|
|
981
|
+
'modalTitle',
|
|
982
|
+
'modalTitleError',
|
|
983
|
+
'modalTitleSuccess',
|
|
984
|
+
'privacyPolicyText',
|
|
985
|
+
'ratingPlaceholder',
|
|
986
|
+
'ratingStarsPlaceholder',
|
|
987
|
+
'screenshotAttachedText',
|
|
988
|
+
'screenshotButtonText',
|
|
989
|
+
'screenshotTakingText',
|
|
990
|
+
'screenshotTopbarText',
|
|
991
|
+
'sendButtonText',
|
|
992
|
+
'successMessage',
|
|
993
|
+
];
|
|
994
|
+
props.forEach((prop) => {
|
|
995
|
+
this.feedbackModal[prop] = this[prop];
|
|
996
|
+
});
|
|
997
|
+
document.body.appendChild(this.feedbackModal);
|
|
998
|
+
}
|
|
999
|
+
disconnectedCallback() {
|
|
1000
|
+
document.body.removeChild(this.feedbackModal);
|
|
1001
|
+
}
|
|
1002
|
+
generateRandomSessionId(length = 16) {
|
|
1003
|
+
return Math.random().toString(36).substr(2, length);
|
|
1004
|
+
}
|
|
1005
|
+
isSafariBrowser() {
|
|
1006
|
+
const isSafari = /safari/i.test(navigator.userAgent) && !/chrome/i.test(navigator.userAgent);
|
|
1007
|
+
return isSafari;
|
|
1008
|
+
}
|
|
1009
|
+
loadInterFont() {
|
|
1010
|
+
const link = document.createElement('link');
|
|
1011
|
+
link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap';
|
|
1012
|
+
link.rel = 'stylesheet';
|
|
1013
|
+
document.head.appendChild(link);
|
|
1014
|
+
}
|
|
1015
|
+
showModal() {
|
|
1016
|
+
if (this.submit) {
|
|
1017
|
+
this.submitRatingFeedback();
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
this.feedbackModal.openModal();
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
async submitRatingFeedback() {
|
|
1024
|
+
try {
|
|
1025
|
+
const body = {
|
|
1026
|
+
url: window.location.href,
|
|
1027
|
+
project: this.project,
|
|
1028
|
+
rating: this.rating || -1,
|
|
1029
|
+
ratingMode: this.ratingMode,
|
|
1030
|
+
message: '',
|
|
1031
|
+
metadata: this.metadata,
|
|
1032
|
+
session: localStorage.getItem('pushfeedback_sessionid') || '',
|
|
1033
|
+
};
|
|
1034
|
+
const res = await fetch('https://app.pushfeedback.com/api/feedback/', {
|
|
1035
|
+
method: 'POST',
|
|
1036
|
+
body: JSON.stringify(body),
|
|
1037
|
+
headers: {
|
|
1038
|
+
'Content-Type': 'application/json',
|
|
1039
|
+
},
|
|
1040
|
+
});
|
|
1041
|
+
if (res.status === 201) {
|
|
1042
|
+
const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
|
|
1043
|
+
this.feedbackSent.emit({ feedback: feedback_with_id });
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
const errorText = await res.text();
|
|
1047
|
+
const response = {
|
|
1048
|
+
status: res.status,
|
|
1049
|
+
message: errorText,
|
|
1050
|
+
};
|
|
1051
|
+
this.feedbackError.emit({ error: response });
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
catch (error) {
|
|
1055
|
+
const response = {
|
|
1056
|
+
status: 500,
|
|
1057
|
+
message: error,
|
|
1058
|
+
};
|
|
1059
|
+
this.feedbackError.emit({ error: response });
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
render() {
|
|
1063
|
+
return (h(Host, null, h("a", { class: `feedback-button-content feedback-button-content--${this.buttonStyle} feedback-button-content--${this.buttonPosition} ${this.customFont ? 'feedback-button-content--custom-font' : ''} ${this.hideMobile ? 'feedback-button-content--hide-mobile' : ''}`, onClick: () => this.showModal() }, !this.hideIcon && this.buttonStyle != 'default' && (h("span", { class: "feedback-button-content-icon" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#fff", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "icon-edit" }, h("path", { d: "M12 20h9" }), h("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })))), h("slot", null))));
|
|
1064
|
+
}
|
|
1065
|
+
get el() { return getElement(this); }
|
|
1066
|
+
};
|
|
1067
|
+
FeedbackButton.style = feedbackButtonCss;
|
|
1068
|
+
|
|
1069
|
+
const feedbackModalCss = ".text-center{flex-grow:1;text-align:center}.feedback-modal-wrapper *{font-family:var(--feedback-font-family)}.feedback-modal-wrapper--custom-font *{font-family:inherit}.feedback-modal-wrapper{position:absolute;z-index:var(--feedback-modal-modal-wrapper-z-index)}.feedback-overlay{background-color:var(--feedback-modal-screenshot-bg-color);height:100%;left:0;opacity:0;position:fixed;top:0;width:100%;z-index:var(--feedback-modal-screnshot-z-index);transition:opacity 0.2s ease-out}.feedback-overlay--visible{opacity:1}.feedback-modal{display:inline-block;position:relative}.feedback-modal-content{background-color:var(--feedback-modal-content-bg-color);border-color:1px solid var(--feedback-modal-header-text-color);border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);box-sizing:border-box;color:var(--feedback-modal-content-text-color);display:flex;flex-direction:column;left:50%;max-width:90%;padding:20px;position:fixed;top:50%;transform:translate(-50%, -50%) scale(0.95);opacity:0;width:100%;z-index:var(--feedback-modal-content-z-index);transition:transform 0.2s ease-out, opacity 0.2s ease-out}.feedback-modal-content--open{transform:translate(-50%, -50%) scale(1);opacity:1}.feedback-modal-header{align-items:center;color:var(--feedback-modal-header-text-color);display:flex;font-size:var(--feedback-header-font-size);font-weight:var(--feedback-modal-header-font-weight);justify-content:space-between;margin-bottom:20px}.feedback-modal-rating-buttons{width:100%;margin-bottom:20px}.feedback-modal-rating-button{padding:0;background-color:transparent;border:transparent;margin-right:5px;cursor:pointer}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button{border:1px solid var(--feedback-modal-button-border-color);border-radius:var(--feedback-modal-button-border-radius);color:var(--feedback-modal-button-text-color);font-size:var(--feedback-modal-button-font-size);font-weight:500;margin-right:10px;justify-content:center;padding:5px 10px}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--selected{background-color:var(--feedback-modal-button-bg-color-active);border:1px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-selected-color)}.feedback-modal-rating-buttons svg{stroke:var(--feedback-modal-rating-button-color);cursor:pointer}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button--selected svg{fill:var(--feedback-modal-rating-button-stars-selected-color);stroke:var(--feedback-modal-rating-button-stars-selected-color)}.feedback-modal-text textarea{background-color:var(--feedback-modal-input-bg-color);border:1px solid var(--feedback-modal-input-border-color);border-radius:var(--feedback-modal-input-border-radius);box-sizing:border-box;color:var(--feedback-modal-input-text-color);font-size:var(--feedback-modal-input-font-size);margin-bottom:20px;height:100px;min-height:100px;padding:10px;resize:vertical;width:100%}.feedback-modal-email input{background-color:var(--feedback-modal-input-bg-color);border:1px solid var(--feedback-modal-input-border-color);border-radius:var(--feedback-modal-input-border-radius);box-sizing:border-box;color:var(--feedback-modal-input-text-color);font-size:var(--feedback-modal-input-font-size);margin-bottom:20px;height:40px;padding:10px;width:100%;margin-bottom:20px}.feedback-modal-privacy{font-size:var(--feedback-modal-input-font-size);margin-bottom:20px}.feedback-modal-text textarea:focus,.feedback-modal-email input:focus{border:1px solid var(--feedback-modal-input-border-color-focused);outline:none}.feedback-modal-buttons{display:flex;flex-direction:column}.feedback-modal-buttons .feedback-modal-button{margin-bottom:20px}.feedback-modal-button{align-items:center;background-color:transparent;border:1px solid var(--feedback-modal-button-border-color);border-radius:var(--feedback-modal-button-border-radius);color:var(--feedback-modal-button-text-color);cursor:pointer;display:flex;font-size:var(--feedback-modal-button-font-size);font-weight:500;justify-content:center;min-height:40px;padding:5px 10px}.feedback-modal-button svg{margin-right:6px}.feedback-modal-button path{fill:var(--feedback-modal-button-icon-color)}.feedback-modal-button:hover path,.feedback-modal-button--active path{fill:var(--feedback-modal-button-icon-color-active)}.feedback-modal-button--submit{background-color:var(--feedback-modal-button-submit-bg-color);border:1px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-submit-text-color)}.feedback-modal-button:hover,.feedback-modal-button--active{background-color:var(--feedback-modal-button-bg-color-active);border:1px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active)}.feedback-modal-button--submit:hover{background-color:var(--feedback-modal-button-submit-bg-color-hover);border:1px solid var(--feedback-modal-button-submit-border-color-hover);color:var(--feedback-modal-button-submit-text-color-hover)}.feedback-modal-input-heading{display:block;font-size:14px;font-weight:300;padding-bottom:10px}.feedback-modal-footer{font-size:12px;text-align:center}.feedback-modal-footer a{color:var(--feedback-modal-footer-link);font-weight:500;text-decoration:none}.feedback-logo,.feedback-footer-text{display:block;text-align:center;margin-top:5px}.feedback-footer-text{margin-top:10px;line-height:1.5}.feedback-modal-close{background-color:var(--feedback-modal-close-bg-color);border:0;border-radius:50%;cursor:pointer;height:22px;margin-left:auto;padding:0;width:22px}.feedback-modal-close svg{stroke:var(--feedback-modal-close-color)}.feedback-modal-screenshot{background-color:var(--feedback-modal-screenshot-bg-color);height:100%;left:0;position:fixed;top:0;width:100%;z-index:var(--feedback-modal-screnshot-z-index)}.feedback-modal-screenshot-header{align-items:center;background-color:var(--feedback-modal-screenshot-header-bg-color);border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);box-sizing:border-box;color:var(--feedback-modal-screenshot-header-text-color);cursor:pointer;display:flex;left:50%;top:20px;transform:translateX(-50%);padding:10px;position:fixed;width:max-content;z-index:var(--feedback-modal-screenshot-header-z-index)}.feedback-modal-screenshot-close{height:24px;padding-left:10px;width:24px}.feedback-modal-screenshot-close svg{stroke:var(--feedback-modal-close-color)}.feedback-modal-message{font-size:var(--feedback-modal-message-font-size);margin-top:0}.feedback-modal-element-hover{background-color:transparent;cursor:pointer;border:1px solid var(--feedback-modal-element-hover-border-color)}.feedback-modal-element-selected{background-color:transparent;border:3px solid var(--feedback-modal-element-selected-border-color) !important;box-shadow:0 0 0 2px rgba(0, 123, 255, 0.3) !important}.screenshot-preview{display:inline-block;width:30px;height:30px;overflow:hidden;border-radius:4px;margin-right:10px;box-shadow:0 2px 4px rgba(0, 0, 0, 0.1);cursor:pointer;transition:transform 0.2s ease}.screenshot-preview:hover{transform:scale(1.1)}.screenshot-preview img{width:100%;height:100%;object-fit:cover}.screenshot-loading{display:inline-flex;align-items:center;margin-right:8px}@media screen and (min-width: 768px){.feedback-modal-content{max-width:var(--feedback-modal-content-max-width)}.feedback-modal-content.feedback-modal-content--bottom-right{bottom:var(--feedback-modal-content-position-bottom);left:initial;right:var(--feedback-modal-content-position-right);top:initial;transform:initial}.feedback-modal-content.feedback-modal-content--bottom-left{bottom:var(--feedback-modal-content-position-bottom);left:var(--feedback-modal-content-position-left);top:initial;transform:initial}.feedback-modal-content.feedback-modal-content--top-right{right:var(--feedback-modal-content-position-right);top:var(--feedback-modal-content-position-top);transform:initial}.feedback-modal-content.feedback-modal-content--top-left{left:var(--feedback-modal-content-position-left);top:var(--feedback-modal-content-position-top);transform:initial}.feedback-modal-content.feedback-modal-content--center-left{left:5px;right:auto;top:50%;transform:translateY(-50%)}.feedback-modal-content.feedback-modal-content--center-right{left:auto;right:5px;top:50%;transform:translateY(-50%)}.feedback-modal-content.feedback-modal-content--sidebar-left.feedback-modal-content--open,.feedback-modal-content.feedback-modal-content--sidebar-right.feedback-modal-content--open{transform:translateX(0)}.feedback-modal-content.feedback-modal-content--sidebar-left{max-width:var(--feedback-modal-content-sidebar-max-width);left:0;right:auto;height:100vh;top:0;transform:translateX(-100%);transition:transform 0.5s ease-in-out;border-radius:0}.feedback-modal-content.feedback-modal-content--sidebar-right{max-width:var(--feedback-modal-content-sidebar-max-width);left:auto;right:0;height:100vh;top:0;transform:translateX(100%);transition:transform 0.5s ease-in-out;border-radius:0}.feedback-modal-text textarea{height:150px;min-height:150px}.feedback-modal-content.feedback-modal-content--bottom-right{transform:translateY(20px)}.feedback-modal-content.feedback-modal-content--bottom-right.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--bottom-left{transform:translateY(20px)}.feedback-modal-content.feedback-modal-content--bottom-left.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--top-right{transform:translateY(-20px)}.feedback-modal-content.feedback-modal-content--top-right.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--top-left{transform:translateY(-20px)}.feedback-modal-content.feedback-modal-content--top-left.feedback-modal-content--open{transform:translateY(0)}}@keyframes feather-spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.feather-loader{animation:feather-spin 1s linear infinite;display:block}.screenshot-error-notification{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:10001;max-width:500px;width:90%;animation:slideDown 0.3s ease-out}@keyframes slideDown{from{opacity:0;transform:translateX(-50%) translateY(-20px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}.screenshot-error-content{background:#fee;border:1px solid #fcc;border-radius:8px;padding:12px 16px;display:flex;align-items:center;gap:12px;box-shadow:0 4px 12px rgba(0, 0, 0, 0.15);color:#c53030}.screenshot-error-content svg:first-child{color:#e53e3e;flex-shrink:0}.screenshot-error-content span{flex:1;font-size:14px;line-height:1.4;font-weight:500}.error-close-btn{background:none;border:none;cursor:pointer;padding:4px;border-radius:4px;color:#c53030;flex-shrink:0;transition:background-color 0.2s ease}.error-close-btn:hover{background:rgba(197, 48, 48, 0.1)}";
|
|
1070
|
+
|
|
1071
|
+
const FeedbackModal = class {
|
|
1072
|
+
constructor(hostRef) {
|
|
1073
|
+
registerInstance(this, hostRef);
|
|
1074
|
+
this.feedbackSent = createEvent(this, "feedbackSent", 7);
|
|
1075
|
+
this.feedbackError = createEvent(this, "feedbackError", 7);
|
|
1076
|
+
this.onScrollDebounced = () => {
|
|
1077
|
+
clearTimeout(this.scrollTimeout);
|
|
1078
|
+
this.scrollTimeout = setTimeout(() => {
|
|
1079
|
+
document.documentElement.classList.remove('feedback-modal-screenshot-closing');
|
|
1080
|
+
document.documentElement.style.top = '';
|
|
1081
|
+
window.removeEventListener('scroll', this.onScrollDebounced);
|
|
1082
|
+
}, 200);
|
|
1083
|
+
};
|
|
1084
|
+
this.handleSubmit = async (event) => {
|
|
1085
|
+
event.preventDefault();
|
|
1086
|
+
if (this.isEmailRequired && !this.formEmail) {
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
this.resetOverflow();
|
|
1090
|
+
this.showScreenshotMode = false;
|
|
1091
|
+
this.showScreenshotTopBar = false;
|
|
1092
|
+
this.showModal = false;
|
|
1093
|
+
this.sending = true;
|
|
1094
|
+
try {
|
|
1095
|
+
const body = {
|
|
1096
|
+
url: window.location.href,
|
|
1097
|
+
message: this.formMessage,
|
|
1098
|
+
email: this.formEmail,
|
|
1099
|
+
project: this.project,
|
|
1100
|
+
screenshot: this.encodedScreenshot,
|
|
1101
|
+
rating: this.selectedRating,
|
|
1102
|
+
ratingMode: this.ratingMode,
|
|
1103
|
+
metadata: this.metadata,
|
|
1104
|
+
verification: this.formVerification,
|
|
1105
|
+
session: localStorage.getItem('pushfeedback_sessionid') || '',
|
|
1106
|
+
};
|
|
1107
|
+
const res = await fetch('https://app.pushfeedback.com/api/feedback/', {
|
|
1108
|
+
method: 'POST',
|
|
1109
|
+
body: JSON.stringify(body),
|
|
1110
|
+
headers: {
|
|
1111
|
+
'Content-Type': 'application/json',
|
|
1112
|
+
},
|
|
1113
|
+
});
|
|
1114
|
+
if (res.status === 201) {
|
|
1115
|
+
const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
|
|
1116
|
+
this.feedbackSent.emit({ feedback: feedback_with_id });
|
|
1117
|
+
this.formSuccess = true;
|
|
1118
|
+
this.formError = false;
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
const errorText = await res.text();
|
|
1122
|
+
const response = {
|
|
1123
|
+
status: res.status,
|
|
1124
|
+
message: errorText,
|
|
1125
|
+
};
|
|
1126
|
+
this.feedbackError.emit({ error: response });
|
|
1127
|
+
this.formSuccess = false;
|
|
1128
|
+
this.formError = true;
|
|
1129
|
+
this.formErrorStatus = res.status;
|
|
1130
|
+
}
|
|
838
1131
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1132
|
+
catch (error) {
|
|
1133
|
+
const response = {
|
|
1134
|
+
status: 500,
|
|
1135
|
+
message: error,
|
|
1136
|
+
};
|
|
1137
|
+
this.feedbackError.emit({ error: response });
|
|
1138
|
+
this.formSuccess = false;
|
|
1139
|
+
this.formError = true;
|
|
1140
|
+
this.formErrorStatus = 500;
|
|
1141
|
+
}
|
|
1142
|
+
finally {
|
|
1143
|
+
this.sending = false;
|
|
1144
|
+
this.showModal = true;
|
|
848
1145
|
}
|
|
849
|
-
// Handle end of drawing
|
|
850
|
-
if (!this.isDrawing || !this.currentAnnotation)
|
|
851
|
-
return;
|
|
852
|
-
this.isDrawing = false;
|
|
853
|
-
this.annotations = [...this.annotations, this.currentAnnotation];
|
|
854
|
-
this.currentAnnotation = null;
|
|
855
|
-
this.redrawAnnotations();
|
|
856
1146
|
};
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
1147
|
+
this.close = () => {
|
|
1148
|
+
this.isAnimating = false;
|
|
1149
|
+
setTimeout(() => {
|
|
1150
|
+
this.sending = false;
|
|
1151
|
+
this.showModal = false;
|
|
1152
|
+
this.showScreenshotMode = false;
|
|
1153
|
+
this.showScreenshotTopBar = false;
|
|
1154
|
+
this.hasSelectedElement = false;
|
|
1155
|
+
this.encodedScreenshot = null;
|
|
1156
|
+
// Remove highlight from ALL selected elements
|
|
1157
|
+
document.querySelectorAll('.feedback-modal-element-selected').forEach(el => {
|
|
1158
|
+
el.classList.remove('feedback-modal-element-selected');
|
|
1159
|
+
});
|
|
1160
|
+
// Reset form states
|
|
1161
|
+
this.formSuccess = false;
|
|
1162
|
+
this.formError = false;
|
|
1163
|
+
this.formErrorStatus = 500;
|
|
1164
|
+
this.formMessage = '';
|
|
1165
|
+
this.formEmail = '';
|
|
1166
|
+
this.resetOverflow();
|
|
1167
|
+
}, 200);
|
|
872
1168
|
};
|
|
873
|
-
//
|
|
874
|
-
this.
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
{ x: annotation.startX, y: annotation.startY },
|
|
881
|
-
{ x: annotation.endX, y: annotation.endY } // End point
|
|
882
|
-
];
|
|
883
|
-
// Draw each handle
|
|
884
|
-
this.canvasContext.fillStyle = '#0070F4'; // Primary color
|
|
885
|
-
this.canvasContext.strokeStyle = '#ffffff';
|
|
886
|
-
this.canvasContext.lineWidth = 2;
|
|
887
|
-
handles.forEach(handle => {
|
|
888
|
-
this.canvasContext.fillRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
|
|
889
|
-
this.canvasContext.strokeRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
|
|
890
|
-
});
|
|
1169
|
+
// Handle screenshot events from canvas editor
|
|
1170
|
+
this.handleScreenshotReady = (event) => {
|
|
1171
|
+
this.encodedScreenshot = event.detail.screenshot;
|
|
1172
|
+
this.showModal = true;
|
|
1173
|
+
this.takingScreenshot = false;
|
|
1174
|
+
this.showCanvasEditor = false;
|
|
1175
|
+
this.autoStartCapture = false;
|
|
891
1176
|
};
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
// Calculate the scale factor between display size and actual canvas size
|
|
898
|
-
const scaleX = this.canvasRef.width / rect.width;
|
|
899
|
-
const scaleY = this.canvasRef.height / rect.height;
|
|
900
|
-
const x = (event.clientX - rect.left) * scaleX;
|
|
901
|
-
const y = (event.clientY - rect.top) * scaleY;
|
|
902
|
-
return { x, y };
|
|
1177
|
+
this.handleScreenshotCancelled = () => {
|
|
1178
|
+
this.showModal = true;
|
|
1179
|
+
this.takingScreenshot = false;
|
|
1180
|
+
this.showCanvasEditor = false;
|
|
1181
|
+
this.autoStartCapture = false;
|
|
903
1182
|
};
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
//
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1183
|
+
this.handleScreenshotError = (event) => {
|
|
1184
|
+
console.error('Screenshot error:', event.detail.error);
|
|
1185
|
+
// Store error message to display in feedback modal
|
|
1186
|
+
this.screenshotError = event.detail.error;
|
|
1187
|
+
this.showScreenshotError = true;
|
|
1188
|
+
// Close canvas editor and return to feedback modal
|
|
1189
|
+
this.showModal = true;
|
|
1190
|
+
this.takingScreenshot = false;
|
|
1191
|
+
this.showCanvasEditor = false;
|
|
1192
|
+
this.autoStartCapture = false;
|
|
1193
|
+
// Auto-hide error after 8 seconds
|
|
1194
|
+
setTimeout(() => {
|
|
1195
|
+
this.showScreenshotError = false;
|
|
1196
|
+
}, 8000);
|
|
914
1197
|
};
|
|
915
|
-
//
|
|
916
|
-
this.
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
const right = Math.max(annotation.startX, annotation.startX + annotation.width);
|
|
922
|
-
const top = Math.min(annotation.startY, annotation.startY + annotation.height);
|
|
923
|
-
const bottom = Math.max(annotation.startY, annotation.startY + annotation.height);
|
|
924
|
-
return x >= left - tolerance && x <= right + tolerance &&
|
|
925
|
-
y >= top - tolerance && y <= bottom + tolerance;
|
|
926
|
-
case 'line':
|
|
927
|
-
case 'arrow':
|
|
928
|
-
// Distance from point to line
|
|
929
|
-
const A = annotation.endY - annotation.startY;
|
|
930
|
-
const B = annotation.startX - annotation.endX;
|
|
931
|
-
const C = annotation.endX * annotation.startY - annotation.startX * annotation.endY;
|
|
932
|
-
const distance = Math.abs(A * x + B * y + C) / Math.sqrt(A * A + B * B);
|
|
933
|
-
return distance <= tolerance;
|
|
934
|
-
case 'text':
|
|
935
|
-
// Simple bounding box for text
|
|
936
|
-
return x >= annotation.x - tolerance && x <= annotation.x + 100 &&
|
|
937
|
-
y >= annotation.y - 20 && y <= annotation.y + tolerance;
|
|
938
|
-
default:
|
|
939
|
-
return false;
|
|
940
|
-
}
|
|
1198
|
+
// Trigger screenshot capture
|
|
1199
|
+
this.openScreenShot = () => {
|
|
1200
|
+
this.showModal = false;
|
|
1201
|
+
this.takingScreenshot = true;
|
|
1202
|
+
this.autoStartCapture = true; // Auto-start new screenshot
|
|
1203
|
+
this.showCanvasEditor = true;
|
|
941
1204
|
};
|
|
942
|
-
//
|
|
943
|
-
this.
|
|
944
|
-
if (
|
|
945
|
-
|
|
946
|
-
const annotation = this.resizingAnnotation;
|
|
947
|
-
const index = this.annotations.findIndex(a => a === annotation);
|
|
948
|
-
if (index === -1)
|
|
949
|
-
return;
|
|
950
|
-
let updatedAnnotation = Object.assign({}, annotation);
|
|
951
|
-
switch (annotation.type) {
|
|
952
|
-
case 'text':
|
|
953
|
-
// Text resize logic (existing)
|
|
954
|
-
const deltaX = currentPos.x - this.dragStartPos.x;
|
|
955
|
-
const deltaY = currentPos.y - this.dragStartPos.y;
|
|
956
|
-
const avgDelta = (deltaX + deltaY) / 2;
|
|
957
|
-
const newSize = Math.max(8, Math.min(72, this.resizeStartSize + avgDelta * 0.5));
|
|
958
|
-
updatedAnnotation.fontSize = Math.round(newSize);
|
|
959
|
-
break;
|
|
960
|
-
case 'rectangle':
|
|
961
|
-
// Rectangle resize logic - only bottom-right corner
|
|
962
|
-
const rectDeltaX = currentPos.x - this.dragStartPos.x;
|
|
963
|
-
const rectDeltaY = currentPos.y - this.dragStartPos.y;
|
|
964
|
-
// Update width and height based on original dimensions plus delta
|
|
965
|
-
updatedAnnotation.width = Math.max(10, this.resizeStartDimensions.width + rectDeltaX); // Minimum width of 10px
|
|
966
|
-
updatedAnnotation.height = Math.max(10, this.resizeStartDimensions.height + rectDeltaY); // Minimum height of 10px
|
|
967
|
-
break;
|
|
968
|
-
case 'line':
|
|
969
|
-
case 'arrow':
|
|
970
|
-
// Line/arrow resize logic - move endpoints
|
|
971
|
-
if (this.resizeHandle === 'start') {
|
|
972
|
-
updatedAnnotation.startX = currentPos.x;
|
|
973
|
-
updatedAnnotation.startY = currentPos.y;
|
|
974
|
-
}
|
|
975
|
-
else if (this.resizeHandle === 'end') {
|
|
976
|
-
updatedAnnotation.endX = currentPos.x;
|
|
977
|
-
updatedAnnotation.endY = currentPos.y;
|
|
978
|
-
}
|
|
979
|
-
break;
|
|
1205
|
+
// Open canvas editor for existing screenshot
|
|
1206
|
+
this.openCanvasEditor = (event) => {
|
|
1207
|
+
if (event) {
|
|
1208
|
+
event.stopPropagation();
|
|
980
1209
|
}
|
|
981
|
-
|
|
982
|
-
this.
|
|
983
|
-
this.
|
|
984
|
-
this.redrawAnnotations();
|
|
1210
|
+
this.showModal = false;
|
|
1211
|
+
this.autoStartCapture = false; // Don't auto-start, just edit existing
|
|
1212
|
+
this.showCanvasEditor = true;
|
|
985
1213
|
};
|
|
986
1214
|
this.sending = false;
|
|
987
1215
|
this.formMessage = '';
|
|
@@ -997,28 +1225,10 @@ const FeedbackModal = class {
|
|
|
997
1225
|
this.overlayVisible = false;
|
|
998
1226
|
this.isAnimating = false;
|
|
999
1227
|
this.takingScreenshot = false;
|
|
1000
|
-
this.showPreviewModal = false;
|
|
1001
|
-
this.screenshotError = '';
|
|
1002
1228
|
this.showScreenshotError = false;
|
|
1229
|
+
this.screenshotError = '';
|
|
1003
1230
|
this.showCanvasEditor = false;
|
|
1004
|
-
this.
|
|
1005
|
-
this.canvasDrawingColor = '#ff0000';
|
|
1006
|
-
this.canvasLineWidth = 3;
|
|
1007
|
-
this.isDrawing = false;
|
|
1008
|
-
this.annotations = [];
|
|
1009
|
-
this.currentAnnotation = null;
|
|
1010
|
-
this.isDragging = false;
|
|
1011
|
-
this.draggedAnnotation = null;
|
|
1012
|
-
this.dragStartPos = null;
|
|
1013
|
-
this.showColorPicker = false;
|
|
1014
|
-
this.editingColorIndex = -1;
|
|
1015
|
-
this.isResizing = false;
|
|
1016
|
-
this.resizingAnnotation = null;
|
|
1017
|
-
this.resizeStartSize = 16;
|
|
1018
|
-
this.resizeStartDimensions = null;
|
|
1019
|
-
this.hoveredAnnotation = null;
|
|
1020
|
-
this.resizeHandle = false;
|
|
1021
|
-
this.defaultColors = ['#ff0000', '#00ff00', '#0000ff', '#000000'];
|
|
1231
|
+
this.autoStartCapture = false;
|
|
1022
1232
|
this.customFont = false;
|
|
1023
1233
|
this.emailAddress = '';
|
|
1024
1234
|
this.hideEmail = false;
|
|
@@ -1057,6 +1267,17 @@ const FeedbackModal = class {
|
|
|
1057
1267
|
this.canvasEditorTitle = 'Edit screenshot';
|
|
1058
1268
|
this.canvasEditorCancelText = 'Cancel';
|
|
1059
1269
|
this.canvasEditorSaveText = 'Save';
|
|
1270
|
+
this.editTextButtonText = 'Edit Text';
|
|
1271
|
+
this.sizeLabelText = 'Size:';
|
|
1272
|
+
this.borderLabelText = 'Border:';
|
|
1273
|
+
this.editTextPromptText = 'Edit text:';
|
|
1274
|
+
this.screenshotErrorGeneral = 'Failed to capture screenshot.';
|
|
1275
|
+
this.screenshotErrorPermission = 'Permission denied. Please allow screen sharing to take screenshots.';
|
|
1276
|
+
this.screenshotErrorNotSupported = 'Screen capture is not supported in this browser.';
|
|
1277
|
+
this.screenshotErrorNotFound = 'No screen sources available for capture.';
|
|
1278
|
+
this.screenshotErrorCancelled = 'Screenshot capture was cancelled.';
|
|
1279
|
+
this.screenshotErrorBrowserNotSupported = 'Your browser does not support screen capture. Please use a browser like Chrome, Firefox, or Safari.';
|
|
1280
|
+
this.screenshotErrorUnexpected = 'An unexpected error occurred. Please try again.';
|
|
1060
1281
|
}
|
|
1061
1282
|
componentWillLoad() {
|
|
1062
1283
|
if (this.fetchData)
|
|
@@ -1091,63 +1312,6 @@ const FeedbackModal = class {
|
|
|
1091
1312
|
handleEmailInput(event) {
|
|
1092
1313
|
this.formEmail = event.target.value;
|
|
1093
1314
|
}
|
|
1094
|
-
async captureViewportScreenshot() {
|
|
1095
|
-
try {
|
|
1096
|
-
// Check if Screen Capture API is supported
|
|
1097
|
-
if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
|
|
1098
|
-
throw new Error('Screen Capture API is not supported in this browser');
|
|
1099
|
-
}
|
|
1100
|
-
// Request screen capture with preference for current tab
|
|
1101
|
-
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
1102
|
-
video: {
|
|
1103
|
-
mediaSource: 'screen',
|
|
1104
|
-
width: { ideal: window.innerWidth },
|
|
1105
|
-
height: { ideal: window.innerHeight }
|
|
1106
|
-
},
|
|
1107
|
-
audio: false,
|
|
1108
|
-
preferCurrentTab: true
|
|
1109
|
-
});
|
|
1110
|
-
// Create video element to capture frame
|
|
1111
|
-
const video = document.createElement('video');
|
|
1112
|
-
video.srcObject = stream;
|
|
1113
|
-
video.autoplay = true;
|
|
1114
|
-
video.muted = true;
|
|
1115
|
-
return new Promise((resolve, reject) => {
|
|
1116
|
-
video.onloadedmetadata = () => {
|
|
1117
|
-
video.play();
|
|
1118
|
-
// Wait a moment for video to stabilize
|
|
1119
|
-
setTimeout(() => {
|
|
1120
|
-
try {
|
|
1121
|
-
// Create canvas to capture frame
|
|
1122
|
-
const canvas = document.createElement('canvas');
|
|
1123
|
-
canvas.width = video.videoWidth;
|
|
1124
|
-
canvas.height = video.videoHeight;
|
|
1125
|
-
const ctx = canvas.getContext('2d');
|
|
1126
|
-
ctx.drawImage(video, 0, 0);
|
|
1127
|
-
// Stop the stream
|
|
1128
|
-
stream.getTracks().forEach(track => track.stop());
|
|
1129
|
-
// Convert to data URL
|
|
1130
|
-
const dataUrl = canvas.toDataURL('image/png');
|
|
1131
|
-
console.log('Screenshot captured successfully using Screen Capture API');
|
|
1132
|
-
resolve(dataUrl);
|
|
1133
|
-
}
|
|
1134
|
-
catch (error) {
|
|
1135
|
-
stream.getTracks().forEach(track => track.stop());
|
|
1136
|
-
reject(error);
|
|
1137
|
-
}
|
|
1138
|
-
}, 100);
|
|
1139
|
-
};
|
|
1140
|
-
video.onerror = (_) => {
|
|
1141
|
-
stream.getTracks().forEach(track => track.stop());
|
|
1142
|
-
reject(new Error('Failed to load video for screenshot capture'));
|
|
1143
|
-
};
|
|
1144
|
-
});
|
|
1145
|
-
}
|
|
1146
|
-
catch (error) {
|
|
1147
|
-
console.error('Screen capture failed:', error);
|
|
1148
|
-
throw error;
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
1315
|
handleCheckboxChange(event) {
|
|
1152
1316
|
this.isPrivacyChecked = event.target.checked;
|
|
1153
1317
|
}
|
|
@@ -1158,7 +1322,7 @@ const FeedbackModal = class {
|
|
|
1158
1322
|
this.selectedRating = newRating;
|
|
1159
1323
|
}
|
|
1160
1324
|
render() {
|
|
1161
|
-
return (h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''}` }, this.showScreenshotError && (h("div", { class: "screenshot-error-notification" }, h("div", { class: "screenshot-error-content" }, h("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("circle", { cx: "12", cy: "12", r: "10" }), h("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), h("line", { x1: "9", y1: "9", x2: "15", y2: "15" })), h("span", null, this.screenshotError), h("button", { class: "error-close-btn", onClick: () => this.showScreenshotError = false, title: "Close" }, h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))))), this.showModal && (h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}` })), this.showModal && (h("div", { class: `feedback-modal-content feedback-modal-content--${this.modalPosition} ${this.isAnimating ? 'feedback-modal-content--open' : ''}`, ref: (el) => (this.modalContent = el) }, h("div", { class: "feedback-modal-header" }, !this.formSuccess && !this.formError ? (h("span", null, this.modalTitle)) : this.formSuccess ? (h("span", null, this.modalTitleSuccess)) : (h("span", null, this.modalTitleError)), h("button", { class: "feedback-modal-close", onClick: this.close }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "#191919", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather feather-x" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))), h("div", { class: "feedback-modal-body" }, !this.formSuccess && !this.formError ? (h("form", { onSubmit: this.handleSubmit }, !this.hideRating && (h("div", { class: "feedback-modal-rating" }, this.ratingMode === 'thumbs' ? (h("div", { class: "feedback-modal-rating-content" }, h("span", { class: "feedback-modal-input-heading" }, this.ratingPlaceholder), h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs" }, h("button", { title: "Yes", class: `feedback-modal-rating-button ${this.selectedRating === 1
|
|
1325
|
+
return (h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''}` }, this.showCanvasEditor && (h("canvas-editor", { ref: (el) => this.canvasEditorRef = el, "canvas-editor-title": this.canvasEditorTitle, "canvas-editor-cancel-text": this.canvasEditorCancelText, "canvas-editor-save-text": this.canvasEditorSaveText, "screenshot-taking-text": this.screenshotTakingText, "screenshot-attached-text": this.screenshotAttachedText, "screenshot-button-text": this.screenshotButtonText, "auto-start-screenshot": this.autoStartCapture, "existing-screenshot": this.encodedScreenshot || '', "edit-text-button-text": this.editTextButtonText, "size-label-text": this.sizeLabelText, "border-label-text": this.borderLabelText, "edit-text-prompt-text": this.editTextPromptText, "screenshot-error-general": this.screenshotErrorGeneral, "screenshot-error-permission": this.screenshotErrorPermission, "screenshot-error-not-supported": this.screenshotErrorNotSupported, "screenshot-error-not-found": this.screenshotErrorNotFound, "screenshot-error-cancelled": this.screenshotErrorCancelled, "screenshot-error-browser-not-supported": this.screenshotErrorBrowserNotSupported, "screenshot-error-unexpected": this.screenshotErrorUnexpected, onScreenshotReady: this.handleScreenshotReady, onScreenshotCancelled: this.handleScreenshotCancelled, onScreenshotFailed: this.handleScreenshotError })), this.showScreenshotError && (h("div", { class: "screenshot-error-notification" }, h("div", { class: "screenshot-error-content" }, h("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("circle", { cx: "12", cy: "12", r: "10" }), h("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), h("line", { x1: "9", y1: "9", x2: "15", y2: "15" })), h("span", null, this.screenshotError), h("button", { class: "error-close-btn", onClick: () => this.showScreenshotError = false, title: "Close" }, h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))))), this.showModal && (h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}` })), this.showModal && (h("div", { class: `feedback-modal-content feedback-modal-content--${this.modalPosition} ${this.isAnimating ? 'feedback-modal-content--open' : ''}`, ref: (el) => (this.modalContent = el) }, h("div", { class: "feedback-modal-header" }, !this.formSuccess && !this.formError ? (h("span", null, this.modalTitle)) : this.formSuccess ? (h("span", null, this.modalTitleSuccess)) : (h("span", null, this.modalTitleError)), h("button", { class: "feedback-modal-close", onClick: this.close }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "#191919", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather feather-x" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))), h("div", { class: "feedback-modal-body" }, !this.formSuccess && !this.formError ? (h("form", { onSubmit: this.handleSubmit }, !this.hideRating && (h("div", { class: "feedback-modal-rating" }, this.ratingMode === 'thumbs' ? (h("div", { class: "feedback-modal-rating-content" }, h("span", { class: "feedback-modal-input-heading" }, this.ratingPlaceholder), h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs" }, h("button", { title: "Yes", class: `feedback-modal-rating-button ${this.selectedRating === 1
|
|
1162
1326
|
? 'feedback-modal-rating-button--selected'
|
|
1163
1327
|
: ''}`, onClick: (event) => {
|
|
1164
1328
|
event.preventDefault();
|
|
@@ -1174,7 +1338,7 @@ const FeedbackModal = class {
|
|
|
1174
1338
|
event.preventDefault();
|
|
1175
1339
|
this.handleRatingChange(rating);
|
|
1176
1340
|
} }, 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 24 24", fill: "none", stroke: "#666", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather-loader" }, h("line", { x1: "12", y1: "2", x2: "12", y2: "6" }), h("line", { x1: "12", y1: "18", x2: "12", y2: "22" }), h("line", { x1: "4.93", y1: "4.93", x2: "7.76", y2: "7.76" }), h("line", { x1: "16.24", y1: "16.24", x2: "19.07", y2: "19.07" }), h("line", { x1: "2", y1: "12", x2: "6", y2: "12" }), h("line", { x1: "18", y1: "12", x2: "22", y2: "12" }), h("line", { x1: "4.93", y1: "19.07", x2: "7.76", y2: "16.24" }), h("line", { x1: "16.24", y1: "7.76", x2: "19.07", y2: "4.93" })))), this.takingScreenshot ? this.screenshotTakingText :
|
|
1177
|
-
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 }))))))
|
|
1341
|
+
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 }))))))));
|
|
1178
1342
|
}
|
|
1179
1343
|
componentDidRender() {
|
|
1180
1344
|
if (this.showModal) {
|
|
@@ -1194,4 +1358,4 @@ const FeedbackModal = class {
|
|
|
1194
1358
|
};
|
|
1195
1359
|
FeedbackModal.style = feedbackModalCss;
|
|
1196
1360
|
|
|
1197
|
-
export { FeedbackButton as feedback_button, FeedbackModal as feedback_modal };
|
|
1361
|
+
export { CanvasEditor as canvas_editor, FeedbackButton as feedback_button, FeedbackModal as feedback_modal };
|