mockaton 11.2.5 → 11.2.6
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 +30 -28
- package/package.json +1 -1
- package/src/client/ApiCommander.js +54 -39
- package/src/client/ApiConstants.js +1 -1
- package/src/client/Filename.js +3 -3
- package/src/client/app.js +62 -61
- package/src/client/dom-utils.js +5 -3
- package/src/client/styles.css +77 -46
- package/src/server/Api.js +88 -125
- package/src/server/Filename.js +8 -6
- package/src/server/MockBroker.js +1 -1
- package/src/server/MockDispatcher.js +3 -2
- package/src/server/Mockaton.js +16 -11
- package/src/server/ProxyRelay.js +3 -2
- package/src/server/StaticDispatcher.js +2 -1
- package/src/server/Watcher.js +31 -3
- package/src/server/WatcherDevClient.js +38 -5
- package/src/server/cli.js +1 -0
- package/src/server/config.js +2 -1
- package/src/server/mockBrokersCollection.js +2 -1
- package/src/server/utils/fs.js +1 -0
- package/src/server/utils/http-response.js +6 -0
package/README.md
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
[](https://github.com/ericfortis/mockaton/actions/workflows/test.yml)
|
|
5
|
-
[](https://github.com/ericfortis/mockaton/actions/workflows/github-code-scanning/codeql)
|
|
6
5
|
[](https://codecov.io/github/ericfortis/mockaton)
|
|
7
6
|
|
|
8
7
|
An HTTP mock server for simulating APIs with minimal setup — ideal
|
|
@@ -46,15 +45,19 @@ curl localhost:2020/api/user
|
|
|
46
45
|
|
|
47
46
|
## Dashboard
|
|
48
47
|
|
|
49
|
-
On the dashboard you can
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
On the dashboard you can:
|
|
49
|
+
- Select a mock variant for a particular route
|
|
50
|
+
- 🕓 Delay responses
|
|
51
|
+
- Trigger an autogenerated `500` error
|
|
52
|
+
- …and cycle it off (for testing retries)
|
|
53
|
+
|
|
54
|
+
Nonetheless, there’s a programmatic API, which is handy for
|
|
55
|
+
setting up tests (see **Commander API** section below).
|
|
53
56
|
|
|
54
57
|
<picture>
|
|
55
|
-
<source media="(prefers-color-scheme: light)" srcset="pixaton-tests/tests/macos/pic-for-readme.
|
|
56
|
-
<source media="(prefers-color-scheme: dark)" srcset="pixaton-tests/tests/macos/pic-for-readme.
|
|
57
|
-
<img alt="Mockaton Dashboard" src="pixaton-tests/tests/macos/pic-for-readme.
|
|
58
|
+
<source media="(prefers-color-scheme: light)" srcset="pixaton-tests/tests/macos/pic-for-readme.vp761x740.light.gold.png">
|
|
59
|
+
<source media="(prefers-color-scheme: dark)" srcset="pixaton-tests/tests/macos/pic-for-readme.vp761x740.dark.gold.png">
|
|
60
|
+
<img alt="Mockaton Dashboard" src="pixaton-tests/tests/macos/pic-for-readme.vp761x740.light.gold.png">
|
|
58
61
|
</picture>
|
|
59
62
|
|
|
60
63
|
|
|
@@ -76,7 +79,7 @@ api/login<b>(invalid login attempt)</b>.POST.401.json
|
|
|
76
79
|
You can **Bulk Select** mocks by comments to simulate the complete states
|
|
77
80
|
you want. For example:
|
|
78
81
|
|
|
79
|
-
<img src="docs/bulk-select.png" width="180px">
|
|
82
|
+
<img src="docs/changelog/bulk-select.png" width="180px">
|
|
80
83
|
|
|
81
84
|
|
|
82
85
|
### Different response status code
|
|
@@ -95,10 +98,11 @@ api/videos.GET.<b>500</b>.txt # Internal Server Error
|
|
|
95
98
|
## Scraping Mocks from your Backend
|
|
96
99
|
|
|
97
100
|
### Option 1: Browser extension
|
|
98
|
-
The companion Chrome [devtools
|
|
99
|
-
extension](https://github.com/ericfortis/download-http-requests-browser-ext)
|
|
101
|
+
The companion Chrome [devtools extension](https://chromewebstore.google.com/detail/mockaton-downloader/babjpljmacbefcmlomjedmgmkecnmlaa)
|
|
100
102
|
lets you download all the HTTP responses and
|
|
101
|
-
save them following Mockaton’s filename convention.
|
|
103
|
+
save them in bulk following Mockaton’s filename convention.
|
|
104
|
+
|
|
105
|
+

|
|
102
106
|
|
|
103
107
|
### Option 2: Fallback to your backend
|
|
104
108
|
<details>
|
|
@@ -127,11 +131,10 @@ Sometimes the flow you need to test is
|
|
|
127
131
|
too difficult to reproduce from the actual backend.
|
|
128
132
|
|
|
129
133
|
- Demo edge cases to PMs, Design, and clients
|
|
130
|
-
- Set up screenshot tests
|
|
131
|
-
- Spot inadvertent regressions during development. For example, the demo
|
|
132
|
-
this repo has a list of colors containing all of their possible
|
|
133
|
-
|
|
134
|
-
indirectly notice if something else broke.
|
|
134
|
+
- Set up screenshot tests (see [pixaton-tests/](pixaton-tests) in this repo)
|
|
135
|
+
- Spot inadvertent regressions during development. For example, the demo
|
|
136
|
+
app in this repo has a list of colors containing all of their possible
|
|
137
|
+
states. This way you’ll indirectly notice if something broke.
|
|
135
138
|
|
|
136
139
|
<img src="./demo-app-vite/pixaton-tests/pic-for-readme.vp740x880.light.gold.png" alt="Mockaton Demo App Screenshot" width="740" />
|
|
137
140
|
|
|
@@ -154,11 +157,10 @@ make run-standalone-demo
|
|
|
154
157
|
- Dashboard: http://localhost:4040/mockaton
|
|
155
158
|
|
|
156
159
|
### Testing scenarios that would otherwise be skipped
|
|
157
|
-
- Trigger dynamic states on an API.
|
|
160
|
+
- Trigger dynamic states on an API. For example, for polled alerts or notifications.
|
|
158
161
|
- Testing retries, you can change an endpoint from a 500 to a 200 on the fly.
|
|
159
|
-
- Simulate errors on third-party APIs, or on your project’s backend
|
|
160
|
-
-
|
|
161
|
-
So you can, e.g.:
|
|
162
|
+
- Simulate errors on third-party APIs, or on your project’s backend.
|
|
163
|
+
- Generate dynamic responses. Use Node’s HTTP handlers (see function mocks below). So you can, e.g.:
|
|
162
164
|
- have an in-memory database
|
|
163
165
|
- read from disk
|
|
164
166
|
- read query string
|
|
@@ -755,6 +757,8 @@ api/foo/bar.GET.200.json
|
|
|
755
757
|
<br/>
|
|
756
758
|
|
|
757
759
|
## Commander API
|
|
760
|
+
[openapi.yaml](docs/openapi.yaml)
|
|
761
|
+
|
|
758
762
|
`Commander` is a JavaScript client for Mockaton’s HTTP API.
|
|
759
763
|
All of its methods return their `fetch` response promise.
|
|
760
764
|
```js
|
|
@@ -773,8 +777,8 @@ await mockaton.select('api/foo.200.GET.json')
|
|
|
773
777
|
```
|
|
774
778
|
|
|
775
779
|
### Toggle 500
|
|
776
|
-
|
|
777
|
-
|
|
780
|
+
- Selects the first found 500, which could be the autogenerated one.
|
|
781
|
+
- Or, selects the default file.
|
|
778
782
|
```js
|
|
779
783
|
await mockaton.toggle500('GET', '/api/foo')
|
|
780
784
|
```
|
|
@@ -848,7 +852,7 @@ await mockaton.setCorsAllowed(true)
|
|
|
848
852
|
|
|
849
853
|
### Reset
|
|
850
854
|
Re-initialize the collection. The selected mocks, cookies, and delays go back to
|
|
851
|
-
default, but the `proxyFallback`, `
|
|
855
|
+
default, but the `proxyFallback`, `collectProxied`, and `corsAllowed` are not affected.
|
|
852
856
|
```js
|
|
853
857
|
await mockaton.reset()
|
|
854
858
|
```
|
|
@@ -873,15 +877,13 @@ example, if you are polling, and you want to test the state change.
|
|
|
873
877
|
|
|
874
878
|
### Client Side
|
|
875
879
|
In contrast to Mockaton, which is an HTTP Server, these
|
|
876
|
-
programs hijack your browser’s HTTP client (
|
|
877
|
-
|
|
880
|
+
programs hijack your browser’s HTTP client (or Node’s).
|
|
878
881
|
- [Mock Server Worker (MSW)](https://mswjs.io)
|
|
879
882
|
- [Nock](https://github.com/nock/nock)
|
|
880
883
|
- [Fetch Mock](https://github.com/wheresrhys/fetch-mock)
|
|
881
|
-
- [Mentoss](https://github.com/humanwhocodes/mentoss)
|
|
884
|
+
- [Mentoss](https://github.com/humanwhocodes/mentoss)
|
|
882
885
|
|
|
883
886
|
### Server Side
|
|
884
|
-
|
|
885
887
|
- [Wire Mock](https://github.com/wiremock/wiremock)
|
|
886
888
|
- [Mock](https://github.com/dhuan/mock)
|
|
887
889
|
- [Swagger](https://swagger.io/)
|
package/package.json
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
API,
|
|
3
|
+
HEADER_SYNC_VERSION,
|
|
4
|
+
LONG_POLL_SERVER_TIMEOUT
|
|
5
|
+
} from './ApiConstants.js'
|
|
2
6
|
|
|
3
7
|
|
|
4
8
|
/** Client for controlling Mockaton via its HTTP API */
|
|
@@ -9,61 +13,72 @@ export class Commander {
|
|
|
9
13
|
this.addr = addr
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @param {number?} currSyncVer - On mismatch, it responds immediately. Otherwise, long polls.
|
|
18
|
-
* @param {AbortSignal} abortSignal
|
|
19
|
-
* @returns {JsonPromise<number>}
|
|
20
|
-
*/
|
|
21
|
-
getSyncVersion = (currSyncVer = undefined, abortSignal = undefined) =>
|
|
22
|
-
fetch(this.addr + API.syncVersion, {
|
|
23
|
-
signal: AbortSignal.any([
|
|
24
|
-
abortSignal,
|
|
25
|
-
AbortSignal.timeout(LONG_POLL_SERVER_TIMEOUT + 1000)
|
|
26
|
-
].filter(Boolean)),
|
|
27
|
-
headers: currSyncVer !== undefined
|
|
28
|
-
? { [HEADER_SYNC_VERSION]: currSyncVer }
|
|
29
|
-
: {}
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
#patch(api, body) {
|
|
34
|
-
return fetch(this.addr + api, {
|
|
35
|
-
method: 'PATCH',
|
|
36
|
-
body: JSON.stringify(body)
|
|
37
|
-
})
|
|
38
|
-
}
|
|
16
|
+
#patch = (api, body) => fetch(this.addr + api, {
|
|
17
|
+
method: 'PATCH',
|
|
18
|
+
body: JSON.stringify(body)
|
|
19
|
+
})
|
|
39
20
|
|
|
21
|
+
/** @returns {Promise<Response>} */
|
|
40
22
|
reset = () => this.#patch(API.reset)
|
|
41
23
|
|
|
42
|
-
|
|
24
|
+
/** @returns {Promise<Response>} */
|
|
43
25
|
setGlobalDelay = delay => this.#patch(API.globalDelay, delay)
|
|
26
|
+
|
|
27
|
+
/** @returns {Promise<Response>} */
|
|
44
28
|
setCorsAllowed = value => this.#patch(API.cors, value)
|
|
29
|
+
|
|
30
|
+
/** @returns {Promise<Response>} */
|
|
45
31
|
setProxyFallback = proxyAddr => this.#patch(API.fallback, proxyAddr)
|
|
32
|
+
|
|
33
|
+
/** @returns {Promise<Response>} */
|
|
46
34
|
setCollectProxied = shouldCollect => this.#patch(API.collectProxied, shouldCollect)
|
|
47
35
|
|
|
36
|
+
/** @returns {JsonPromise<State.cookies>} */
|
|
37
|
+
selectCookie = label => this.#patch(API.cookies, label)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
/** @returns {Promise<Response>} */
|
|
41
|
+
bulkSelectByComment = comment => this.#patch(API.bulkSelect, comment)
|
|
42
|
+
|
|
48
43
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
49
|
-
select = file =>
|
|
50
|
-
this.#patch(API.select, file)
|
|
44
|
+
select = file => this.#patch(API.select, file)
|
|
51
45
|
|
|
52
|
-
bulkSelectByComment = comment =>
|
|
53
|
-
this.#patch(API.bulkSelect, comment)
|
|
54
46
|
|
|
55
47
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
56
|
-
toggle500 = (method, urlMask) =>
|
|
57
|
-
this.#patch(API.toggle500, [method, urlMask])
|
|
48
|
+
toggle500 = (method, urlMask) => this.#patch(API.toggle500, [method, urlMask])
|
|
58
49
|
|
|
59
50
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
60
|
-
setRouteIsProxied = (method, urlMask, proxied) =>
|
|
61
|
-
this.#patch(API.proxied, [method, urlMask, proxied])
|
|
51
|
+
setRouteIsProxied = (method, urlMask, proxied) => this.#patch(API.proxied, [method, urlMask, proxied])
|
|
62
52
|
|
|
63
53
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
64
|
-
setRouteIsDelayed = (method, urlMask, delayed) =>
|
|
65
|
-
|
|
54
|
+
setRouteIsDelayed = (method, urlMask, delayed) => this.#patch(API.delay, [method, urlMask, delayed])
|
|
55
|
+
|
|
66
56
|
|
|
57
|
+
/** @returns {Promise<Response>} */
|
|
67
58
|
setStaticRouteStatus = (urlMask, status) => this.#patch(API.staticStatus, [urlMask, status])
|
|
59
|
+
|
|
60
|
+
/** @returns {Promise<Response>} */
|
|
68
61
|
setStaticRouteIsDelayed = (urlMask, delayed) => this.#patch(API.delayStatic, [urlMask, delayed])
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
/** @returns {JsonPromise<State>} */
|
|
66
|
+
getState = () => fetch(this.addr + API.state)
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* This is for listening to real-time updates. It responds when a new mock is added, deleted, or renamed.
|
|
70
|
+
* @param {number?} currSyncVer - On mismatch, it responds immediately. Otherwise, long polls.
|
|
71
|
+
* @param {AbortSignal} abortSignal
|
|
72
|
+
* @returns {JsonPromise<number>}
|
|
73
|
+
*/
|
|
74
|
+
getSyncVersion = (currSyncVer = undefined, abortSignal = undefined) =>
|
|
75
|
+
fetch(this.addr + API.syncVersion, {
|
|
76
|
+
signal: AbortSignal.any([
|
|
77
|
+
abortSignal,
|
|
78
|
+
AbortSignal.timeout(LONG_POLL_SERVER_TIMEOUT + 1000)
|
|
79
|
+
].filter(Boolean)),
|
|
80
|
+
headers: currSyncVer !== undefined
|
|
81
|
+
? { [HEADER_SYNC_VERSION]: currSyncVer }
|
|
82
|
+
: {}
|
|
83
|
+
})
|
|
69
84
|
}
|
package/src/client/Filename.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const reComments = /\(.*?\)/g // Anything within parentheses
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
Array.from(file.matchAll(reComments), ([c]) => c)
|
|
7
|
-
|
|
5
|
+
export function extractComments(file) {
|
|
6
|
+
return Array.from(file.matchAll(reComments), ([c]) => c)
|
|
7
|
+
}
|
|
8
8
|
|
|
9
9
|
export function parseFilename(file) {
|
|
10
10
|
const tokens = file.replace(reComments, '').split('.')
|
package/src/client/app.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
createElement as r,
|
|
3
|
+
createSvgElement as s,
|
|
4
|
+
className, restoreFocus, Defer, Fragment
|
|
5
|
+
} from './dom-utils.js'
|
|
6
|
+
|
|
4
7
|
import { store } from './app-store.js'
|
|
8
|
+
import { parseFilename } from './Filename.js'
|
|
9
|
+
import { HEADER_502 } from './ApiConstants.js'
|
|
5
10
|
|
|
6
11
|
|
|
7
12
|
const CSS = {
|
|
@@ -27,12 +32,18 @@ const CSS = {
|
|
|
27
32
|
Resizer: null,
|
|
28
33
|
SaveProxiedCheckbox: null,
|
|
29
34
|
SettingsMenu: null,
|
|
35
|
+
Table: null,
|
|
36
|
+
TableHeading: null,
|
|
37
|
+
TableRow: null,
|
|
30
38
|
|
|
31
39
|
animIn: null,
|
|
40
|
+
canProxy: null,
|
|
41
|
+
checkboxBody: null,
|
|
32
42
|
chosen: null,
|
|
33
43
|
dittoDir: null,
|
|
34
44
|
leftSide: null,
|
|
35
45
|
nonDefault: null,
|
|
46
|
+
nonGroupedByMethod: null,
|
|
36
47
|
rightSide: null,
|
|
37
48
|
status4xx: null,
|
|
38
49
|
|
|
@@ -84,7 +95,7 @@ function App() {
|
|
|
84
95
|
style: { width: leftSideRef.width },
|
|
85
96
|
className: CSS.leftSide
|
|
86
97
|
},
|
|
87
|
-
r('
|
|
98
|
+
r('div', className(CSS.Table),
|
|
88
99
|
MockList(),
|
|
89
100
|
StaticFilesList())),
|
|
90
101
|
r('div', { className: CSS.rightSide },
|
|
@@ -212,7 +223,7 @@ function SaveProxiedCheckbox(ref) {
|
|
|
212
223
|
checked: store.collectProxied,
|
|
213
224
|
onChange() { store.setCollectProxied(this.checked) }
|
|
214
225
|
}),
|
|
215
|
-
r('span',
|
|
226
|
+
r('span', className(CSS.checkboxBody), t`Save Mocks`)))
|
|
216
227
|
}
|
|
217
228
|
|
|
218
229
|
|
|
@@ -258,7 +269,7 @@ function SettingsMenu(id) {
|
|
|
258
269
|
checked: store.groupByMethod,
|
|
259
270
|
onChange: store.toggleGroupByMethod
|
|
260
271
|
}),
|
|
261
|
-
r('span',
|
|
272
|
+
r('span', className(CSS.checkboxBody), t`Group by Method`)),
|
|
262
273
|
|
|
263
274
|
r('a', {
|
|
264
275
|
href: 'https://github.com/ericfortis/mockaton',
|
|
@@ -280,9 +291,8 @@ function MockList() {
|
|
|
280
291
|
|
|
281
292
|
if (store.groupByMethod)
|
|
282
293
|
return Object.keys(store.brokersByMethod).map(method => Fragment(
|
|
283
|
-
r('
|
|
284
|
-
|
|
285
|
-
r('th', { colspan: 2 }, method)),
|
|
294
|
+
r('div', className(CSS.TableHeading, store.canProxy && CSS.canProxy),
|
|
295
|
+
method),
|
|
286
296
|
store.brokersAsRowsByMethod(method).map(Row)))
|
|
287
297
|
|
|
288
298
|
return store.brokersAsRowsByMethod('*').map(Row)
|
|
@@ -293,33 +303,26 @@ function MockList() {
|
|
|
293
303
|
* @param {number} i
|
|
294
304
|
*/
|
|
295
305
|
function Row(row, i) {
|
|
296
|
-
const {
|
|
306
|
+
const { method, urlMask } = row
|
|
297
307
|
return (
|
|
298
|
-
r('
|
|
299
|
-
key,
|
|
300
|
-
...className(
|
|
308
|
+
r('div', {
|
|
309
|
+
key: row.key,
|
|
310
|
+
...className(CSS.TableRow,
|
|
311
|
+
render.count > 1 && row.isNew && CSS.animIn)
|
|
301
312
|
},
|
|
302
|
-
store.canProxy &&
|
|
303
|
-
ProxyToggler(method, urlMask, row.proxied)),
|
|
313
|
+
store.canProxy && ProxyToggler(method, urlMask, row.proxied),
|
|
304
314
|
|
|
305
|
-
|
|
306
|
-
DelayRouteToggler(method, urlMask, row.delayed)),
|
|
315
|
+
DelayRouteToggler(method, urlMask, row.delayed),
|
|
307
316
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
urlMask,
|
|
312
|
-
!row.proxied && row.status === 500, // checked
|
|
313
|
-
row.opts.length === 1 && row.status === 500)), // disabled
|
|
317
|
+
InternalServerErrorToggler(method, urlMask,
|
|
318
|
+
!row.proxied && row.status === 500, // checked
|
|
319
|
+
row.opts.length === 1 && row.status === 500), // disabled
|
|
314
320
|
|
|
315
|
-
!store.groupByMethod && r('
|
|
316
|
-
method),
|
|
321
|
+
!store.groupByMethod && r('span', className(CSS.Method), method),
|
|
317
322
|
|
|
318
|
-
|
|
319
|
-
PreviewLink(method, urlMask, row.urlMaskDittoed, i === 0)),
|
|
323
|
+
PreviewLink(method, urlMask, row.urlMaskDittoed, i === 0),
|
|
320
324
|
|
|
321
|
-
|
|
322
|
-
MockSelector(row))))
|
|
325
|
+
MockSelector(row)))
|
|
323
326
|
}
|
|
324
327
|
|
|
325
328
|
function renderRow(method, urlMask) {
|
|
@@ -331,10 +334,10 @@ function renderRow(method, urlMask) {
|
|
|
331
334
|
})
|
|
332
335
|
|
|
333
336
|
function trFor(key) {
|
|
334
|
-
return leftSideRef.elem.querySelector(
|
|
337
|
+
return leftSideRef.elem.querySelector(`.${CSS.TableRow}[key="${key}"]`)
|
|
335
338
|
}
|
|
336
339
|
function unChooseOld() {
|
|
337
|
-
return leftSideRef.elem.querySelector(`
|
|
340
|
+
return leftSideRef.elem.querySelector(`a.${CSS.chosen}`)
|
|
338
341
|
?.classList.remove(CSS.chosen)
|
|
339
342
|
}
|
|
340
343
|
}
|
|
@@ -398,7 +401,7 @@ function InternalServerErrorToggler(method, urlMask, checked, disabled) {
|
|
|
398
401
|
onChange() { store.toggle500(method, urlMask) },
|
|
399
402
|
'data-focus-group': FocusGroup.StatusToggler
|
|
400
403
|
}),
|
|
401
|
-
r('span',
|
|
404
|
+
r('span', className(CSS.checkboxBody), t`500`)))
|
|
402
405
|
}
|
|
403
406
|
|
|
404
407
|
function ProxyToggler(method, urlMask, checked) {
|
|
@@ -421,49 +424,47 @@ function ProxyToggler(method, urlMask, checked) {
|
|
|
421
424
|
/** # StaticFilesList */
|
|
422
425
|
|
|
423
426
|
function StaticFilesList() {
|
|
424
|
-
const { canProxy, groupByMethod } = store
|
|
425
427
|
const rows = store.staticBrokersAsRows()
|
|
426
428
|
return !rows.length
|
|
427
429
|
? null
|
|
428
430
|
: Fragment(
|
|
429
|
-
r('
|
|
430
|
-
|
|
431
|
-
|
|
431
|
+
r('div',
|
|
432
|
+
className(CSS.TableHeading,
|
|
433
|
+
store.canProxy && CSS.canProxy,
|
|
434
|
+
!store.groupByMethod && CSS.nonGroupedByMethod),
|
|
435
|
+
store.groupByMethod ? t`Static GET` : t`Static`),
|
|
432
436
|
rows.map(StaticRow))
|
|
433
437
|
}
|
|
434
438
|
|
|
435
439
|
/** @param {StaticBrokerRowModel} row */
|
|
436
440
|
function StaticRow(row) {
|
|
437
|
-
const {
|
|
441
|
+
const { groupByMethod } = store
|
|
438
442
|
const [ditto, tail] = row.urlMaskDittoed
|
|
439
443
|
return (
|
|
440
|
-
r('
|
|
444
|
+
r('div', {
|
|
441
445
|
key: row.key,
|
|
442
|
-
...className(
|
|
446
|
+
...className(CSS.TableRow,
|
|
447
|
+
render.count > 1 && row.isNew && CSS.animIn)
|
|
443
448
|
},
|
|
444
|
-
|
|
445
|
-
r('td', null,
|
|
446
|
-
DelayStaticRouteToggler(row.urlMask, row.delayed)),
|
|
449
|
+
DelayStaticRouteToggler(row.urlMask, row.delayed),
|
|
447
450
|
|
|
448
|
-
|
|
449
|
-
NotFoundToggler(row.urlMask, row.status === 404)),
|
|
451
|
+
NotFoundToggler(row.urlMask, row.status === 404),
|
|
450
452
|
|
|
451
|
-
!groupByMethod && r('
|
|
452
|
-
'GET'),
|
|
453
|
+
!groupByMethod && r('span', className(CSS.Method), 'GET'),
|
|
453
454
|
|
|
454
|
-
r('
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
: tail))))
|
|
455
|
+
r('a', {
|
|
456
|
+
href: row.urlMask,
|
|
457
|
+
target: '_blank',
|
|
458
|
+
className: CSS.PreviewLink,
|
|
459
|
+
'data-focus-group': FocusGroup.PreviewLink
|
|
460
|
+
}, ditto
|
|
461
|
+
? [r('span', className(CSS.dittoDir), ditto), tail]
|
|
462
|
+
: tail)))
|
|
463
463
|
}
|
|
464
464
|
|
|
465
465
|
function DelayStaticRouteToggler(route, checked) {
|
|
466
466
|
return ClickDragToggler({
|
|
467
|
+
optClassName: store.canProxy && CSS.canProxy,
|
|
467
468
|
checked,
|
|
468
469
|
focusGroup: FocusGroup.DelayToggler,
|
|
469
470
|
commit(checked) {
|
|
@@ -486,11 +487,11 @@ function NotFoundToggler(route, checked) {
|
|
|
486
487
|
store.setStaticRouteStatus(route, this.checked ? 404 : 200)
|
|
487
488
|
}
|
|
488
489
|
}),
|
|
489
|
-
r('span',
|
|
490
|
+
r('span', className(CSS.checkboxBody), t`404`)))
|
|
490
491
|
}
|
|
491
492
|
|
|
492
493
|
|
|
493
|
-
function ClickDragToggler({ checked, commit, focusGroup }) {
|
|
494
|
+
function ClickDragToggler({ checked, commit, focusGroup, optClassName }) {
|
|
494
495
|
function onPointerEnter(event) {
|
|
495
496
|
if (event.buttons === 1)
|
|
496
497
|
onPointerDown.call(this)
|
|
@@ -509,7 +510,7 @@ function ClickDragToggler({ checked, commit, focusGroup }) {
|
|
|
509
510
|
}
|
|
510
511
|
return (
|
|
511
512
|
r('label', {
|
|
512
|
-
className
|
|
513
|
+
...className(CSS.DelayToggler, optClassName),
|
|
513
514
|
title: t`Delay`
|
|
514
515
|
},
|
|
515
516
|
r('input', {
|
|
@@ -542,7 +543,7 @@ function Resizer(ref) {
|
|
|
542
543
|
}
|
|
543
544
|
|
|
544
545
|
function onMove(event) {
|
|
545
|
-
const MIN_LEFT_WIDTH =
|
|
546
|
+
const MIN_LEFT_WIDTH = 340
|
|
546
547
|
raf = raf || requestAnimationFrame(() => {
|
|
547
548
|
ref.width = Math.max(initialWidth - (initialX - event.clientX), MIN_LEFT_WIDTH) + 'px'
|
|
548
549
|
ref.elem.style.width = ref.width
|
|
@@ -804,7 +805,7 @@ function initKeyboardNavigation() {
|
|
|
804
805
|
}
|
|
805
806
|
|
|
806
807
|
function rowFocusable(el, step) {
|
|
807
|
-
const row = el.closest(
|
|
808
|
+
const row = el.closest(`.${CSS.TableRow}`)
|
|
808
809
|
if (row) {
|
|
809
810
|
const focusables = Array.from(row.querySelectorAll('a, input, select:not(:disabled)'))
|
|
810
811
|
return circularAdjacent(step, focusables, el)
|
|
@@ -813,7 +814,7 @@ function initKeyboardNavigation() {
|
|
|
813
814
|
|
|
814
815
|
function allInFocusGroup(focusGroup) {
|
|
815
816
|
return Array.from(leftSideRef.elem.querySelectorAll(
|
|
816
|
-
|
|
817
|
+
`.${CSS.TableRow} [data-focus-group="${focusGroup}"]:is(input, a)`))
|
|
817
818
|
}
|
|
818
819
|
|
|
819
820
|
function circularAdjacent(step = 1, arr, pivot) {
|
package/src/client/dom-utils.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export function className(...args) {
|
|
2
|
-
return {
|
|
2
|
+
return {
|
|
3
|
+
className: args.filter(Boolean).join(' ')
|
|
4
|
+
}
|
|
3
5
|
}
|
|
4
6
|
|
|
5
7
|
export function createElement(tag, props, ...children) {
|
|
@@ -14,8 +16,8 @@ export function createElement(tag, props, ...children) {
|
|
|
14
16
|
return elem
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
export function createSvgElement(
|
|
18
|
-
const elem = document.createElementNS('http://www.w3.org/2000/svg',
|
|
19
|
+
export function createSvgElement(tag, props, ...children) {
|
|
20
|
+
const elem = document.createElementNS('http://www.w3.org/2000/svg', tag)
|
|
19
21
|
for (const [k, v] of Object.entries(props))
|
|
20
22
|
elem.setAttribute(k, v)
|
|
21
23
|
elem.append(...children.flat().filter(Boolean))
|