1
- import { getCurrentHub } from '@sentry/core' ;
1
+ // import { getCurrentHub } from '@sentry/core';
2
2
import type { Integration } from '@sentry/types' ;
3
3
import { isNodeEnv } from '@sentry/utils' ;
4
4
5
5
import type { FeedbackConfigurationWithDefaults } from './types' ;
6
6
import { sendFeedbackRequest } from './util/sendFeedbackRequest' ;
7
+ import { Actor } from './widget/Actor' ;
7
8
import { createActorStyles } from './widget/Actor.css' ;
8
9
import { Dialog } from './widget/Dialog' ;
9
10
import { createDialogStyles } from './widget/Dialog.css' ;
10
- import { Icon } from './widget/Icon' ;
11
11
12
12
export { sendFeedbackRequest } ;
13
13
@@ -50,13 +50,31 @@ export class Feedback implements Integration {
50
50
*/
51
51
public name : string ;
52
52
53
+ /**
54
+ * Feedback configuration options
55
+ */
53
56
public options : FeedbackConfigurationWithDefaults ;
54
57
55
- private actor : HTMLButtonElement | null = null ;
56
- private dialog : ReturnType < typeof Dialog > | null = null ;
57
- private host : HTMLDivElement | null = null ;
58
- private shadow : ShadowRoot | null = null ;
59
- private isDialogOpen : boolean = false ;
58
+ /**
59
+ * Reference to widget actor element (button that opens dialog).
60
+ */
61
+ private _actor : ReturnType < typeof Actor > | null ;
62
+ /**
63
+ * Reference to dialog element
64
+ */
65
+ private _dialog : ReturnType < typeof Dialog > | null ;
66
+ /**
67
+ * Reference to the host element where widget is inserted
68
+ */
69
+ private _host : HTMLDivElement | null ;
70
+ /**
71
+ * Refernce to Shadow DOM root
72
+ */
73
+ private _shadow : ShadowRoot | null ;
74
+ /**
75
+ * State property to track if dialog is currently open
76
+ */
77
+ private _isDialogOpen : boolean ;
60
78
61
79
public constructor ( {
62
80
showEmail = true ,
@@ -80,7 +98,14 @@ export class Feedback implements Integration {
80
98
namePlaceholder = 'Your Name' ,
81
99
nameLabel = 'Name' ,
82
100
} : Partial < FeedbackConfigurationWithDefaults > = { } ) {
101
+ // Initializations
83
102
this . name = Feedback . id ;
103
+ this . _actor = null ;
104
+ this . _dialog = null ;
105
+ this . _host = null ;
106
+ this . _shadow = null ;
107
+ this . _isDialogOpen = false ;
108
+
84
109
this . options = {
85
110
isAnonymous,
86
111
isEmailRequired,
@@ -117,12 +142,54 @@ export class Feedback implements Integration {
117
142
this . _injectWidget ( ) ;
118
143
}
119
144
145
+ /**
146
+ * Removes the Feedback widget
147
+ */
148
+ public remove ( ) : void {
149
+ if ( this . _host ) {
150
+ this . _host . remove ( ) ;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Opens the Feedback dialog form
156
+ */
157
+ public openDialog ( ) : void {
158
+ if ( this . _dialog ) {
159
+ this . _dialog . openDialog ( ) ;
160
+ return ;
161
+ }
162
+
163
+ if ( ! this . _shadow ) {
164
+ this . _shadow = this . _createShadowHost ( ) ;
165
+ }
166
+
167
+ this . _shadow . appendChild ( createDialogStyles ( document , THEME ) ) ;
168
+ this . _dialog = Dialog ( { onCancel : this . closeDialog , options : this . options } ) ;
169
+ this . _shadow . appendChild ( this . _dialog . $el ) ;
170
+ this . _actor && this . _actor . hide ( ) ;
171
+ }
172
+
173
+ /**
174
+ * Closes the dialog
175
+ */
176
+ public closeDialog = ( ) : void => {
177
+ if ( this . _dialog ) {
178
+ this . _dialog . closeDialog ( ) ;
179
+ }
180
+
181
+ // TODO: if has default actor, show the button
182
+ if ( this . _actor ) {
183
+ this . _actor . show ( ) ;
184
+ }
185
+ } ;
186
+
120
187
/**
121
188
*
122
189
*/
123
- protected _injectWidget ( ) {
190
+ protected _injectWidget ( ) : void {
124
191
// TODO: This is only here for hot reloading
125
- if ( this . host ) {
192
+ if ( this . _host ) {
126
193
this . remove ( ) ;
127
194
}
128
195
const existingFeedback = document . querySelector ( '#sentry-feedback' ) ;
@@ -132,94 +199,59 @@ export class Feedback implements Integration {
132
199
133
200
// TODO: End hotloading
134
201
135
- this . createWidgetButton ( ) ;
202
+ this . _shadow = this . _createShadowHost ( ) ;
203
+ this . _createWidgetActor ( ) ;
136
204
137
- if ( ! this . host ) {
205
+ if ( ! this . _host ) {
138
206
return ;
139
207
}
140
208
141
- document . body . appendChild ( this . host ) ;
142
- }
143
-
144
- /**
145
- * Removes the Feedback widget
146
- */
147
- public remove ( ) {
148
- if ( this . host ) {
149
- this . host . remove ( ) ;
150
- }
209
+ document . body . appendChild ( this . _host ) ;
151
210
}
152
211
153
212
/**
154
- *
213
+ * Creates the host element of widget's shadow DOM
155
214
*/
156
- protected createWidgetButton ( ) {
215
+ protected _createShadowHost ( ) : ShadowRoot {
157
216
// Create the host
158
- this . host = document . createElement ( 'div' ) ;
159
- this . host . id = 'sentry-feedback' ;
160
- this . shadow = this . host . attachShadow ( { mode : 'open' } ) ;
161
-
162
- this . shadow . appendChild ( createActorStyles ( document , THEME ) ) ;
163
-
164
- const actorButton = document . createElement ( 'button' ) ;
165
- actorButton . type = 'button' ;
166
- actorButton . className = 'widget-actor' ;
167
- actorButton . ariaLabel = this . options . buttonLabel ;
168
- const buttonTextEl = document . createElement ( 'span' ) ;
169
- buttonTextEl . className = 'widget-actor-text' ;
170
- buttonTextEl . textContent = this . options . buttonLabel ;
171
- this . shadow . appendChild ( actorButton ) ;
172
-
173
- actorButton . appendChild ( Icon ( { color : THEME . light . foreground } ) ) ;
174
- actorButton . appendChild ( buttonTextEl ) ;
175
-
176
- actorButton . addEventListener ( 'click' , this . handleActorClick . bind ( this ) ) ;
177
- this . actor = actorButton ;
178
- }
179
-
180
- /**
181
- *
182
- */
183
- protected handleActorClick ( ) {
184
- console . log ( 'button clicked' ) ;
185
-
186
- // Open dialog
187
- if ( ! this . isDialogOpen ) {
188
- this . openDialog ( ) ;
189
- }
217
+ this . _host = document . createElement ( 'div' ) ;
218
+ this . _host . id = 'sentry-feedback' ;
190
219
191
- // Hide actor button
192
- if ( this . actor ) {
193
- this . actor . classList . add ( 'hidden' ) ;
194
- }
220
+ // Create the shadow root
221
+ return this . _host . attachShadow ( { mode : 'open' } ) ;
195
222
}
196
223
197
224
/**
198
- * Opens the Feedback dialog form
225
+ * Creates the host element of our shadow DOM as well as the actor
199
226
*/
200
- public openDialog ( ) {
201
- if ( this . dialog ) {
202
- this . dialog . openDialog ( ) ;
227
+ protected _createWidgetActor ( ) : void {
228
+ if ( ! this . _shadow ) {
229
+ // This shouldn't happen... we could call `_createShadowHost` if this is the case?
203
230
return ;
204
231
}
205
232
206
- this . shadow ?. appendChild ( createDialogStyles ( document , THEME ) ) ;
207
- this . dialog = Dialog ( { onCancel : this . closeDialog , options : this . options } ) ;
208
- this . shadow ?. appendChild ( this . dialog . $el ) ;
233
+ // Insert styles for actor
234
+ this . _shadow . appendChild ( createActorStyles ( document , THEME ) ) ;
235
+
236
+ // Create Actor component
237
+ this . _actor = Actor ( { options : this . options , theme : THEME , onClick : this . _handleActorClick } ) ;
238
+
239
+ this . _shadow . appendChild ( this . _actor . $el ) ;
209
240
}
210
241
211
242
/**
212
- * Closes the dialog
243
+ * Handles when the actor is clicked, opens the dialog modal and calls any
244
+ * callbacks.
213
245
*/
214
- public closeDialog = ( ) => {
215
- if ( this . dialog ) {
216
- this . dialog . closeDialog ( ) ;
246
+ protected _handleActorClick = ( ) : void => {
247
+ // Open dialog
248
+ if ( ! this . _isDialogOpen ) {
249
+ this . openDialog ( ) ;
217
250
}
218
251
219
- // TODO: if has default actor, show the button
220
-
221
- if ( this . actor ) {
222
- this . actor . classList . remove ( 'hidden' ) ;
252
+ // Hide actor button
253
+ if ( this . _actor ) {
254
+ this . _actor . classList . add ( 'hidden' ) ;
223
255
}
224
256
} ;
225
257
}
0 commit comments