mockaton 12.6.1 → 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/package.json +1 -1
- package/src/client/ApiCommander.js +8 -31
- package/src/client/ApiConstants.js +0 -2
- package/src/client/IndexHtml.js +4 -6
- package/src/client/app-header.js +161 -0
- package/src/client/app-icons.js +29 -0
- package/src/client/{payload-viewer.js → app-payload-viewer.js} +12 -15
- package/src/client/app-store.js +11 -15
- package/src/client/app.css +11 -3
- package/src/client/app.js +114 -252
- package/src/client/dom-utils.js +1 -3
- package/src/client/{css-modules.test.js → dom-utils.test.js} +7 -2
- package/src/client/watcherDev.js +26 -17
- package/src/server/Api.js +6 -6
- package/src/server/Mockaton.test.js +45 -34
- package/src/server/Watcher.js +25 -21
- package/src/server/WatcherDevClient.js +23 -14
- package/src/server/cli.js +1 -1
- package/src/server/cli.test.js +18 -8
- package/src/server/utils/mime.js +2 -5
- package/src/server/utils/mime.test.js +1 -8
- package/src/server/utils/validate.test.js +16 -11
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
48
|
+
r('div', { className: CSS.SubToolbar },
|
|
44
49
|
GroupByMethod(),
|
|
45
50
|
BulkSelector()),
|
|
46
|
-
r('div',
|
|
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
|
|
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',
|
|
62
|
+
r('label', { className: CSS.GroupByMethod },
|
|
178
63
|
r('input', {
|
|
179
|
-
ref,
|
|
180
64
|
type: 'checkbox',
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
onChange() { store.setCollectProxied(this.checked) }
|
|
65
|
+
checked: store.groupByMethod,
|
|
66
|
+
onChange: store.toggleGroupByMethod
|
|
184
67
|
}),
|
|
185
|
-
r('span',
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
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()))
|
|
68
|
+
r('span', { className: CSS.checkboxBody }, t`Group by Method`)))
|
|
206
69
|
}
|
|
207
70
|
|
|
208
71
|
|
|
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',
|
|
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
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
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
95
|
}
|
|
249
96
|
|
|
250
97
|
|
|
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',
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
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',
|
|
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',
|
|
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
|
-
|
|
330
|
+
className: classNames(CSS.DelayToggler, optClassName),
|
|
476
331
|
title: t`Delay`,
|
|
477
332
|
body: TimerIcon()
|
|
478
333
|
})
|
|
@@ -481,13 +336,38 @@ function DelayToggler({ checked, commit, optClassName }) {
|
|
|
481
336
|
function ClickDragToggler({ checked, commit, className, title, body }) {
|
|
482
337
|
function onPointerEnter(event) {
|
|
483
338
|
if (event.buttons === 1)
|
|
484
|
-
onPointerDown.call(this)
|
|
339
|
+
onPointerDown.call(this, event)
|
|
485
340
|
}
|
|
486
|
-
|
|
341
|
+
|
|
342
|
+
function onPointerDown(event) {
|
|
343
|
+
if (event.altKey) {
|
|
344
|
+
onExclusiveClick.call(this)
|
|
345
|
+
return
|
|
346
|
+
}
|
|
487
347
|
this.checked = !this.checked
|
|
488
348
|
this.focus()
|
|
489
349
|
commit(this.checked)
|
|
490
350
|
}
|
|
351
|
+
|
|
352
|
+
function onExclusiveClick() {
|
|
353
|
+
const selector = selectorForColumnOf(this)
|
|
354
|
+
if (!selector)
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
// Uncheck all other in the column.
|
|
358
|
+
for (const elem of leftSideRef.elem.querySelectorAll(selector))
|
|
359
|
+
if (elem !== this && elem.checked && !elem.disabled) {
|
|
360
|
+
elem.checked = false
|
|
361
|
+
elem.dispatchEvent(new Event('change'))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (!this.checked) {
|
|
365
|
+
this.checked = true
|
|
366
|
+
this.dispatchEvent(new Event('change'))
|
|
367
|
+
}
|
|
368
|
+
this.focus()
|
|
369
|
+
}
|
|
370
|
+
|
|
491
371
|
function onClick(event) {
|
|
492
372
|
if (event.pointerType === 'mouse')
|
|
493
373
|
event.preventDefault()
|
|
@@ -496,7 +376,7 @@ function ClickDragToggler({ checked, commit, className, title, body }) {
|
|
|
496
376
|
commit(this.checked)
|
|
497
377
|
}
|
|
498
378
|
return (
|
|
499
|
-
r('label', {
|
|
379
|
+
r('label', { className: classNames(CSS.Toggler, className), title },
|
|
500
380
|
r('input', {
|
|
501
381
|
type: 'checkbox',
|
|
502
382
|
checked,
|
|
@@ -505,7 +385,7 @@ function ClickDragToggler({ checked, commit, className, title, body }) {
|
|
|
505
385
|
onClick,
|
|
506
386
|
onChange
|
|
507
387
|
}),
|
|
508
|
-
r('span',
|
|
388
|
+
r('span', { className: CSS.checkboxBody }, body)))
|
|
509
389
|
}
|
|
510
390
|
|
|
511
391
|
function Resizer(ref) {
|
|
@@ -596,89 +476,72 @@ ErrorToast.close = () => {
|
|
|
596
476
|
}
|
|
597
477
|
|
|
598
478
|
|
|
599
|
-
/** # Graphics */
|
|
600
|
-
|
|
601
|
-
function Logo() {
|
|
602
|
-
return (
|
|
603
|
-
s('svg', { viewBox: '0 0 556 100' },
|
|
604
|
-
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' }),
|
|
605
|
-
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' }),
|
|
606
|
-
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' })))
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
function TimerIcon() {
|
|
610
|
-
return (
|
|
611
|
-
s('svg', { viewBox: '0 0 24 24' },
|
|
612
|
-
s('path', { d: 'm11 5.6 0.14 7.2 6 3.7' })))
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
function CloudIcon() {
|
|
616
|
-
return (
|
|
617
|
-
s('svg', { viewBox: '0 0 24 24' },
|
|
618
|
-
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' }),
|
|
619
|
-
s('path', { d: 'm6.1 9.1c2.8 0 5 2.3 5 5' })))
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
function HelpIcon() {
|
|
623
|
-
return (
|
|
624
|
-
s('svg', { viewBox: '0 0 24 24' },
|
|
625
|
-
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' })))
|
|
626
|
-
}
|
|
627
|
-
|
|
628
479
|
|
|
629
|
-
/**
|
|
630
|
-
|
|
631
|
-
* The version increments when a mock file is added, removed, or renamed.
|
|
632
|
-
*/
|
|
633
|
-
function initRealTimeUpdates() {
|
|
480
|
+
/** The version increments when a mock file is added, removed, or renamed. */
|
|
481
|
+
function onRealTimeUpdate(onUpdate) {
|
|
634
482
|
let oldVersion = -1
|
|
635
|
-
let
|
|
483
|
+
let es = null
|
|
484
|
+
let timer = null
|
|
636
485
|
|
|
637
|
-
|
|
486
|
+
connect()
|
|
638
487
|
document.addEventListener('visibilitychange', () => {
|
|
639
|
-
if (document.hidden)
|
|
640
|
-
|
|
641
|
-
controller = new AbortController()
|
|
642
|
-
}
|
|
488
|
+
if (document.hidden)
|
|
489
|
+
teardown()
|
|
643
490
|
else
|
|
644
|
-
|
|
491
|
+
connect()
|
|
645
492
|
})
|
|
493
|
+
window.addEventListener('beforeunload', teardown)
|
|
646
494
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const response = await store.getSyncVersion(oldVersion, controller.signal)
|
|
650
|
-
if (!response.ok)
|
|
651
|
-
throw response.status
|
|
495
|
+
function connect() {
|
|
496
|
+
if (es) return
|
|
652
497
|
|
|
498
|
+
clearTimeout(timer)
|
|
499
|
+
es = new EventSource(API.syncVersion)
|
|
500
|
+
|
|
501
|
+
es.onmessage = function (event) {
|
|
653
502
|
if (ErrorToast.isOffline)
|
|
654
503
|
ErrorToast.close()
|
|
655
504
|
|
|
656
|
-
const version =
|
|
657
|
-
if (oldVersion !== version) {
|
|
505
|
+
const version = Number(event.data)
|
|
506
|
+
if (oldVersion !== version) {
|
|
658
507
|
oldVersion = version
|
|
659
|
-
|
|
508
|
+
onUpdate()
|
|
660
509
|
}
|
|
661
|
-
longPoll()
|
|
662
510
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
511
|
+
|
|
512
|
+
es.onerror = function () {
|
|
513
|
+
teardown()
|
|
514
|
+
timer = setTimeout(connect, 3000)
|
|
666
515
|
}
|
|
667
516
|
}
|
|
517
|
+
|
|
518
|
+
function teardown() {
|
|
519
|
+
clearTimeout(timer)
|
|
520
|
+
es?.close()
|
|
521
|
+
es = null
|
|
522
|
+
}
|
|
668
523
|
}
|
|
669
524
|
|
|
670
525
|
|
|
671
|
-
|
|
672
|
-
|
|
526
|
+
|
|
527
|
+
function selectorForColumnOf(elem) {
|
|
528
|
+
return columnSelectors().find(s => elem?.matches(s))
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function columnSelectors() {
|
|
532
|
+
return [
|
|
673
533
|
`.${CSS.TableRow} .${CSS.ProxyToggler} input`,
|
|
674
534
|
`.${CSS.TableRow} .${CSS.DelayToggler} input`,
|
|
675
535
|
`.${CSS.TableRow} .${CSS.StatusCodeToggler} input`,
|
|
676
536
|
`.${CSS.TableRow} .${CSS.PreviewLink}`,
|
|
677
537
|
// No .MockSelector because down/up arrows have native behavior on them
|
|
678
538
|
]
|
|
539
|
+
}
|
|
679
540
|
|
|
541
|
+
|
|
542
|
+
function initKeyboardNavigation() {
|
|
680
543
|
const rowSelectors = [
|
|
681
|
-
...columnSelectors,
|
|
544
|
+
...columnSelectors(),
|
|
682
545
|
`.${CSS.TableRow} .${CSS.MockSelector}:enabled`,
|
|
683
546
|
]
|
|
684
547
|
|
|
@@ -687,7 +550,7 @@ function initKeyboardNavigation() {
|
|
|
687
550
|
case 'ArrowDown':
|
|
688
551
|
case 'ArrowUp': {
|
|
689
552
|
const pivot = document.activeElement
|
|
690
|
-
const sel =
|
|
553
|
+
const sel = selectorForColumnOf(pivot)
|
|
691
554
|
if (sel) {
|
|
692
555
|
const offset = key === 'ArrowDown' ? +1 : -1
|
|
693
556
|
const siblings = leftSideRef.elem.querySelectorAll(sel)
|
|
@@ -714,4 +577,3 @@ function initKeyboardNavigation() {
|
|
|
714
577
|
return arr[(arr.indexOf(pivot) + step + arr.length) % arr.length]
|
|
715
578
|
}
|
|
716
579
|
}
|
|
717
|
-
|
package/src/client/dom-utils.js
CHANGED
|
@@ -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 = [
|
package/src/client/watcherDev.js
CHANGED
|
@@ -1,29 +1,38 @@
|
|
|
1
1
|
import { API } from './ApiConstants.js'
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
9
|
+
sseClientHotReload,
|
|
10
10
|
DASHBOARD_ASSETS,
|
|
11
11
|
CLIENT_DIR
|
|
12
12
|
} from './WatcherDevClient.js'
|
|
13
|
-
import {
|
|
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,
|
|
32
|
+
[API.syncVersion, sseClientSyncVersion],
|
|
33
33
|
|
|
34
|
-
[API.watchHotReload,
|
|
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,
|
|
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
|
|
95
|
+
function reset(_, response) {
|
|
96
96
|
mockBrokersCollection.init()
|
|
97
97
|
staticCollection.init()
|
|
98
98
|
response.ok()
|