mockaton 12.2.2 → 12.3.0
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/README.md +3 -14
- package/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/client/ApiCommander.js +1 -1
- package/src/client/ApiConstants.js +3 -2
- package/src/client/Filename.js +45 -1
- package/src/client/app-store.js +2 -4
- package/src/client/app.js +200 -184
- package/src/client/dom-utils.js +9 -14
- package/src/client/indexHtml.js +19 -20
- package/src/client/styles.css +33 -85
- package/src/server/Api.js +7 -6
- package/src/server/MockBroker.js +2 -2
- package/src/server/Mockaton.js +2 -1
- package/src/server/Mockaton.test.js +3 -2
- package/src/server/ProxyRelay.js +2 -1
- package/src/server/StaticDispatcher.js +1 -1
- package/src/server/Watcher.js +1 -1
- package/src/server/WatcherDevClient.js +3 -2
- package/src/server/mockBrokersCollection.js +1 -1
- package/src/server/utils/HttpIncomingMessage.js +2 -2
- package/src/server/utils/HttpServerResponse.js +1 -1
- package/src/server/utils/mime.js +1 -1
- package/src/server/ApiConstants.js +0 -32
- package/src/server/Filename.js +0 -65
package/src/client/app.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createElement as r,
|
|
3
3
|
createSvgElement as s,
|
|
4
|
-
|
|
4
|
+
classNames, restoreFocus, Fragment, adoptCSS
|
|
5
5
|
} from './dom-utils.js'
|
|
6
6
|
|
|
7
7
|
import { store } from './app-store.js'
|
|
@@ -11,29 +11,25 @@ import { HEADER_502 } from './ApiConstants.js'
|
|
|
11
11
|
import CSS from './styles.css' with { type: 'css' }
|
|
12
12
|
adoptCSS(CSS)
|
|
13
13
|
|
|
14
|
-
const FocusGroup = {
|
|
15
|
-
ProxyToggler: 0,
|
|
16
|
-
DelayToggler: 1,
|
|
17
|
-
StatusToggler: 2,
|
|
18
|
-
PreviewLink: 3
|
|
19
|
-
}
|
|
20
14
|
|
|
21
15
|
const t = translation => translation[0]
|
|
22
16
|
|
|
23
17
|
store.onError = onError
|
|
24
18
|
store.render = render
|
|
25
19
|
store.renderRow = renderRow
|
|
26
|
-
|
|
20
|
+
|
|
27
21
|
initRealTimeUpdates()
|
|
28
22
|
initKeyboardNavigation()
|
|
29
23
|
|
|
24
|
+
let mounted = false
|
|
30
25
|
function render() {
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
restoreFocus(() => {
|
|
27
|
+
document.body.replaceChildren(...App())
|
|
28
|
+
})
|
|
33
29
|
if (store.hasChosenLink)
|
|
34
30
|
previewMock()
|
|
31
|
+
mounted = true
|
|
35
32
|
}
|
|
36
|
-
render.count = 0
|
|
37
33
|
|
|
38
34
|
|
|
39
35
|
const leftSideRef = {}
|
|
@@ -47,10 +43,10 @@ function App() {
|
|
|
47
43
|
style: { width: leftSideRef.width },
|
|
48
44
|
className: CSS.leftSide
|
|
49
45
|
},
|
|
50
|
-
r('div',
|
|
46
|
+
r('div', classNames(CSS.SubToolbar),
|
|
51
47
|
GroupByMethod(),
|
|
52
48
|
BulkSelector()),
|
|
53
|
-
r('div',
|
|
49
|
+
r('div', classNames(CSS.Table),
|
|
54
50
|
MockList(),
|
|
55
51
|
StaticFilesList())),
|
|
56
52
|
r('div', { className: CSS.rightSide },
|
|
@@ -64,11 +60,12 @@ function Header() {
|
|
|
64
60
|
r('header', null,
|
|
65
61
|
r('a', {
|
|
66
62
|
className: CSS.Logo,
|
|
67
|
-
href: 'https://mockaton.com'
|
|
63
|
+
href: 'https://mockaton.com',
|
|
64
|
+
alt: t`Documentation`
|
|
68
65
|
},
|
|
69
66
|
Logo()),
|
|
70
67
|
r('div', null,
|
|
71
|
-
r('div',
|
|
68
|
+
r('div', classNames(CSS.GlobalDelayWrap),
|
|
72
69
|
GlobalDelayField(),
|
|
73
70
|
GlobalDelayJitterField()),
|
|
74
71
|
CookieSelector(),
|
|
@@ -92,10 +89,9 @@ function GlobalDelayField() {
|
|
|
92
89
|
onWheel.timer = setTimeout(onChange.bind(this), 300)
|
|
93
90
|
}
|
|
94
91
|
return (
|
|
95
|
-
r('label',
|
|
92
|
+
r('label', classNames(CSS.Field, CSS.GlobalDelayField),
|
|
96
93
|
r('span', null, t`Delay (ms)`),
|
|
97
94
|
r('input', {
|
|
98
|
-
name: 'delay',
|
|
99
95
|
type: 'number',
|
|
100
96
|
min: 0,
|
|
101
97
|
step: 100,
|
|
@@ -122,10 +118,9 @@ function GlobalDelayJitterField() {
|
|
|
122
118
|
onWheel.timer = setTimeout(onChange.bind(this), 300)
|
|
123
119
|
}
|
|
124
120
|
return (
|
|
125
|
-
r('label',
|
|
121
|
+
r('label', classNames(CSS.Field, CSS.GlobalDelayJitterField),
|
|
126
122
|
r('span', null, t`Max Jitter %`),
|
|
127
123
|
r('input', {
|
|
128
|
-
name: 'delay-jitter',
|
|
129
124
|
type: 'number',
|
|
130
125
|
min: 0,
|
|
131
126
|
max: 300,
|
|
@@ -143,12 +138,14 @@ function CookieSelector() {
|
|
|
143
138
|
const disabled = cookies.length <= 1
|
|
144
139
|
const list = cookies.length ? cookies : [[t`None`, true]]
|
|
145
140
|
return (
|
|
146
|
-
r('label',
|
|
141
|
+
r('label', classNames(CSS.Field, CSS.CookieSelector),
|
|
147
142
|
r('span', null, t`Cookie`),
|
|
148
143
|
r('select', {
|
|
149
144
|
autocomplete: 'off',
|
|
150
145
|
disabled,
|
|
151
|
-
title: disabled
|
|
146
|
+
title: disabled
|
|
147
|
+
? t`No cookies specified in config.cookies`
|
|
148
|
+
: undefined,
|
|
152
149
|
onChange() { store.selectCookie(this.value) }
|
|
153
150
|
}, list.map(([value, selected]) =>
|
|
154
151
|
r('option', { value, selected }, value)))))
|
|
@@ -165,13 +162,12 @@ function ProxyFallbackField() {
|
|
|
165
162
|
store.setProxyFallback(this.value.trim())
|
|
166
163
|
}
|
|
167
164
|
return (
|
|
168
|
-
r('div',
|
|
165
|
+
r('div', classNames(CSS.Field, CSS.FallbackBackend),
|
|
169
166
|
r('label', null,
|
|
170
167
|
r('span', null, t`Fallback`),
|
|
171
168
|
r('input', {
|
|
172
|
-
name: 'fallback',
|
|
173
169
|
type: 'url',
|
|
174
|
-
|
|
170
|
+
name: 'fallback',
|
|
175
171
|
placeholder: t`Type backend address`,
|
|
176
172
|
value: store.proxyFallback,
|
|
177
173
|
onChange
|
|
@@ -181,16 +177,15 @@ function ProxyFallbackField() {
|
|
|
181
177
|
|
|
182
178
|
function SaveProxiedCheckbox(ref) {
|
|
183
179
|
return (
|
|
184
|
-
r('label',
|
|
180
|
+
r('label', classNames(CSS.SaveProxiedCheckbox),
|
|
185
181
|
r('input', {
|
|
186
|
-
name: 'save-proxied',
|
|
187
182
|
ref,
|
|
188
183
|
type: 'checkbox',
|
|
189
184
|
disabled: !store.canProxy,
|
|
190
185
|
checked: store.collectProxied,
|
|
191
186
|
onChange() { store.setCollectProxied(this.checked) }
|
|
192
187
|
}),
|
|
193
|
-
r('span',
|
|
188
|
+
r('span', classNames(CSS.checkboxBody), t`Save Mocks`)))
|
|
194
189
|
}
|
|
195
190
|
|
|
196
191
|
|
|
@@ -204,13 +199,13 @@ function ResetButton() {
|
|
|
204
199
|
|
|
205
200
|
|
|
206
201
|
function HelpLink() {
|
|
207
|
-
return
|
|
202
|
+
return (
|
|
203
|
+
r('a', {
|
|
208
204
|
target: '_blank',
|
|
209
205
|
href: 'https://mockaton.com',
|
|
210
206
|
title: t`Documentation`,
|
|
211
207
|
className: CSS.HelpLink
|
|
212
|
-
},
|
|
213
|
-
HelpIcon())
|
|
208
|
+
}, HelpIcon()))
|
|
214
209
|
}
|
|
215
210
|
|
|
216
211
|
|
|
@@ -227,12 +222,14 @@ function BulkSelector() {
|
|
|
227
222
|
}
|
|
228
223
|
const disabled = !comments.length
|
|
229
224
|
return (
|
|
230
|
-
r('label',
|
|
225
|
+
r('label', classNames(CSS.BulkSelector),
|
|
231
226
|
r('span', null, t`Bulk Select`),
|
|
232
227
|
r('select', {
|
|
233
228
|
autocomplete: 'off',
|
|
234
229
|
disabled,
|
|
235
|
-
title: disabled
|
|
230
|
+
title: disabled
|
|
231
|
+
? t`No mock files have comments which are anything within parentheses on the filename.`
|
|
232
|
+
: undefined,
|
|
236
233
|
onChange
|
|
237
234
|
},
|
|
238
235
|
r('option', { value: firstOption }, firstOption),
|
|
@@ -244,14 +241,13 @@ function BulkSelector() {
|
|
|
244
241
|
|
|
245
242
|
function GroupByMethod() {
|
|
246
243
|
return (
|
|
247
|
-
r('label',
|
|
244
|
+
r('label', classNames(CSS.GroupByMethod),
|
|
248
245
|
r('input', {
|
|
249
|
-
name: 'group-by-method',
|
|
250
246
|
type: 'checkbox',
|
|
251
247
|
checked: store.groupByMethod,
|
|
252
248
|
onChange: store.toggleGroupByMethod
|
|
253
249
|
}),
|
|
254
|
-
r('span',
|
|
250
|
+
r('span', classNames(CSS.checkboxBody), t`Group by Method`)))
|
|
255
251
|
}
|
|
256
252
|
|
|
257
253
|
|
|
@@ -262,10 +258,10 @@ function MockList() {
|
|
|
262
258
|
return r('div', null, t`No mocks found`)
|
|
263
259
|
|
|
264
260
|
if (store.groupByMethod)
|
|
265
|
-
return Object.keys(store.brokersByMethod).map(method =>
|
|
266
|
-
|
|
267
|
-
method),
|
|
268
|
-
|
|
261
|
+
return Object.keys(store.brokersByMethod).map(method =>
|
|
262
|
+
Fragment(
|
|
263
|
+
r('div', classNames(CSS.TableHeading, store.canProxy && CSS.canProxy), method),
|
|
264
|
+
store.brokersAsRowsByMethod(method).map(Row)))
|
|
269
265
|
|
|
270
266
|
return store.brokersAsRowsByMethod('*').map(Row)
|
|
271
267
|
}
|
|
@@ -279,18 +275,29 @@ function Row(row, i) {
|
|
|
279
275
|
return (
|
|
280
276
|
r('div', {
|
|
281
277
|
key: row.key,
|
|
282
|
-
...
|
|
283
|
-
|
|
278
|
+
...classNames(CSS.TableRow,
|
|
279
|
+
mounted && row.isNew && CSS.animIn)
|
|
284
280
|
},
|
|
285
281
|
store.canProxy && ProxyToggler(method, urlMask, row.proxied),
|
|
286
282
|
|
|
287
|
-
|
|
283
|
+
DelayToggler({
|
|
284
|
+
checked: row.delayed,
|
|
285
|
+
commit(checked) {
|
|
286
|
+
store.setDelayed(method, urlMask, checked)
|
|
287
|
+
},
|
|
288
|
+
}),
|
|
288
289
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
StatusCodeToggler({
|
|
291
|
+
title: t`Internal Server Error`,
|
|
292
|
+
body: t`500`,
|
|
293
|
+
disabled: row.opts.length === 1 && row.status === 500,
|
|
294
|
+
checked: !row.proxied && row.status === 500,
|
|
295
|
+
commit() {
|
|
296
|
+
store.toggle500(method, urlMask)
|
|
297
|
+
}
|
|
298
|
+
}),
|
|
292
299
|
|
|
293
|
-
!store.groupByMethod && r('span',
|
|
300
|
+
!store.groupByMethod && r('span', classNames(CSS.Method), method),
|
|
294
301
|
|
|
295
302
|
PreviewLink(method, urlMask, row.urlMaskDittoed, i === 0),
|
|
296
303
|
|
|
@@ -298,39 +305,58 @@ function Row(row, i) {
|
|
|
298
305
|
}
|
|
299
306
|
|
|
300
307
|
function renderRow(method, urlMask) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
308
|
+
const row = store.brokerAsRow(method, urlMask)
|
|
309
|
+
const tr = leftSideRef.elem.querySelector(`.${CSS.TableRow}[key="${row.key}"]`)
|
|
310
|
+
mergeTableRow(tr, Row(row))
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function mergeTableRow(oldRow, newRow) {
|
|
314
|
+
for (let i = 0; i < newRow.children.length; i++) {
|
|
315
|
+
const oldEl = oldRow.children[i]
|
|
316
|
+
const newEl = newRow.children[i]
|
|
317
|
+
switch (newEl.tagName) {
|
|
318
|
+
case 'LABEL': {
|
|
319
|
+
const oldInput = oldEl.querySelector('[type="checkbox"]')
|
|
320
|
+
const newInput = newEl.querySelector('[type="checkbox"]')
|
|
321
|
+
oldInput.checked = newInput.checked
|
|
322
|
+
oldInput.disabled = newInput.disabled
|
|
323
|
+
break
|
|
324
|
+
}
|
|
325
|
+
case 'A':
|
|
326
|
+
oldEl.className = newEl.className
|
|
327
|
+
break
|
|
328
|
+
case 'SELECT':
|
|
329
|
+
oldEl.disabled = newEl.disabled
|
|
330
|
+
oldEl.replaceChildren(...newEl.cloneNode(true).children)
|
|
331
|
+
break
|
|
332
|
+
}
|
|
314
333
|
}
|
|
315
334
|
}
|
|
316
335
|
|
|
317
336
|
|
|
337
|
+
|
|
318
338
|
function PreviewLink(method, urlMask, urlMaskDittoed, autofocus) {
|
|
319
339
|
function onClick(event) {
|
|
340
|
+
unChooseOld()
|
|
320
341
|
event.preventDefault()
|
|
321
342
|
store.previewLink(method, urlMask)
|
|
343
|
+
previewMock()
|
|
344
|
+
}
|
|
345
|
+
function unChooseOld() {
|
|
346
|
+
return leftSideRef.elem.querySelector(`a.${CSS.chosen}`)
|
|
347
|
+
?.classList.remove(CSS.chosen)
|
|
322
348
|
}
|
|
349
|
+
|
|
323
350
|
const isChosen = store.chosenLink.method === method && store.chosenLink.urlMask === urlMask
|
|
324
351
|
const [ditto, tail] = urlMaskDittoed
|
|
325
352
|
return (
|
|
326
353
|
r('a', {
|
|
327
|
-
...
|
|
354
|
+
...classNames(CSS.PreviewLink, isChosen && CSS.chosen),
|
|
328
355
|
href: urlMask,
|
|
329
356
|
autofocus,
|
|
330
|
-
'data-focus-group': FocusGroup.PreviewLink,
|
|
331
357
|
onClick
|
|
332
358
|
}, ditto
|
|
333
|
-
? [r('span',
|
|
359
|
+
? [r('span', classNames(CSS.dittoDir), ditto), tail]
|
|
334
360
|
: tail))
|
|
335
361
|
}
|
|
336
362
|
|
|
@@ -339,11 +365,18 @@ function PreviewLink(method, urlMask, urlMaskDittoed, autofocus) {
|
|
|
339
365
|
function MockSelector(row) {
|
|
340
366
|
return (
|
|
341
367
|
r('select', {
|
|
342
|
-
onChange() {
|
|
343
|
-
|
|
368
|
+
onChange() {
|
|
369
|
+
store.selectFile(this.value)
|
|
370
|
+
},
|
|
371
|
+
onKeyDown(event) {
|
|
372
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowLeft')
|
|
373
|
+
event.preventDefault()
|
|
374
|
+
// Because in Firefox they change the select.option, and
|
|
375
|
+
// we use those keys for spreadsheet-like navigation.
|
|
376
|
+
},
|
|
344
377
|
'aria-label': t`Mock Selector`,
|
|
345
378
|
disabled: row.opts.length < 2,
|
|
346
|
-
...
|
|
379
|
+
...classNames(
|
|
347
380
|
CSS.MockSelector,
|
|
348
381
|
row.selectedIdx > 0 && CSS.nonDefault,
|
|
349
382
|
row.selectedFileIs4xx && CSS.status4xx)
|
|
@@ -352,45 +385,18 @@ function MockSelector(row) {
|
|
|
352
385
|
}
|
|
353
386
|
|
|
354
387
|
|
|
355
|
-
function
|
|
388
|
+
function ProxyToggler(method, urlMask, checked) {
|
|
356
389
|
return ClickDragToggler({
|
|
390
|
+
className: CSS.ProxyToggler,
|
|
391
|
+
title: t`Proxy Toggler`,
|
|
357
392
|
checked,
|
|
358
|
-
commit(checked) {
|
|
359
|
-
|
|
393
|
+
commit(checked) {
|
|
394
|
+
store.setProxied(method, urlMask, checked)
|
|
395
|
+
},
|
|
396
|
+
body: CloudIcon()
|
|
360
397
|
})
|
|
361
398
|
}
|
|
362
399
|
|
|
363
|
-
function InternalServerErrorToggler(method, urlMask, checked, disabled) {
|
|
364
|
-
return (
|
|
365
|
-
r('label', {
|
|
366
|
-
className: CSS.InternalServerErrorToggler,
|
|
367
|
-
title: t`Internal Server Error`
|
|
368
|
-
},
|
|
369
|
-
r('input', {
|
|
370
|
-
type: 'checkbox',
|
|
371
|
-
disabled,
|
|
372
|
-
checked,
|
|
373
|
-
onChange() { store.toggle500(method, urlMask) },
|
|
374
|
-
'data-focus-group': FocusGroup.StatusToggler
|
|
375
|
-
}),
|
|
376
|
-
r('span', className(CSS.checkboxBody), t`500`)))
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function ProxyToggler(method, urlMask, checked) {
|
|
380
|
-
return (
|
|
381
|
-
r('label', {
|
|
382
|
-
className: CSS.ProxyToggler,
|
|
383
|
-
title: t`Proxy Toggler`
|
|
384
|
-
},
|
|
385
|
-
r('input', {
|
|
386
|
-
type: 'checkbox',
|
|
387
|
-
checked,
|
|
388
|
-
onChange() { store.setProxied(method, urlMask, this.checked) },
|
|
389
|
-
'data-focus-group': FocusGroup.ProxyToggler
|
|
390
|
-
}),
|
|
391
|
-
CloudIcon()))
|
|
392
|
-
}
|
|
393
|
-
|
|
394
400
|
|
|
395
401
|
|
|
396
402
|
/** # StaticFilesList */
|
|
@@ -401,10 +407,12 @@ function StaticFilesList() {
|
|
|
401
407
|
? null
|
|
402
408
|
: Fragment(
|
|
403
409
|
r('div',
|
|
404
|
-
|
|
410
|
+
classNames(CSS.TableHeading,
|
|
405
411
|
store.canProxy && CSS.canProxy,
|
|
406
412
|
!store.groupByMethod && CSS.nonGroupedByMethod),
|
|
407
|
-
store.groupByMethod
|
|
413
|
+
store.groupByMethod
|
|
414
|
+
? t`Static GET`
|
|
415
|
+
: t`Static`),
|
|
408
416
|
rows.map(StaticRow))
|
|
409
417
|
}
|
|
410
418
|
|
|
@@ -415,55 +423,62 @@ function StaticRow(row) {
|
|
|
415
423
|
return (
|
|
416
424
|
r('div', {
|
|
417
425
|
key: row.key,
|
|
418
|
-
...
|
|
419
|
-
|
|
426
|
+
...classNames(CSS.TableRow,
|
|
427
|
+
mounted && row.isNew && CSS.animIn)
|
|
420
428
|
},
|
|
421
|
-
DelayStaticRouteToggler(row.urlMask, row.delayed),
|
|
422
429
|
|
|
423
|
-
|
|
430
|
+
DelayToggler({
|
|
431
|
+
optClassName: store.canProxy && CSS.canProxy,
|
|
432
|
+
checked: row.delayed,
|
|
433
|
+
commit(checked) {
|
|
434
|
+
store.setDelayedStatic(row.urlMask, checked)
|
|
435
|
+
}
|
|
436
|
+
}),
|
|
437
|
+
|
|
438
|
+
StatusCodeToggler({
|
|
439
|
+
title: t`Not Found`,
|
|
440
|
+
body: t`404`,
|
|
441
|
+
checked: row.status === 404,
|
|
442
|
+
commit(checked) {
|
|
443
|
+
store.setStaticRouteStatus(row.urlMask, checked
|
|
444
|
+
? 404
|
|
445
|
+
: 200)
|
|
446
|
+
}
|
|
447
|
+
}),
|
|
424
448
|
|
|
425
|
-
!groupByMethod && r('span',
|
|
449
|
+
!groupByMethod && r('span', classNames(CSS.Method), 'GET'),
|
|
426
450
|
|
|
427
451
|
r('a', {
|
|
428
452
|
href: row.urlMask,
|
|
429
453
|
target: '_blank',
|
|
430
454
|
className: CSS.PreviewLink,
|
|
431
|
-
'data-focus-group': FocusGroup.PreviewLink
|
|
432
455
|
}, ditto
|
|
433
|
-
? [r('span',
|
|
456
|
+
? [r('span', classNames(CSS.dittoDir), ditto), tail]
|
|
434
457
|
: tail)))
|
|
435
458
|
}
|
|
436
459
|
|
|
437
|
-
function
|
|
460
|
+
function StatusCodeToggler({ title, body, commit, checked, disabled }) {
|
|
438
461
|
return ClickDragToggler({
|
|
439
|
-
|
|
462
|
+
title,
|
|
463
|
+
disabled,
|
|
464
|
+
className: CSS.StatusCodeToggler,
|
|
465
|
+
commit,
|
|
440
466
|
checked,
|
|
441
|
-
|
|
442
|
-
commit(checked) {
|
|
443
|
-
store.setDelayedStatic(route, checked)
|
|
444
|
-
}
|
|
467
|
+
body
|
|
445
468
|
})
|
|
446
469
|
}
|
|
447
470
|
|
|
448
|
-
function
|
|
449
|
-
return (
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
checked,
|
|
457
|
-
'data-focus-group': FocusGroup.StatusToggler,
|
|
458
|
-
onChange() {
|
|
459
|
-
store.setStaticRouteStatus(route, this.checked ? 404 : 200)
|
|
460
|
-
}
|
|
461
|
-
}),
|
|
462
|
-
r('span', className(CSS.checkboxBody), t`404`)))
|
|
471
|
+
function DelayToggler({ checked, commit, optClassName }) {
|
|
472
|
+
return ClickDragToggler({
|
|
473
|
+
checked,
|
|
474
|
+
commit,
|
|
475
|
+
...classNames(CSS.DelayToggler, optClassName),
|
|
476
|
+
title: t`Delay`,
|
|
477
|
+
body: TimerIcon()
|
|
478
|
+
})
|
|
463
479
|
}
|
|
464
480
|
|
|
465
|
-
|
|
466
|
-
function ClickDragToggler({ checked, commit, focusGroup, optClassName }) {
|
|
481
|
+
function ClickDragToggler({ checked, commit, className, title, body }) {
|
|
467
482
|
function onPointerEnter(event) {
|
|
468
483
|
if (event.buttons === 1)
|
|
469
484
|
onPointerDown.call(this)
|
|
@@ -481,20 +496,16 @@ function ClickDragToggler({ checked, commit, focusGroup, optClassName }) {
|
|
|
481
496
|
commit(this.checked)
|
|
482
497
|
}
|
|
483
498
|
return (
|
|
484
|
-
r('label', {
|
|
485
|
-
...className(CSS.DelayToggler, optClassName),
|
|
486
|
-
title: t`Delay`
|
|
487
|
-
},
|
|
499
|
+
r('label', { ...classNames(CSS.Toggler, className), title },
|
|
488
500
|
r('input', {
|
|
489
501
|
type: 'checkbox',
|
|
490
|
-
'data-focus-group': focusGroup,
|
|
491
502
|
checked,
|
|
492
503
|
onPointerEnter,
|
|
493
504
|
onPointerDown,
|
|
494
505
|
onClick,
|
|
495
506
|
onChange
|
|
496
507
|
}),
|
|
497
|
-
|
|
508
|
+
r('span', classNames(CSS.checkboxBody), body)))
|
|
498
509
|
}
|
|
499
510
|
|
|
500
511
|
function Resizer(ref) {
|
|
@@ -549,7 +560,7 @@ const payloadViewerCodeRef = {}
|
|
|
549
560
|
|
|
550
561
|
function PayloadViewer() {
|
|
551
562
|
return (
|
|
552
|
-
r('div',
|
|
563
|
+
r('div', classNames(CSS.PayloadViewer),
|
|
553
564
|
RightToolbar(),
|
|
554
565
|
r('pre', null,
|
|
555
566
|
r('code', { ref: payloadViewerCodeRef },
|
|
@@ -557,9 +568,10 @@ function PayloadViewer() {
|
|
|
557
568
|
}
|
|
558
569
|
|
|
559
570
|
function RightToolbar() {
|
|
560
|
-
return
|
|
561
|
-
r('
|
|
562
|
-
|
|
571
|
+
return (
|
|
572
|
+
r('div', classNames(CSS.SubToolbar),
|
|
573
|
+
r('h2', { ref: payloadViewerTitleRef },
|
|
574
|
+
!store.hasChosenLink && t`Preview`)))
|
|
563
575
|
}
|
|
564
576
|
|
|
565
577
|
|
|
@@ -588,8 +600,12 @@ function PayloadViewerTitleWhenProxied(response) {
|
|
|
588
600
|
const SPINNER_DELAY = 80
|
|
589
601
|
function PayloadViewerProgressBar() {
|
|
590
602
|
return (
|
|
591
|
-
r('div',
|
|
592
|
-
r('div', {
|
|
603
|
+
r('div', classNames(CSS.ProgressBar),
|
|
604
|
+
r('div', {
|
|
605
|
+
style: {
|
|
606
|
+
animationDuration: store.delay - SPINNER_DELAY + 'ms'
|
|
607
|
+
}
|
|
608
|
+
})))
|
|
593
609
|
}
|
|
594
610
|
|
|
595
611
|
async function previewMock() {
|
|
@@ -724,7 +740,7 @@ function HelpIcon() {
|
|
|
724
740
|
* The version increments when a mock file is added, removed, or renamed.
|
|
725
741
|
*/
|
|
726
742
|
function initRealTimeUpdates() {
|
|
727
|
-
let oldVersion =
|
|
743
|
+
let oldVersion = -1
|
|
728
744
|
let controller = new AbortController()
|
|
729
745
|
|
|
730
746
|
longPoll()
|
|
@@ -740,21 +756,18 @@ function initRealTimeUpdates() {
|
|
|
740
756
|
async function longPoll() {
|
|
741
757
|
try {
|
|
742
758
|
const response = await store.getSyncVersion(oldVersion, controller.signal)
|
|
743
|
-
if (response.ok)
|
|
744
|
-
if (ErrorToast.isOffline)
|
|
745
|
-
ErrorToast.close()
|
|
746
|
-
|
|
747
|
-
const version = await response.json()
|
|
748
|
-
const shouldSkip = oldVersion === undefined
|
|
749
|
-
if (oldVersion !== version) { // because it could be < or >
|
|
750
|
-
oldVersion = version
|
|
751
|
-
if (!shouldSkip)
|
|
752
|
-
store.fetchState()
|
|
753
|
-
}
|
|
754
|
-
longPoll()
|
|
755
|
-
}
|
|
756
|
-
else
|
|
759
|
+
if (!response.ok)
|
|
757
760
|
throw response.status
|
|
761
|
+
|
|
762
|
+
if (ErrorToast.isOffline)
|
|
763
|
+
ErrorToast.close()
|
|
764
|
+
|
|
765
|
+
const version = await response.json()
|
|
766
|
+
if (oldVersion !== version) { // because it could be < or >
|
|
767
|
+
oldVersion = version
|
|
768
|
+
store.fetchState()
|
|
769
|
+
}
|
|
770
|
+
longPoll()
|
|
758
771
|
}
|
|
759
772
|
catch (error) {
|
|
760
773
|
if (error !== '_hidden_tab_')
|
|
@@ -765,45 +778,48 @@ function initRealTimeUpdates() {
|
|
|
765
778
|
|
|
766
779
|
|
|
767
780
|
function initKeyboardNavigation() {
|
|
768
|
-
|
|
781
|
+
const columnSelectors = [
|
|
782
|
+
`.${CSS.TableRow} .${CSS.ProxyToggler} input`,
|
|
783
|
+
`.${CSS.TableRow} .${CSS.DelayToggler} input`,
|
|
784
|
+
`.${CSS.TableRow} .${CSS.StatusCodeToggler} input`,
|
|
785
|
+
`.${CSS.TableRow} .${CSS.PreviewLink}`,
|
|
786
|
+
// No .MockSelector because down/up arrows have native behavior on them
|
|
787
|
+
]
|
|
769
788
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
789
|
+
const rowSelectors = [
|
|
790
|
+
...columnSelectors,
|
|
791
|
+
`.${CSS.TableRow} .${CSS.MockSelector}:enabled`,
|
|
792
|
+
]
|
|
793
|
+
|
|
794
|
+
addEventListener('keydown', function ({ key }) {
|
|
795
|
+
switch (key) {
|
|
773
796
|
case 'ArrowDown':
|
|
774
797
|
case 'ArrowUp': {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
798
|
+
const pivot = document.activeElement
|
|
799
|
+
const sel = columnSelectors.find(s => pivot?.matches(s))
|
|
800
|
+
if (sel) {
|
|
801
|
+
const offset = key === 'ArrowDown' ? +1 : -1
|
|
802
|
+
const siblings = leftSideRef.elem.querySelectorAll(sel)
|
|
803
|
+
circularAdjacent(offset, siblings, pivot).focus()
|
|
779
804
|
}
|
|
780
805
|
break
|
|
781
806
|
}
|
|
782
807
|
case 'ArrowRight':
|
|
783
808
|
case 'ArrowLeft': {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
809
|
+
const pivot = document.activeElement
|
|
810
|
+
const sel = rowSelectors.find(s => pivot?.matches(s))
|
|
811
|
+
if (sel) {
|
|
812
|
+
const offset = key === 'ArrowRight' ? +1 : -1
|
|
813
|
+
const siblings = pivot.closest(`.${CSS.TableRow}`).querySelectorAll(rowSelectors.join(','))
|
|
814
|
+
circularAdjacent(offset, siblings, pivot).focus()
|
|
787
815
|
}
|
|
788
816
|
break
|
|
789
817
|
}
|
|
790
818
|
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
function rowFocusable(el, step) {
|
|
794
|
-
const row = el.closest(`.${CSS.TableRow}`)
|
|
795
|
-
if (row) {
|
|
796
|
-
const focusables = Array.from(row.querySelectorAll('a, input, select:not(:disabled)'))
|
|
797
|
-
return circularAdjacent(step, focusables, el)
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
function allInFocusGroup(focusGroup) {
|
|
802
|
-
return Array.from(leftSideRef.elem.querySelectorAll(
|
|
803
|
-
`.${CSS.TableRow} [data-focus-group="${focusGroup}"]:is(input, a)`))
|
|
804
|
-
}
|
|
819
|
+
})
|
|
805
820
|
|
|
806
|
-
function circularAdjacent(step
|
|
821
|
+
function circularAdjacent(step, siblings, pivot) {
|
|
822
|
+
const arr = Array.from(siblings)
|
|
807
823
|
return arr[(arr.indexOf(pivot) + step + arr.length) % arr.length]
|
|
808
824
|
}
|
|
809
825
|
}
|