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.
Files changed (27) hide show
  1. package/dist/cjs/{feedback-button_2.cjs.entry.js → canvas-editor_3.cjs.entry.js} +883 -718
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. package/dist/cjs/pushfeedback.cjs.js +1 -1
  4. package/dist/collection/collection-manifest.json +1 -0
  5. package/dist/collection/components/canvas-editor/canvas-editor.css +404 -0
  6. package/dist/collection/components/canvas-editor/canvas-editor.js +1282 -0
  7. package/dist/collection/components/feedback-button/feedback-button.js +220 -0
  8. package/dist/collection/components/feedback-modal/feedback-modal.css +2 -458
  9. package/dist/collection/components/feedback-modal/feedback-modal.js +247 -782
  10. package/dist/components/canvas-editor.d.ts +11 -0
  11. package/dist/components/canvas-editor.js +6 -0
  12. package/dist/components/canvas-editor2.js +917 -0
  13. package/dist/components/feedback-button.js +40 -1
  14. package/dist/components/feedback-modal2.js +68 -784
  15. package/dist/components/index.d.ts +1 -0
  16. package/dist/components/index.js +1 -0
  17. package/dist/esm/{feedback-button_2.entry.js → canvas-editor_3.entry.js} +883 -719
  18. package/dist/esm/loader.js +1 -1
  19. package/dist/esm/pushfeedback.js +1 -1
  20. package/dist/pushfeedback/p-2c39091c.entry.js +1 -0
  21. package/dist/pushfeedback/pushfeedback.esm.js +1 -1
  22. package/dist/types/components/canvas-editor/canvas-editor.d.ts +108 -0
  23. package/dist/types/components/feedback-button/feedback-button.d.ts +11 -0
  24. package/dist/types/components/feedback-modal/feedback-modal.d.ts +22 -79
  25. package/dist/types/components.d.ts +102 -0
  26. package/package.json +3 -4
  27. 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 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}}";
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 FeedbackButton = class {
5
+ const CanvasEditor = class {
6
6
  constructor(hostRef) {
7
7
  registerInstance(this, hostRef);
8
- this.feedbackSent = createEvent(this, "feedbackSent", 7);
9
- this.feedbackError = createEvent(this, "feedbackError", 7);
10
- this.buttonPosition = 'default';
11
- this.buttonStyle = 'default';
12
- this.hideIcon = false;
13
- this.hideMobile = false;
14
- this.sessionId = '';
15
- this.metadata = '';
16
- this.submit = false;
17
- this.customFont = false;
18
- this.emailAddress = '';
19
- this.isEmailRequired = false;
20
- this.fetchData = true;
21
- this.hideEmail = false;
22
- this.hidePrivacyPolicy = true;
23
- this.hideRating = false;
24
- this.hideScreenshotButton = false;
25
- this.modalPosition = 'center';
26
- this.project = '';
27
- this.rating = undefined;
28
- this.ratingMode = 'thumbs';
29
- this.canvasEditorTitle = 'Edit screenshot';
30
- this.canvasEditorCancelText = 'Cancel';
31
- this.canvasEditorSaveText = 'Save';
32
- this.emailPlaceholder = 'Email address (optional)';
33
- this.errorMessage = 'Please try again later.';
34
- this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
35
- this.errorMessage404 = 'We could not find the provided project id in PushFeedback.';
36
- this.footerText = '';
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
- else {
61
- localStorage.setItem('pushfeedback_sessionid', this.sessionId);
62
- }
63
- }
64
- componentDidLoad() {
65
- if (this.buttonPosition === 'center-right') {
66
- const buttonContent = this.el.shadowRoot.querySelector('.feedback-button-content');
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
- buttonContent.style.right = `${((buttonContent.offsetWidth + adjustement) / 2) * -1}px`;
72
- }
73
- if (!this.customFont) {
74
- this.loadInterFont();
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
- if (res.status === 201) {
163
- const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
164
- this.feedbackSent.emit({ feedback: feedback_with_id });
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
- const errorText = await res.text();
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
- catch (error) {
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.showModal = true;
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.encodedScreenshot = finalDataUrl;
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; // Account for reduced padding (16px * 2)
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); // Never scale up, only down
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 (this scales the canvas visually)
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 resize handle if this annotation is hovered
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); // Same as line
183
+ this.drawLineResizeHandles(annotation);
505
184
  }
506
185
  break;
507
186
  case 'text':
508
- const fontSize = annotation.fontSize || 16;
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 resize handle if this annotation is hovered
513
- if (this.hoveredAnnotation === annotation) {
514
- this.drawTextResizeHandle(annotation);
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 resize handle for text annotation
520
- this.drawTextResizeHandle = (annotation) => {
521
- if (!this.canvasContext || annotation.type !== 'text')
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 || 16;
230
+ const fontSize = annotation.fontSize || 24;
524
231
  const textWidth = this.getTextWidth(annotation.text, fontSize);
525
- const handleSize = 8;
526
- const handleX = annotation.x + textWidth;
527
- const handleY = annotation.y;
528
- // Draw resize handle (small square) - using widget primary color
529
- this.canvasContext.fillStyle = '#0070F4'; // var(--feedback-primary-color)
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.fillRect(handleX - handleSize / 2, handleY - handleSize / 2, handleSize, handleSize);
533
- this.canvasContext.strokeRect(handleX - handleSize / 2, handleY - handleSize / 2, handleSize, handleSize);
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
- // Check if point is in resize handle for any annotation type
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
- // Get text width for resize handle positioning
636
- this.getTextWidth = (text, fontSize) => {
637
- // Approximate text width calculation
638
- return text.length * fontSize * 0.6;
639
- };
640
- // Start text resize
641
- this.startTextResize = (annotation, startPos) => {
642
- this.isResizing = true;
643
- this.resizingAnnotation = annotation;
644
- this.resizeStartSize = annotation.fontSize || 16;
645
- this.dragStartPos = startPos;
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
- // Handle text resize
648
- this.handleTextResize = (currentPos) => {
649
- if (!this.resizingAnnotation || !this.dragStartPos)
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 deltaX = currentPos.x - this.dragStartPos.x;
652
- const deltaY = currentPos.y - this.dragStartPos.y;
653
- const avgDelta = (deltaX + deltaY) / 2;
654
- // Calculate new font size (minimum 8px, maximum 72px)
655
- const newSize = Math.max(8, Math.min(72, this.resizeStartSize + avgDelta * 0.5));
656
- // Update annotation font size
657
- const index = this.annotations.findIndex(a => a === this.resizingAnnotation);
658
- if (index !== -1) {
659
- this.annotations[index] = Object.assign(Object.assign({}, this.resizingAnnotation), { fontSize: Math.round(newSize) });
660
- this.resizingAnnotation = this.annotations[index];
661
- }
662
- this.redrawAnnotations();
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 any annotation type
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 === 'text') {
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
- // Enhanced mouse down handler with resize detection for all annotation types
679
- this.handleCanvasMouseDown = (event) => {
680
- if (!this.canvasRef)
398
+ // Handle resize for different annotation types
399
+ this.handleResize = (currentPos) => {
400
+ if (!this.resizingAnnotation || !this.dragStartPos)
681
401
  return;
682
- // Disable drawing on mobile devices
683
- if (window.innerWidth <= 768)
402
+ const annotation = this.resizingAnnotation;
403
+ const index = this.annotations.findIndex(a => a === annotation);
404
+ if (index === -1)
684
405
  return;
685
- // Close color picker if open
686
- if (this.showColorPicker) {
687
- this.closeColorPicker();
688
- }
689
- const coords = this.getCanvasCoordinates(event);
690
- // Check if clicking on existing annotation first
691
- const found = this.findAnnotationAt(coords.x, coords.y);
692
- if (found) {
693
- // Check if clicking on resize handle for any annotation type
694
- const handle = this.isPointInResizeHandle(coords.x, coords.y, found.annotation);
695
- if (handle) {
696
- this.startResize(found.annotation, handle, coords);
697
- this.canvasRef.style.cursor = 'nw-resize';
698
- return;
699
- }
700
- // Start dragging existing annotation
701
- if (!this.isDrawing) {
702
- this.isDragging = true;
703
- this.draggedAnnotation = found.annotation;
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('Enter text:');
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: 16
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 any annotation type
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 any annotation type
800
- const handle = this.isPointInResizeHandle(coords.x, coords.y, found.annotation);
801
- if (handle) {
802
- this.canvasRef.style.cursor = 'nw-resize';
803
- this.hoveredAnnotation = found.annotation;
804
- this.redrawAnnotations();
805
- return;
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
- // Handle end of dragging
840
- if (this.isDragging) {
841
- this.isDragging = false;
842
- this.draggedAnnotation = null;
843
- this.dragStartPos = null;
844
- if (this.canvasRef) {
845
- this.canvasRef.style.cursor = 'crosshair';
846
- }
847
- return;
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
- // Draw resize handles for rectangle annotation (only bottom-right corner)
858
- this.drawRectangleResizeHandles = (annotation) => {
859
- if (!this.canvasContext || annotation.type !== 'rectangle')
860
- return;
861
- const handleSize = 8;
862
- const right = annotation.startX + annotation.width;
863
- const bottom = annotation.startY + annotation.height;
864
- // Only draw bottom-right corner handle
865
- const handle = { x: right, y: bottom };
866
- // Draw the handle
867
- this.canvasContext.fillStyle = '#0070F4'; // Primary color
868
- this.canvasContext.strokeStyle = '#ffffff';
869
- this.canvasContext.lineWidth = 2;
870
- this.canvasContext.fillRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
871
- this.canvasContext.strokeRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
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
- // Draw resize handles for line/arrow annotation
874
- this.drawLineResizeHandles = (annotation) => {
875
- if (!this.canvasContext || (annotation.type !== 'line' && annotation.type !== 'arrow'))
876
- return;
877
- const handleSize = 8;
878
- // Define handle positions (2 endpoints)
879
- const handles = [
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
- // Convert screen coordinates to canvas coordinates
893
- this.getCanvasCoordinates = (event) => {
894
- if (!this.canvasRef)
895
- return { x: 0, y: 0 };
896
- const rect = this.canvasRef.getBoundingClientRect();
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
- // Find annotation under mouse cursor
905
- this.findAnnotationAt = (x, y) => {
906
- // Check in reverse order (top to bottom)
907
- for (let i = this.annotations.length - 1; i >= 0; i--) {
908
- const annotation = this.annotations[i];
909
- if (this.isPointInAnnotation(x, y, annotation)) {
910
- return { annotation, index: i };
911
- }
912
- }
913
- return null;
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
- // Check if point is within annotation bounds
916
- this.isPointInAnnotation = (x, y, annotation) => {
917
- const tolerance = 10; // Click tolerance
918
- switch (annotation.type) {
919
- case 'rectangle':
920
- const left = Math.min(annotation.startX, annotation.startX + annotation.width);
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
- // Handle resize for different annotation types
943
- this.handleResize = (currentPos) => {
944
- if (!this.resizingAnnotation || !this.dragStartPos)
945
- return;
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
- // Update annotation in array
982
- this.annotations[index] = updatedAnnotation;
983
- this.resizingAnnotation = updatedAnnotation;
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.canvasDrawingTool = 'rectangle';
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 })))))), this.showCanvasEditor && (h("div", { class: "canvas-editor-overlay" }, h("div", { class: "canvas-editor-modal" }, h("div", { class: "canvas-editor-header" }, h("div", { class: "canvas-editor-title" }, h("h3", null, this.canvasEditorTitle)), h("div", { class: "canvas-editor-toolbar" }, h("div", { class: "toolbar-section" }, h("div", { class: "tool-group" }, h("button", { class: `tool-btn ${this.canvasDrawingTool === 'rectangle' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'rectangle', title: "Rectangle" }, h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'line' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'line', title: "Line" }, h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("line", { x1: "5", y1: "12", x2: "19", y2: "12" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'arrow' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'arrow', title: "Arrow" }, h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("line", { x1: "7", y1: "17", x2: "17", y2: "7" }), h("polyline", { points: "7,7 17,7 17,17" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'text' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'text', title: "Text" }, h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("polyline", { points: "4,7 4,4 20,4 20,7" }), h("line", { x1: "9", y1: "20", x2: "15", y2: "20" }), h("line", { x1: "12", y1: "4", x2: "12", y2: "20" }))), h("div", { class: "toolbar-divider" }), h("button", { class: "tool-btn undo-btn", onClick: this.undoLastAnnotation, disabled: this.annotations.length === 0, title: "Undo" }, h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("polyline", { points: "1,4 1,10 7,10" }), h("path", { d: "M3.51,15a9,9,0,0,0,14.85-3.36,9,9,0,0,0-9.19-10.15L1.83,10" }))))), h("div", { class: "toolbar-section" }, h("div", { class: "color-palette" }, this.defaultColors.map((color, index) => (h("div", { class: "color-slot-wrapper" }, h("button", { class: `color-btn ${this.canvasDrawingColor === color ? 'active' : ''} ${this.editingColorIndex === index ? 'editing' : ''}`, style: { backgroundColor: color }, onClick: () => this.handleColorSlotClick(index), title: `Color ${index + 1} - Click to customize` }, this.editingColorIndex === index && (h("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "white", "stroke-width": "2" }, h("path", { d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" })))), this.editingColorIndex === index && this.showColorPicker && (h("div", { class: "color-picker-dropdown" }, h("input", { type: "color", value: color, onInput: (e) => this.handleColorPickerInput(e), onClick: (e) => this.handleColorPickerClick(e) })))))))), h("div", { class: "toolbar-section" }, h("div", { class: "size-control" }, h("input", { type: "range", min: "1", max: "10", value: this.canvasLineWidth, onInput: (e) => this.canvasLineWidth = parseInt(e.target.value), class: "size-slider" }), h("span", { class: "size-value" }, this.canvasLineWidth, "px"))), h("div", { class: "toolbar-section" }, h("button", { class: "action-btn secondary", onClick: this.closeCanvasEditor }, this.canvasEditorCancelText), h("button", { class: "action-btn primary", onClick: this.saveAnnotations }, this.canvasEditorSaveText))), h("div", { class: "canvas-editor-content" }, h("canvas", { ref: (el) => this.canvasRef = el, class: "annotation-canvas", onMouseDown: this.handleCanvasMouseDown, onMouseMove: this.handleCanvasMouseMove, onMouseUp: this.handleCanvasMouseUp, onMouseLeave: this.handleCanvasMouseUp }))))))));
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 };