mockaton 12.7.0 → 12.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/client/app.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import {
2
2
  createElement as r,
3
- createSvgElement as s,
4
3
  t, classNames, restoreFocus, Fragment, defineClassNames
5
4
  } from './dom-utils.js'
5
+
6
6
  import { store } from './app-store.js'
7
- import { PayloadViewer, previewMock } from './payload-viewer.js'
7
+ import { API } from './ApiConstants.js'
8
+ import { Header } from './app-header.js'
9
+ import { TimerIcon, CloudIcon } from './app-icons.js'
10
+ import { PayloadViewer, previewMock } from './app-payload-viewer.js'
8
11
 
9
12
  import CSS from './app.css' with { type: 'css' }
10
13
  document.adoptedStyleSheets.push(CSS)
@@ -15,14 +18,12 @@ store.onError = onError
15
18
  store.render = render
16
19
  store.renderRow = renderRow
17
20
 
18
- initRealTimeUpdates()
21
+ onRealTimeUpdate(store.fetchState)
19
22
  initKeyboardNavigation()
20
23
 
21
24
  let mounted = false
22
25
  function render() {
23
- restoreFocus(() => {
24
- document.body.replaceChildren(...App())
25
- })
26
+ restoreFocus(() => document.body.replaceChildren(App()))
26
27
  if (store.hasChosenLink)
27
28
  previewMock()
28
29
  mounted = true
@@ -32,183 +33,42 @@ function render() {
32
33
  const leftSideRef = {}
33
34
 
34
35
  function App() {
35
- return [
36
- Header(),
36
+ return Fragment(Header(), Main())
37
+ }
38
+
39
+
40
+ function Main() {
41
+ return (
37
42
  r('main', null,
38
43
  r('div', {
39
44
  ref: leftSideRef,
40
45
  style: { width: leftSideRef.width },
41
46
  className: CSS.leftSide
42
47
  },
43
- r('div', classNames(CSS.SubToolbar),
48
+ r('div', { className: CSS.SubToolbar },
44
49
  GroupByMethod(),
45
50
  BulkSelector()),
46
- r('div', classNames(CSS.Table),
51
+ r('div', { className: CSS.Table },
47
52
  MockList(),
48
53
  StaticFilesList())),
49
54
  r('div', { className: CSS.rightSide },
50
55
  Resizer(leftSideRef),
51
- PayloadViewer()))
52
- ]
53
- }
54
-
55
- function Header() {
56
- return (
57
- r('header', null,
58
- r('a', {
59
- className: CSS.Logo,
60
- href: 'https://mockaton.com',
61
- alt: t`Documentation`
62
- },
63
- Logo()),
64
- r('div', null,
65
- r('div', classNames(CSS.GlobalDelayWrap),
66
- GlobalDelayField(),
67
- GlobalDelayJitterField()),
68
- CookieSelector(),
69
- store.showProxyField && ProxyFallbackField(),
70
- ResetButton(),
71
- HelpLink()
72
- )))
73
- }
74
-
75
-
76
- function GlobalDelayField() {
77
- function onChange() {
78
- store.setGlobalDelay(this.valueAsNumber)
79
- }
80
- function onWheel(event) {
81
- if (event.deltaY > 0)
82
- this.stepUp()
83
- else
84
- this.stepDown()
85
- clearTimeout(onWheel.timer)
86
- onWheel.timer = setTimeout(onChange.bind(this), 300)
87
- }
88
- return (
89
- r('label', classNames(CSS.Field, CSS.GlobalDelayField),
90
- r('span', null, t`Delay (ms)`),
91
- r('input', {
92
- type: 'number',
93
- min: 0,
94
- step: 100,
95
- autocomplete: 'none',
96
- value: store.delay,
97
- onChange,
98
- onWheel: [onWheel, { passive: true }]
99
- })))
100
- }
101
-
102
- function GlobalDelayJitterField() {
103
- function onChange() {
104
- this.value = this.valueAsNumber.toFixed(0)
105
- this.value = Math.max(0, this.valueAsNumber)
106
- this.value = Math.min(300, this.valueAsNumber)
107
- store.setGlobalDelayJitter(this.valueAsNumber / 100)
108
- }
109
- function onWheel(event) {
110
- if (event.deltaY > 0)
111
- this.stepUp()
112
- else
113
- this.stepDown()
114
- clearTimeout(onWheel.timer)
115
- onWheel.timer = setTimeout(onChange.bind(this), 300)
116
- }
117
- return (
118
- r('label', classNames(CSS.Field, CSS.GlobalDelayJitterField),
119
- r('span', null, t`Max Jitter %`),
120
- r('input', {
121
- type: 'number',
122
- min: 0,
123
- max: 300,
124
- step: 10,
125
- autocomplete: 'none',
126
- value: (store.delayJitter * 100).toFixed(0),
127
- onChange,
128
- onWheel: [onWheel, { passive: true }]
129
- })))
130
- }
131
-
132
-
133
- function CookieSelector() {
134
- const { cookies } = store
135
- const disabled = cookies.length <= 1
136
- const list = cookies.length ? cookies : [[t`None`, true]]
137
- return (
138
- r('label', classNames(CSS.Field, CSS.CookieSelector),
139
- r('span', null, t`Cookie`),
140
- r('select', {
141
- autocomplete: 'off',
142
- disabled,
143
- title: disabled
144
- ? t`No cookies specified in config.cookies`
145
- : undefined,
146
- onChange() { store.selectCookie(this.value) }
147
- }, list.map(([value, selected]) =>
148
- r('option', { value, selected }, value)))))
56
+ PayloadViewer())))
149
57
  }
150
58
 
151
59
 
152
- function ProxyFallbackField() {
153
- const checkboxRef = {}
154
- function onChange() {
155
- checkboxRef.elem.disabled = !this.validity.valid || !this.value.trim()
156
- if (!this.validity.valid)
157
- this.reportValidity()
158
- else
159
- store.setProxyFallback(this.value.trim())
160
- }
161
- return (
162
- r('div', classNames(CSS.Field, CSS.FallbackBackend),
163
- r('label', null,
164
- r('span', null, t`Fallback`),
165
- r('input', {
166
- type: 'url',
167
- name: 'fallback',
168
- placeholder: t`Type backend address`,
169
- value: store.proxyFallback,
170
- onChange
171
- })),
172
- SaveProxiedCheckbox(checkboxRef)))
173
- }
174
-
175
- function SaveProxiedCheckbox(ref) {
60
+ function GroupByMethod() {
176
61
  return (
177
- r('label', classNames(CSS.SaveProxiedCheckbox),
62
+ r('label', { className: CSS.GroupByMethod },
178
63
  r('input', {
179
- ref,
180
64
  type: 'checkbox',
181
- disabled: !store.canProxy,
182
- checked: store.collectProxied,
183
- onChange() { store.setCollectProxied(this.checked) }
65
+ checked: store.groupByMethod,
66
+ onChange: store.toggleGroupByMethod
184
67
  }),
185
- r('span', classNames(CSS.checkboxBody), t`Save Mocks`)))
68
+ r('span', { className: CSS.checkboxBody }, t`Group by Method`)))
186
69
  }
187
70
 
188
71
 
189
- function ResetButton() {
190
- return (
191
- r('button', {
192
- className: CSS.ResetButton,
193
- onClick: store.reset
194
- }, t`Reset`))
195
- }
196
-
197
-
198
- function HelpLink() {
199
- return (
200
- r('a', {
201
- target: '_blank',
202
- href: 'https://mockaton.com',
203
- title: t`Documentation`,
204
- className: CSS.HelpLink
205
- }, HelpIcon()))
206
- }
207
-
208
-
209
- /** # Left Side */
210
-
211
-
212
72
  function BulkSelector() {
213
73
  const { comments } = store
214
74
  const firstOption = t`Pick Comment…`
@@ -219,7 +79,7 @@ function BulkSelector() {
219
79
  }
220
80
  const disabled = !comments.length
221
81
  return (
222
- r('label', classNames(CSS.BulkSelector),
82
+ r('label', { className: CSS.BulkSelector },
223
83
  r('span', null, t`Bulk Select`),
224
84
  r('select', {
225
85
  autocomplete: 'off',
@@ -232,23 +92,9 @@ function BulkSelector() {
232
92
  r('option', { value: firstOption }, firstOption),
233
93
  r('hr'),
234
94
  comments.map(value => r('option', { value }, value)))))
235
- // TODO For a11y, use `menu` instead of `select`
236
95
  }
237
96
 
238
97
 
239
- function GroupByMethod() {
240
- return (
241
- r('label', classNames(CSS.GroupByMethod),
242
- r('input', {
243
- type: 'checkbox',
244
- checked: store.groupByMethod,
245
- onChange: store.toggleGroupByMethod
246
- }),
247
- r('span', classNames(CSS.checkboxBody), t`Group by Method`)))
248
- }
249
-
250
-
251
- /** # MockList */
252
98
 
253
99
  function MockList() {
254
100
  if (!Object.keys(store.brokersByMethod).length)
@@ -257,7 +103,11 @@ function MockList() {
257
103
  if (store.groupByMethod)
258
104
  return Object.keys(store.brokersByMethod).map(method =>
259
105
  Fragment(
260
- r('div', classNames(CSS.TableHeading, store.canProxy && CSS.canProxy), method),
106
+ r('div', {
107
+ className: classNames(
108
+ CSS.TableHeading,
109
+ store.canProxy && CSS.canProxy)
110
+ }, method),
261
111
  store.brokersAsRowsByMethod(method).map(Row)))
262
112
 
263
113
  return store.brokersAsRowsByMethod('*').map(Row)
@@ -272,7 +122,8 @@ function Row(row, i) {
272
122
  return (
273
123
  r('div', {
274
124
  key: row.key,
275
- ...classNames(CSS.TableRow,
125
+ className: classNames(
126
+ CSS.TableRow,
276
127
  mounted && row.isNew && CSS.animIn)
277
128
  },
278
129
  store.canProxy && ProxyToggler(method, urlMask, row.proxied),
@@ -294,7 +145,7 @@ function Row(row, i) {
294
145
  }
295
146
  }),
296
147
 
297
- !store.groupByMethod && r('span', classNames(CSS.Method), method),
148
+ !store.groupByMethod && r('span', { className: CSS.Method }, method),
298
149
 
299
150
  PreviewLink(method, urlMask, row.urlMaskDittoed, i === 0),
300
151
 
@@ -350,12 +201,14 @@ function PreviewLink(method, urlMask, urlMaskDittoed, autofocus) {
350
201
  const [ditto, tail] = urlMaskDittoed
351
202
  return (
352
203
  r('a', {
353
- ...classNames(CSS.PreviewLink, isChosen && CSS.chosen),
204
+ className: classNames(
205
+ CSS.PreviewLink,
206
+ isChosen && CSS.chosen),
354
207
  href: urlMask,
355
208
  autofocus,
356
209
  onClick
357
210
  }, ditto
358
- ? [r('span', classNames(CSS.dittoDir), ditto), tail]
211
+ ? [r('span', { className: CSS.dittoDir }, ditto), tail]
359
212
  : tail))
360
213
  }
361
214
 
@@ -375,7 +228,7 @@ function MockSelector(row) {
375
228
  },
376
229
  'aria-label': t`Mock Selector`,
377
230
  disabled: row.opts.length < 2,
378
- ...classNames(
231
+ className: classNames(
379
232
  CSS.MockSelector,
380
233
  row.selectedIdx > 0 && CSS.nonDefault,
381
234
  row.selectedFileIs4xx && CSS.status4xx)
@@ -405,10 +258,11 @@ function StaticFilesList() {
405
258
  return !rows.length
406
259
  ? null
407
260
  : Fragment(
408
- r('div',
409
- classNames(CSS.TableHeading,
410
- store.canProxy && CSS.canProxy,
411
- !store.groupByMethod && CSS.nonGroupedByMethod),
261
+ r('div', {
262
+ className: classNames(CSS.TableHeading,
263
+ store.canProxy && CSS.canProxy,
264
+ !store.groupByMethod && CSS.nonGroupedByMethod),
265
+ },
412
266
  store.groupByMethod
413
267
  ? t`Static GET`
414
268
  : t`Static`),
@@ -422,7 +276,8 @@ function StaticRow(row) {
422
276
  return (
423
277
  r('div', {
424
278
  key: row.key,
425
- ...classNames(CSS.TableRow,
279
+ className: classNames(
280
+ CSS.TableRow,
426
281
  mounted && row.isNew && CSS.animIn)
427
282
  },
428
283
 
@@ -445,14 +300,14 @@ function StaticRow(row) {
445
300
  }
446
301
  }),
447
302
 
448
- !groupByMethod && r('span', classNames(CSS.Method), 'GET'),
303
+ !groupByMethod && r('span', { className: CSS.Method }, 'GET'),
449
304
 
450
305
  r('a', {
451
306
  href: row.urlMask,
452
307
  target: '_blank',
453
308
  className: CSS.PreviewLink,
454
309
  }, ditto
455
- ? [r('span', classNames(CSS.dittoDir), ditto), tail]
310
+ ? [r('span', { className: CSS.dittoDir }, ditto), tail]
456
311
  : tail)))
457
312
  }
458
313
 
@@ -472,7 +327,7 @@ function DelayToggler({ checked, commit, optClassName }) {
472
327
  canClickDrag: true,
473
328
  checked,
474
329
  commit,
475
- ...classNames(CSS.DelayToggler, optClassName),
330
+ className: classNames(CSS.DelayToggler, optClassName),
476
331
  title: t`Delay`,
477
332
  body: TimerIcon()
478
333
  })
@@ -521,7 +376,7 @@ function ClickDragToggler({ checked, commit, className, title, body }) {
521
376
  commit(this.checked)
522
377
  }
523
378
  return (
524
- r('label', { ...classNames(CSS.Toggler, className), title },
379
+ r('label', { className: classNames(CSS.Toggler, className), title },
525
380
  r('input', {
526
381
  type: 'checkbox',
527
382
  checked,
@@ -530,7 +385,7 @@ function ClickDragToggler({ checked, commit, className, title, body }) {
530
385
  onClick,
531
386
  onChange
532
387
  }),
533
- r('span', classNames(CSS.checkboxBody), body)))
388
+ r('span', { className: CSS.checkboxBody }, body)))
534
389
  }
535
390
 
536
391
  function Resizer(ref) {
@@ -621,77 +476,54 @@ ErrorToast.close = () => {
621
476
  }
622
477
 
623
478
 
624
- /** # Graphics */
625
479
 
626
- function Logo() {
627
- return (
628
- s('svg', { viewBox: '0 0 556 100' },
629
- s('path', { d: 'm13.75 1.8789c-5.9487 0.19352-10.865 4.5652-11.082 11.686v81.445c-1e-7 2.216 1.784 4 4 4h4.793c2.216 0 4-1.784 4-4v-64.982c0.02794-3.4488 3.0988-3.5551 4.2031-1.1562l16.615 59.059c1.4393 5.3711 5.1083 7.9633 8.7656 7.9473 3.6573 0.01603 7.3263-2.5762 8.7656-7.9473l16.615-59.059c1.1043-2.3989 4.1752-2.2925 4.2031 1.1562v64.982c0 2.216 1.784 4 4 4h4.793c2.216 0 4-1.784 4-4v-81.445c-0.17732-7.0807-5.1334-11.492-11.082-11.686-5.9487-0.19352-12.652 3.8309-15.609 13.619l-15.686 57.334-15.686-57.334c-2.9569-9.7882-9.6607-13.813-15.609-13.619zm239.19 0.074219c-2.216 0-4 1.784-4 4v89.057c0 2.216 1.784 4 4 4h4.793c2.216 0 3.9868-1.784 4-4l0.10644-17.94c0.0734-0.07237 12.175-13.75 12.175-13.75 5.6772 11.091 11.404 22.158 17.113 33.232 1.0168 1.9689 3.4217 2.7356 5.3906 1.7188l4.2578-2.1992c1.9689-1.0168 2.7356-3.4217 1.7188-5.3906-6.4691-12.585-12.958-25.16-19.442-37.738l17.223-19.771c1.4555-1.671 1.2803-4.189-0.39062-5.6445l-3.6133-3.1465c-0.73105-0.63679-1.6224-0.96212-2.5176-0.98633-1.151-0.03113-2.3063 0.43508-3.125 1.375l-28.896 33.174v-51.99c0-2.216-1.784-4-4-4zm-58.255 23.316c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312l-0.125-7.8457c0-2.216-1.784-4-4-4h-4.6524c-2.216 0-4 1.784-4 4l3e-3 6.7888c3e-3 3.8063-1.5601 9.3694-8.4716 9.3694h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.6937 0 8.3697 5.2207 8.4687 11.828v2.2207c0 2.216 1.784 4 4 4h4.6524c2.216 0 4-1.784 4-4l0.125-5.7363c0-10.699-8.6117-19.312-19.311-19.312zm-72.182 0c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312v-34.535c0-10.699-8.6117-19.312-19.311-19.312zm1.9356 11h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v26.209c0 6.9115-1.5631 12.475-8.4746 12.475h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477z' }),
630
- s('path', { opacity: 0.7, d: 'm331.9 25.27c-10.699 0-19.312 8.6137-19.312 19.312v4.3682c0 2.216 1.784 4 4 4h4.7715c2.216 0 4-1.784 4-4v-0.20414c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v7.0148h-28.059c-10.699 0-19.312 8.6117-19.312 19.311v4.0477c0 10.699 8.6137 19.313 19.312 19.312h17.812c2.216-1e-6 4-1.784 4-4v-4.7715c0-2.216-1.784-4-4-4h-13.648c-6.9115-2e-5 -12.477-1.5651-12.477-8.5649 0-6.9998 5.5651-8.5629 12.477-8.5629h23.895v25.897c0 2.216 1.784 4 4 4h4.7715c2.216-1e-6 4-1.784 4-4v-49.848c0-10.699-8.6117-19.312-19.311-19.312z' }),
631
- s('path', { d: 'm392.75 1.373c-2.216 0-4 1.784-4 4v18.043h-5.3086c-2.216 0-4 1.784-4 4v4.793c0 2.216 1.784 4 4 4h5.3086v51.398c0 6.1465 3.7064 10.823 9.232 10.823h16.531c2.216 0 4-1.784 4-4v-4.793c0-2.216-1.784-4-4-4h-12.97v-49.428h9.8711c2.216 0 4-1.784 4-4v-4.793c0-2.216-1.784-4-4-4h-9.8711v-18.043c0-2.216-1.784-4-4-4zm122.96 23.896c-10.699 0-19.312 8.6137-19.312 19.312v49.812c0 2.216 1.784 4 4 4h4.7715c2.216 0 4-1.784 4-4v-45.648c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v45.684c0 2.216 1.784 4 4 4h4.7715c2.216-1e-6 4-1.784 4-4v-49.848c0-10.699-8.6117-19.312-19.311-19.312zm-69.999 0c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312v-34.535c0-10.699-8.6117-19.312-19.311-19.312zm1.9356 11h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v26.209c0 6.9115-1.5631 12.475-8.4746 12.475h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477z' })))
632
- }
633
-
634
- function TimerIcon() {
635
- return (
636
- s('svg', { viewBox: '0 0 24 24' },
637
- s('path', { d: 'm11 5.6 0.14 7.2 6 3.7' })))
638
- }
639
-
640
- function CloudIcon() {
641
- return (
642
- s('svg', { viewBox: '0 0 24 24' },
643
- s('path', { d: 'm6.1 8.9c0.98-2.3 3.3-3.9 6-3.9 3.3-2e-7 6 2.5 6.4 5.7 0.018 0.15 0.024 0.18 0.026 0.23 0.0016 0.037 8.2e-4 0.084 0.098 0.14 0.097 0.054 0.29 0.05 0.48 0.05 2.2 0 4 1.8 4 4s-1.8 4-4 4c-4-0.038-9-0.038-13-0.018-2.8 0-5-2.2-5-5-2.2e-7 -2.8 2.2-5 5-5 2.8 2e-7 5 2.2 5 5' }),
644
- s('path', { d: 'm6.1 9.1c2.8 0 5 2.3 5 5' })))
645
- }
646
-
647
- function HelpIcon() {
648
- return (
649
- s('svg', { viewBox: '0 0 24 24' },
650
- s('path', { d: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 17h-2v-2h2zm2.07-7.75-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25' })))
651
- }
652
-
653
-
654
- /**
655
- * # Long polls UI sync version
656
- * The version increments when a mock file is added, removed, or renamed.
657
- */
658
- function initRealTimeUpdates() {
480
+ /** The version increments when a mock file is added, removed, or renamed. */
481
+ function onRealTimeUpdate(onUpdate) {
659
482
  let oldVersion = -1
660
- let controller = new AbortController()
483
+ let es = null
484
+ let timer = null
661
485
 
662
- longPoll()
486
+ connect()
663
487
  document.addEventListener('visibilitychange', () => {
664
- if (document.hidden) {
665
- controller.abort('_hidden_tab_')
666
- controller = new AbortController()
667
- }
488
+ if (document.hidden)
489
+ teardown()
668
490
  else
669
- longPoll()
491
+ connect()
670
492
  })
493
+ window.addEventListener('beforeunload', teardown)
494
+
495
+ function connect() {
496
+ if (es) return
671
497
 
672
- async function longPoll() {
673
- try {
674
- const response = await store.getSyncVersion(oldVersion, controller.signal)
675
- if (!response.ok)
676
- throw response.status
498
+ clearTimeout(timer)
499
+ es = new EventSource(API.syncVersion)
677
500
 
501
+ es.onmessage = function (event) {
678
502
  if (ErrorToast.isOffline)
679
503
  ErrorToast.close()
680
504
 
681
- const version = await response.json()
682
- if (oldVersion !== version) { // because it could be < or >
505
+ const version = Number(event.data)
506
+ if (oldVersion !== version) {
683
507
  oldVersion = version
684
- store.fetchState()
508
+ onUpdate()
685
509
  }
686
- longPoll()
687
510
  }
688
- catch (error) {
689
- if (error !== '_hidden_tab_')
690
- setTimeout(longPoll, 3000)
511
+
512
+ es.onerror = function () {
513
+ teardown()
514
+ timer = setTimeout(connect, 3000)
691
515
  }
692
516
  }
517
+
518
+ function teardown() {
519
+ clearTimeout(timer)
520
+ es?.close()
521
+ es = null
522
+ }
693
523
  }
694
524
 
525
+
526
+
695
527
  function selectorForColumnOf(elem) {
696
528
  return columnSelectors().find(s => elem?.matches(s))
697
529
  }
@@ -745,4 +577,3 @@ function initKeyboardNavigation() {
745
577
  return arr[(arr.indexOf(pivot) + step + arr.length) % arr.length]
746
578
  }
747
579
  }
748
-
@@ -3,9 +3,7 @@ export function t(translation) {
3
3
  }
4
4
 
5
5
  export function classNames(...args) {
6
- return {
7
- className: args.filter(Boolean).join(' ')
8
- }
6
+ return args.filter(Boolean).join(' ')
9
7
  }
10
8
 
11
9
  export function createElement(tag, props, ...children) {
@@ -1,6 +1,11 @@
1
1
  import { test } from 'node:test'
2
- import { deepEqual } from 'node:assert/strict'
3
- import { extractClassNames } from './dom-utils.js'
2
+ import { deepEqual, equal } from 'node:assert/strict'
3
+ import { extractClassNames, classNames } from './dom-utils.js'
4
+
5
+
6
+ test('classNames', () => {
7
+ equal(classNames('a', false && 'b'), 'a')
8
+ })
4
9
 
5
10
 
6
11
  const cssRules = [
@@ -1,29 +1,38 @@
1
1
  import { API } from './ApiConstants.js'
2
2
 
3
3
 
4
- longPollDevChanges()
5
- async function longPollDevChanges() {
6
- try {
7
- const response = await fetch(API.watchHotReload)
8
- if (!response.ok)
9
- throw response.statusText
10
-
11
- const file = await response.json() || ''
12
- if (file.endsWith('.css')) {
13
- await hotReloadCSS(file)
14
- longPollDevChanges()
15
- }
4
+ let es = null
5
+ let timer = null
6
+
7
+ window.addEventListener('beforeunload', teardown)
8
+ connect()
9
+ function connect() {
10
+ if (es) return
11
+
12
+ clearTimeout(timer)
13
+ es = new EventSource(API.watchHotReload)
14
+
15
+ es.onmessage = function (event) {
16
+ const file = event.data
17
+ if (file.endsWith('.css'))
18
+ hotReloadCSS(file)
16
19
  else if (file)
17
20
  location.reload()
18
- else // server timeout
19
- longPollDevChanges()
20
21
  }
21
- catch (error) {
22
- console.error('hot reload', error?.message || error)
23
- setTimeout(longPollDevChanges, 3000)
22
+
23
+ es.onerror = function () {
24
+ console.error('hot reload')
25
+ teardown()
26
+ timer = setTimeout(connect, 3000)
24
27
  }
25
28
  }
26
29
 
30
+ function teardown() {
31
+ clearTimeout(timer)
32
+ es?.close()
33
+ es = null
34
+ }
35
+
27
36
  async function hotReloadCSS(file) {
28
37
  const mod = await import(`./${file}?${Date.now()}`, { with: { type: 'css' } })
29
38
  document.adoptedStyleSheets = [mod.default]
package/src/server/Api.js CHANGED
@@ -6,11 +6,11 @@
6
6
  import { join } from 'node:path'
7
7
 
8
8
  import {
9
- longPollDevClientHotReload,
9
+ sseClientHotReload,
10
10
  DASHBOARD_ASSETS,
11
11
  CLIENT_DIR
12
12
  } from './WatcherDevClient.js'
13
- import { longPollClientSyncVersion, startWatchers, stopWatchers } from './Watcher.js'
13
+ import { startWatchers, stopWatchers, sseClientSyncVersion } from './Watcher.js'
14
14
 
15
15
  import pkgJSON from '../../package.json' with { type: 'json' }
16
16
 
@@ -29,16 +29,16 @@ export const apiGetReqs = new Map([
29
29
  ...DASHBOARD_ASSETS.map(f => [API.dashboard + '/' + f, serveStatic(f)]),
30
30
 
31
31
  [API.state, getState],
32
- [API.syncVersion, longPollClientSyncVersion],
32
+ [API.syncVersion, sseClientSyncVersion],
33
33
 
34
- [API.watchHotReload, longPollDevClientHotReload],
34
+ [API.watchHotReload, sseClientHotReload],
35
35
  [API.throws, () => { throw new Error('Test500') }]
36
36
  ])
37
37
 
38
38
 
39
39
  export const apiPatchReqs = new Map([
40
40
  [API.cors, setCorsAllowed],
41
- [API.reset, reinitialize],
41
+ [API.reset, reset],
42
42
  [API.cookies, selectCookie],
43
43
  [API.globalDelay, setGlobalDelay],
44
44
  [API.globalDelayJitter, setGlobalDelayJitter],
@@ -92,7 +92,7 @@ function getState(_, response) {
92
92
 
93
93
  /** # PATCH */
94
94
 
95
- function reinitialize(_, response) {
95
+ function reset(_, response) {
96
96
  mockBrokersCollection.init()
97
97
  staticCollection.init()
98
98
  response.ok()