1
- import { assertMode } from "./assertMode.js" ;
2
- import { options } from "./options.js" ;
3
- import { disableTypeChecking } from "./validateType.js" ;
4
- import { enableTypeChecking } from "./validateType.js" ;
5
- import { warnedTable } from "./warnedTable.js" ;
6
- import { Warning } from "./Warning.js" ;
1
+ import { assertMode } from "./assertMode.js" ;
2
+ import { options } from "./options.js" ;
3
+ import { createTable } from "./warnedTable.js" ;
4
+ import { Warning } from "./Warning.js" ;
5
+ /**
6
+ * @typedef {MessageEvent<{action: string}> } MessageEventRTI
7
+ */
7
8
/**
8
9
* @param {HTMLDivElement } div - The <div>.
9
10
*/
@@ -58,10 +59,11 @@ class TypePanel {
58
59
buttonLoadState = document . createElement ( 'button' ) ;
59
60
buttonSaveState = document . createElement ( 'button' ) ;
60
61
buttonClear = document . createElement ( 'button' ) ;
62
+ warnedTable = createTable ( ) ;
61
63
constructor ( ) {
62
64
const {
63
65
div, inputEnable, spanErrors, span, select, option_spam, option_once, option_never,
64
- buttonHide, buttonLoadState, buttonSaveState, buttonClear
66
+ buttonHide, buttonLoadState, buttonSaveState, buttonClear, warnedTable ,
65
67
} = this ;
66
68
div . style . position = "absolute" ;
67
69
div . style . bottom = "0px" ;
@@ -72,9 +74,9 @@ class TypePanel {
72
74
inputEnable . type = "checkbox" ;
73
75
inputEnable . onchange = ( e ) => {
74
76
if ( inputEnable . checked ) {
75
- enableTypeChecking ( ) ;
77
+ this . enableTypeChecking ( ) ;
76
78
} else {
77
- disableTypeChecking ( ) ;
79
+ this . disableTypeChecking ( ) ;
78
80
}
79
81
} ;
80
82
inputEnable . onchange ( ) ;
@@ -98,7 +100,7 @@ class TypePanel {
98
100
onchange ( ) ; // set mode in options
99
101
buttonHide . textContent = 'Hide' ;
100
102
buttonHide . onclick = ( ) => {
101
- div . style . display = 'none' ;
103
+ this . hide ( ) ;
102
104
} ;
103
105
buttonLoadState . textContent = 'Load state' ;
104
106
buttonLoadState . onclick = ( ) => this . loadState ( ) ;
@@ -118,6 +120,55 @@ class TypePanel {
118
120
document . addEventListener ( "DOMContentLoaded" , finalFunc ) ;
119
121
}
120
122
this . loadState ( ) ;
123
+ // In the simplest case RTI sends its errors onto `window` to update UI state.
124
+ // If you start a Worker, you have to attach RTI yourself.
125
+ window . addEventListener ( 'message' , ( e ) => {
126
+ const { data} = e ;
127
+ const { type, destination} = data ;
128
+ // console.log("TypePanel Message event", e);
129
+ // console.log("TypePanel Message data", data);
130
+ if ( type !== 'rti' ) {
131
+ return ;
132
+ }
133
+ if ( destination !== 'ui' ) {
134
+ return ;
135
+ }
136
+ this . handleEvent ( e ) ;
137
+ } ) ;
138
+ }
139
+ hide ( ) {
140
+ this . div . style . display = 'none' ;
141
+ }
142
+ show ( ) {
143
+ this . div . style . display = '' ;
144
+ }
145
+ disableTypeChecking ( ) {
146
+ localStorage . setItem ( 'rti-enabled' , 'false' ) ;
147
+ this . sendEnabledDisabledStateToWorker ( ) ;
148
+ }
149
+ enableTypeChecking ( ) {
150
+ localStorage . setItem ( 'rti-enabled' , 'true' ) ;
151
+ this . sendEnabledDisabledStateToWorker ( ) ;
152
+ }
153
+ lastKnownCountWithStatus = '0-true' ;
154
+ sendEnabledDisabledStateToWorker ( ) {
155
+ // Problem: First time the worker may not even have started and `this.eventSources.size === 0`
156
+ // So we first know a RTI worker started after receiving the first message from it.
157
+ const { eventSources} = this ;
158
+ const key = `${ eventSources . size } -${ this . inputEnable . checked } ` ;
159
+ // Only update when either changed.
160
+ if ( key === this . lastKnownCountWithStatus ) {
161
+ return ;
162
+ }
163
+ this . lastKnownCountWithStatus = key ;
164
+ // console.log("Update state to eventSources", eventSources, "key", key);
165
+ this . eventSources . forEach ( eventSource => {
166
+ eventSource . postMessage ( {
167
+ type : 'rti' ,
168
+ action : this . inputEnable . checked ? 'enable' : 'disable' ,
169
+ destination : 'worker' ,
170
+ } ) ;
171
+ } ) ;
121
172
}
122
173
clear ( ) {
123
174
const { warned} = options ;
@@ -173,7 +224,7 @@ class TypePanel {
173
224
// If we didn't find it, create it.
174
225
if ( ! foundWarning ) {
175
226
foundWarning = new Warning ( 'msg' , 'value' , 'expect' , loc , name ) ;
176
- warnedTable ?. append ( foundWarning . tr ) ;
227
+ this . warnedTable ?. append ( foundWarning . tr ) ;
177
228
options . warned [ `${ loc } -${ name } ` ] = foundWarning ;
178
229
}
179
230
foundWarning . state = state ;
@@ -190,6 +241,70 @@ class TypePanel {
190
241
updateErrorCount ( ) {
191
242
this . spanErrors . innerText = `Type validation errors: ${ options . count } ` ;
192
243
}
244
+ get eventSources ( ) {
245
+ /** @type {Set<EventTarget | MessageEventSource> } */
246
+ const eventSources = new Set ( ) ;
247
+ for ( const key in options . warned ) {
248
+ const warning = options . warned [ key ] ;
249
+ if ( warning . eventSource ) {
250
+ eventSources . add ( warning . eventSource ) ;
251
+ }
252
+ }
253
+ return eventSources ;
254
+ }
255
+ /**
256
+ * @param {MessageEventRTI } event - The event from Worker, IFrame or own window.
257
+ */
258
+ addError ( event ) {
259
+ const { value, expect, loc, name, valueToString, strings, extras = [ ] , key} = event . data ;
260
+ const msg = `${ loc } > The '${ name } ' argument has an invalid type. ${ strings . join ( ' ' ) } ` . trim ( ) ;
261
+ this . updateErrorCount ( ) ;
262
+ let warnObj = options . warned [ key ] ;
263
+ if ( ! warnObj ) {
264
+ warnObj = new Warning ( msg , value , expect , loc , name ) ;
265
+ this . warnedTable ?. append ( warnObj . tr ) ;
266
+ options . warned [ key ] = warnObj ;
267
+ }
268
+ warnObj . event = event ;
269
+ warnObj . hits ++ ;
270
+ warnObj . warn ( msg , { expect, value, valueToString} , ...extras ) ;
271
+ // The value may change and we only show the latest wrong value
272
+ warnObj . value = value ;
273
+ // Message may change aswell, especially after loading state.
274
+ warnObj . msg = msg ;
275
+ }
276
+ /**
277
+ * @param {MessageEventRTI } event - The event from Worker, IFrame or own window.
278
+ */
279
+ deleteBreakpoint ( event ) {
280
+ const { key} = event . data ;
281
+ const warnObj = options . warned [ key ] ;
282
+ if ( ! warnObj ) {
283
+ console . warn ( "warnObj doesn't exist" , { key} ) ;
284
+ return ;
285
+ }
286
+ warnObj . dbg = false ;
287
+ }
288
+ /**
289
+ * @param {MessageEventRTI } event - The event from Worker, IFrame or own window.
290
+ */
291
+ addBreakpoint ( event ) {
292
+ console . warn ( 'TypePanel#addBreakpoint> Not adding breakpoints for UI via messages, event' , event ) ;
293
+ }
294
+ /**
295
+ * @param {MessageEventRTI } event - The event from Worker, IFrame or own window.
296
+ */
297
+ handleEvent ( event ) {
298
+ const { action} = event . data ;
299
+ this [ action ] ( event ) ;
300
+ // Could be anywhere we know that a new worker is sending RTI messages.
301
+ this . sendEnabledDisabledStateToWorker ( ) ;
302
+ }
303
+ }
304
+ /** @type {TypePanel | undefined } */
305
+ let typePanel ;
306
+ // @todo create UI explicitly programmatically inside e.g. src/index.rti.js of the projects using it.
307
+ if ( typeof importScripts === 'undefined' ) {
308
+ typePanel = new TypePanel ( ) ;
193
309
}
194
- const typePanel = new TypePanel ( ) ;
195
310
export { niceDiv , TypePanel , typePanel } ;
0 commit comments