mockaton 13.3.2 → 13.3.4
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/app-payload-viewer.js +6 -7
- package/src/client/app-store.js +7 -20
- package/src/client/app.css +16 -14
- package/src/client/app.js +86 -91
- package/src/client/dirStructure.js +47 -0
- package/src/client/dirStructure.test.js +81 -0
- package/src/client/graphics.js +18 -28
- package/src/server/Mockaton.js +1 -1
- package/src/server/Watcher.js +0 -1
- package/src/server/mockBrokersCollection.js +1 -1
package/package.json
CHANGED
|
@@ -93,8 +93,6 @@ export async function previewMock() {
|
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
async function updatePayloadViewer(proxied, file, response) {
|
|
96
|
-
const mime = response.headers.get('content-type') || ''
|
|
97
|
-
|
|
98
96
|
titleRef.elem.replaceChildren(proxied
|
|
99
97
|
? PayloadViewerTitleWhenProxied(response)
|
|
100
98
|
: PayloadViewerTitle(file, response.statusText))
|
|
@@ -104,6 +102,12 @@ async function updatePayloadViewer(proxied, file, response) {
|
|
|
104
102
|
return
|
|
105
103
|
}
|
|
106
104
|
|
|
105
|
+
async function bodyAsText() {
|
|
106
|
+
return (await response.text()) || t`/* Empty Response Body */`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const mime = response.headers.get('content-type') || ''
|
|
110
|
+
|
|
107
111
|
if (mime.startsWith('image/'))
|
|
108
112
|
codeRef.elem.replaceChildren(r('img', {
|
|
109
113
|
src: URL.createObjectURL(await response.blob())
|
|
@@ -140,11 +144,6 @@ async function updatePayloadViewer(proxied, file, response) {
|
|
|
140
144
|
href: URL.createObjectURL(await response.blob()),
|
|
141
145
|
download: store.chosenLink.urlMask
|
|
142
146
|
}, t`Download`))
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
async function bodyAsText() {
|
|
146
|
-
return (await response.text()) || t`/* Empty Response Body */`
|
|
147
|
-
}
|
|
148
147
|
}
|
|
149
148
|
|
|
150
149
|
|
package/src/client/app-store.js
CHANGED
|
@@ -140,21 +140,7 @@ export const store = {
|
|
|
140
140
|
store.brokersByMethod[method][urlMask] = broker
|
|
141
141
|
},
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
const groups = []
|
|
145
|
-
let g = null
|
|
146
|
-
for (const row of store._brokersAsRowsByMethod(method)) {
|
|
147
|
-
const folder = row.urlMask.substring(0, row.urlMask.lastIndexOf('/') + 1)
|
|
148
|
-
if (!g || g.folder !== folder) {
|
|
149
|
-
g = { folder, children: [] }
|
|
150
|
-
groups.push(g)
|
|
151
|
-
}
|
|
152
|
-
g.children.push(row)
|
|
153
|
-
}
|
|
154
|
-
return groups
|
|
155
|
-
},
|
|
156
|
-
|
|
157
|
-
_brokersAsRowsByMethod(method) {
|
|
143
|
+
brokersAsRowsByMethod(method) {
|
|
158
144
|
const rows = store._brokersAsArray(method)
|
|
159
145
|
.map(b => new BrokerRowModel(b, store.canProxy))
|
|
160
146
|
.sort((a, b) => a.urlMask.localeCompare(b.urlMask))
|
|
@@ -223,18 +209,18 @@ export const store = {
|
|
|
223
209
|
function initPreference(param) {
|
|
224
210
|
const qs = new URLSearchParams(globalThis.location?.search)
|
|
225
211
|
if (!qs.has(param)) {
|
|
226
|
-
const group = globalThis.localStorage?.getItem(param) !== '
|
|
212
|
+
const group = globalThis.localStorage?.getItem(param) !== '0'
|
|
227
213
|
if (!group) {
|
|
228
214
|
const url = new URL(globalThis.location?.href)
|
|
229
|
-
url.searchParams.set(param,
|
|
215
|
+
url.searchParams.set(param, '0')
|
|
230
216
|
history.replaceState(null, '', url)
|
|
231
217
|
}
|
|
232
218
|
return group
|
|
233
219
|
}
|
|
234
|
-
return qs.get(param) !== '
|
|
220
|
+
return qs.get(param) !== '0'
|
|
235
221
|
}
|
|
236
222
|
|
|
237
|
-
// When false, the URL and localStorage will have param=
|
|
223
|
+
// When false, the URL and localStorage will have param='0'
|
|
238
224
|
function togglePreference(param, nextVal) {
|
|
239
225
|
if (nextVal)
|
|
240
226
|
globalThis.localStorage?.removeItem(param)
|
|
@@ -245,7 +231,7 @@ function togglePreference(param, nextVal) {
|
|
|
245
231
|
if (nextVal)
|
|
246
232
|
url.searchParams.delete(param)
|
|
247
233
|
else
|
|
248
|
-
url.searchParams.set(param,
|
|
234
|
+
url.searchParams.set(param, '0')
|
|
249
235
|
history.replaceState(null, '', url)
|
|
250
236
|
}
|
|
251
237
|
|
|
@@ -285,6 +271,7 @@ export class BrokerRowModel {
|
|
|
285
271
|
method = ''
|
|
286
272
|
urlMask = ''
|
|
287
273
|
urlMaskDittoed = ['', '']
|
|
274
|
+
children = []
|
|
288
275
|
#broker = /** @type ClientMockBroker */ {}
|
|
289
276
|
#canProxy = false
|
|
290
277
|
|
package/src/client/app.css
CHANGED
|
@@ -6,17 +6,17 @@
|
|
|
6
6
|
--colorBgHeader: light-dark(#f2f2f3, #141414);
|
|
7
7
|
--colorBgHeaderField: light-dark(#fff, #222);
|
|
8
8
|
|
|
9
|
-
--colorBorder: light-dark(#e0e0e0, #
|
|
9
|
+
--colorBorder: light-dark(#e0e0e0, #2c2c2c);
|
|
10
10
|
--colorBorderActive: light-dark(#c8c8c8, #3c3c3c);
|
|
11
11
|
|
|
12
12
|
--colorLabel: light-dark(#555, #aaa);
|
|
13
13
|
--colorText: light-dark(#000, #fff);
|
|
14
14
|
|
|
15
|
-
--colorAccent: light-dark(#
|
|
16
|
-
--colorHover: light-dark(#
|
|
15
|
+
--colorAccent: light-dark(#0081ff, #2495ff);
|
|
16
|
+
--colorHover: light-dark(#dbedff, #062d59);
|
|
17
17
|
|
|
18
18
|
--colorRed: light-dark(#da0f00, #f41606);
|
|
19
|
-
--colorPink: light-dark(#ed206a, #
|
|
19
|
+
--colorPink: light-dark(#ed206a, #ff2e7a);
|
|
20
20
|
--colorPurple: light-dark(#9b71e8, #ae81ff);
|
|
21
21
|
--colorGreen: light-dark(#388e3c, #a6e22e);
|
|
22
22
|
--colorBg4xx: light-dark(#ffedd1, #68554a);
|
|
@@ -44,6 +44,8 @@ body {
|
|
|
44
44
|
padding: 0;
|
|
45
45
|
border: 0;
|
|
46
46
|
margin: 0;
|
|
47
|
+
letter-spacing: -0.374px;
|
|
48
|
+
line-height: 1.2;
|
|
47
49
|
font-family: inherit;
|
|
48
50
|
font-size: 100%;
|
|
49
51
|
scrollbar-width: thin;
|
|
@@ -119,13 +121,8 @@ header {
|
|
|
119
121
|
align-self: end;
|
|
120
122
|
margin-right: 22px;
|
|
121
123
|
margin-bottom: 3px;
|
|
122
|
-
opacity: 94%;
|
|
123
124
|
transition: opacity 240ms ease-in-out;
|
|
124
125
|
|
|
125
|
-
&:hover {
|
|
126
|
-
opacity: 1;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
126
|
svg {
|
|
130
127
|
width: 120px;
|
|
131
128
|
pointer-events: none;
|
|
@@ -192,12 +189,14 @@ header {
|
|
|
192
189
|
height: 24px;
|
|
193
190
|
flex-shrink: 0;
|
|
194
191
|
align-self: end;
|
|
192
|
+
margin-bottom: 2px;
|
|
195
193
|
margin-left: auto;
|
|
196
194
|
border-radius: 50%;
|
|
197
|
-
fill:
|
|
195
|
+
fill: white;
|
|
196
|
+
background: var(--colorAccent);
|
|
198
197
|
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
svg {
|
|
199
|
+
transform: scale(.7);
|
|
201
200
|
}
|
|
202
201
|
}
|
|
203
202
|
}
|
|
@@ -420,6 +419,7 @@ main {
|
|
|
420
419
|
.Table {
|
|
421
420
|
height: 100%;
|
|
422
421
|
padding: 16px;
|
|
422
|
+
padding-bottom: 64px;
|
|
423
423
|
padding-left: 12px;
|
|
424
424
|
user-select: none;
|
|
425
425
|
overflow-y: auto;
|
|
@@ -462,6 +462,7 @@ main {
|
|
|
462
462
|
overflow: hidden;
|
|
463
463
|
align-items: center;
|
|
464
464
|
padding: 5px 0;
|
|
465
|
+
font-weight: 500;
|
|
465
466
|
font-size: 12px;
|
|
466
467
|
list-style: none;
|
|
467
468
|
cursor: pointer;
|
|
@@ -516,7 +517,7 @@ main {
|
|
|
516
517
|
}
|
|
517
518
|
|
|
518
519
|
&[open] {
|
|
519
|
-
&:has(summary:hover) {
|
|
520
|
+
&:has(summary:hover):not(:has(details:hover)) {
|
|
520
521
|
background: linear-gradient(90deg, var(--colorHover), var(--colorBg));
|
|
521
522
|
}
|
|
522
523
|
|
|
@@ -567,6 +568,7 @@ main {
|
|
|
567
568
|
padding: 6px 8px;
|
|
568
569
|
margin-right: -2px;
|
|
569
570
|
margin-left: 4px;
|
|
571
|
+
font-weight: 500;
|
|
570
572
|
border-radius: var(--radius);
|
|
571
573
|
word-break: break-word;
|
|
572
574
|
|
|
@@ -578,7 +580,7 @@ main {
|
|
|
578
580
|
background: var(--colorAccent);
|
|
579
581
|
}
|
|
580
582
|
.dittoDir {
|
|
581
|
-
opacity: 0.
|
|
583
|
+
opacity: 0.8;
|
|
582
584
|
filter: saturate(0.1);
|
|
583
585
|
}
|
|
584
586
|
}
|
package/src/client/app.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createElement as r, t, classNames, restoreFocus, Fragment, defineClassN
|
|
|
3
3
|
import { store } from './app-store.js'
|
|
4
4
|
import { API } from './ApiConstants.js'
|
|
5
5
|
import { Header } from './app-header.js'
|
|
6
|
+
import { dirStructure } from './dirStructure.js'
|
|
6
7
|
import { PayloadViewer, previewMock } from './app-payload-viewer.js'
|
|
7
8
|
import { TimerIcon, CloudIcon, ChevronDownIcon } from './graphics.js'
|
|
8
9
|
|
|
@@ -21,36 +22,36 @@ initKeyboardNavigation()
|
|
|
21
22
|
let mounted = false
|
|
22
23
|
function render() {
|
|
23
24
|
restoreFocus(() => document.body.replaceChildren(App()))
|
|
24
|
-
if (store.hasChosenLink)
|
|
25
|
-
|
|
25
|
+
if (store.hasChosenLink) previewMock()
|
|
26
|
+
if (!mounted) LeftSide.$('a')?.focus()
|
|
26
27
|
mounted = true
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
const leftSideRef = {}
|
|
31
|
-
|
|
32
30
|
function App() {
|
|
33
|
-
return Fragment(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
function Main() {
|
|
38
|
-
return (
|
|
31
|
+
return Fragment(
|
|
32
|
+
Header(),
|
|
39
33
|
r('main', null,
|
|
40
|
-
|
|
41
|
-
ref: leftSideRef,
|
|
42
|
-
style: { width: leftSideRef.width },
|
|
43
|
-
className: CSS.leftSide
|
|
44
|
-
},
|
|
45
|
-
r('div', { className: CSS.SubToolbar },
|
|
46
|
-
GroupByMethod(),
|
|
47
|
-
BulkSelector()),
|
|
48
|
-
r('div', { className: CSS.Table }, MockList())),
|
|
34
|
+
LeftSide(),
|
|
49
35
|
r('div', { className: CSS.rightSide },
|
|
50
|
-
Resizer(
|
|
36
|
+
Resizer(LeftSide.ref),
|
|
51
37
|
PayloadViewer())))
|
|
52
38
|
}
|
|
53
39
|
|
|
40
|
+
function LeftSide() {
|
|
41
|
+
return r('div', {
|
|
42
|
+
ref: LeftSide.ref,
|
|
43
|
+
style: { width: LeftSide.ref.width },
|
|
44
|
+
className: CSS.leftSide
|
|
45
|
+
},
|
|
46
|
+
r('div', { className: CSS.SubToolbar },
|
|
47
|
+
GroupByMethod(),
|
|
48
|
+
BulkSelector()),
|
|
49
|
+
r('div', { className: CSS.Table }, MockList()))
|
|
50
|
+
}
|
|
51
|
+
LeftSide.ref = { width: undefined }
|
|
52
|
+
LeftSide.$ = selector => LeftSide.ref.elem.querySelector(selector)
|
|
53
|
+
LeftSide.$$ = selector => LeftSide.ref.elem.querySelectorAll(selector)
|
|
54
|
+
|
|
54
55
|
|
|
55
56
|
function GroupByMethod() {
|
|
56
57
|
return (
|
|
@@ -90,7 +91,6 @@ function BulkSelector() {
|
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
|
|
93
|
-
|
|
94
94
|
function MockList() {
|
|
95
95
|
if (!Object.keys(store.brokersByMethod).length)
|
|
96
96
|
return r('div', null, t`No mocks found`)
|
|
@@ -100,15 +100,27 @@ function MockList() {
|
|
|
100
100
|
r('div', {
|
|
101
101
|
className: classNames(CSS.TableHeading, store.canProxy && CSS.canProxy)
|
|
102
102
|
}, method),
|
|
103
|
-
FolderGroups(store.
|
|
103
|
+
FolderGroups(store.brokersAsRowsByMethod(method))))
|
|
104
|
+
|
|
105
|
+
return FolderGroups(store.brokersAsRowsByMethod('*'))
|
|
106
|
+
}
|
|
104
107
|
|
|
105
|
-
|
|
108
|
+
function FolderGroups(bRows) {
|
|
109
|
+
const res = []
|
|
110
|
+
for (const b of dirStructure(bRows)) {
|
|
111
|
+
if (!b.children.length)
|
|
112
|
+
res.push(Row(b))
|
|
113
|
+
else
|
|
114
|
+
res.push(FolderGroup(b))
|
|
115
|
+
}
|
|
116
|
+
return res
|
|
106
117
|
}
|
|
107
118
|
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
119
|
+
function FolderGroup(broker) {
|
|
120
|
+
const folder = broker.urlMask
|
|
121
|
+
const children = broker.children
|
|
122
|
+
return (
|
|
123
|
+
r('details', {
|
|
112
124
|
className: CSS.FolderGroup,
|
|
113
125
|
open: !store.collapsedFolders.has(folder),
|
|
114
126
|
onToggle() {
|
|
@@ -124,55 +136,77 @@ function FolderGroups(groups) {
|
|
|
124
136
|
store.canProxy && CSS.canProxy)
|
|
125
137
|
},
|
|
126
138
|
folder + '…')),
|
|
127
|
-
|
|
139
|
+
Row(broker),
|
|
140
|
+
children.map(c => c.children.length
|
|
141
|
+
? FolderGroup(c)
|
|
142
|
+
: Row(c))))
|
|
128
143
|
}
|
|
129
144
|
|
|
130
|
-
/**
|
|
131
|
-
|
|
132
|
-
* @param {number} i
|
|
133
|
-
*/
|
|
134
|
-
function Row(row, i) {
|
|
145
|
+
/** @param {BrokerRowModel} row */
|
|
146
|
+
function Row(row) {
|
|
135
147
|
const { method, urlMask } = row
|
|
136
148
|
return (
|
|
137
149
|
r('div', {
|
|
138
150
|
key: row.key,
|
|
139
151
|
className: classNames(CSS.TableRow, mounted && row.isNew && CSS.animIn)
|
|
140
152
|
},
|
|
141
|
-
store.canProxy && ProxyToggler(method, urlMask, row.proxied),
|
|
142
153
|
|
|
143
|
-
|
|
144
|
-
|
|
154
|
+
store.canProxy && ClickDragToggler({
|
|
155
|
+
className: CSS.ProxyToggler,
|
|
156
|
+
title: t`Proxy Toggler`,
|
|
157
|
+
body: CloudIcon(),
|
|
158
|
+
checked: row.proxied,
|
|
145
159
|
commit(checked) {
|
|
146
|
-
store.
|
|
147
|
-
}
|
|
160
|
+
store.setProxied(method, urlMask, checked)
|
|
161
|
+
}
|
|
148
162
|
}),
|
|
149
163
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
checked:
|
|
155
|
-
commit() {
|
|
156
|
-
store.
|
|
164
|
+
ClickDragToggler({
|
|
165
|
+
className: CSS.DelayToggler,
|
|
166
|
+
title: t`Delay`,
|
|
167
|
+
body: TimerIcon(),
|
|
168
|
+
checked: row.delayed,
|
|
169
|
+
commit(checked) {
|
|
170
|
+
store.setDelayed(method, urlMask, checked)
|
|
157
171
|
}
|
|
158
172
|
}),
|
|
159
173
|
|
|
174
|
+
ClickDragToggler(
|
|
175
|
+
row.isStatic
|
|
176
|
+
? {
|
|
177
|
+
className: CSS.StatusCodeToggler,
|
|
178
|
+
title: t`Not Found`,
|
|
179
|
+
body: t`404`,
|
|
180
|
+
disabled: row.opts.length === 1 && row.status === 404,
|
|
181
|
+
checked: !row.proxied && row.status === 404,
|
|
182
|
+
commit() { store.toggleStatus(method, urlMask, 404) }
|
|
183
|
+
}
|
|
184
|
+
: {
|
|
185
|
+
className: CSS.StatusCodeToggler,
|
|
186
|
+
title: t`Internal Server Error`,
|
|
187
|
+
body: t`500`,
|
|
188
|
+
disabled: row.opts.length === 1 && row.status === 500,
|
|
189
|
+
checked: !row.proxied && row.status === 500,
|
|
190
|
+
commit() { store.toggleStatus(method, urlMask, 500) }
|
|
191
|
+
}),
|
|
192
|
+
|
|
160
193
|
!store.groupByMethod && r('span', { className: CSS.Method }, method),
|
|
161
194
|
|
|
162
|
-
PreviewLink(method, urlMask, row.urlMaskDittoed
|
|
195
|
+
PreviewLink(method, urlMask, row.urlMaskDittoed),
|
|
163
196
|
|
|
164
197
|
MockSelector(row)))
|
|
165
198
|
}
|
|
166
199
|
|
|
200
|
+
|
|
167
201
|
function renderRow(method, urlMask) {
|
|
168
202
|
unChooseOld()
|
|
169
203
|
const row = store.brokerAsRow(method, urlMask)
|
|
170
|
-
const tr =
|
|
204
|
+
const tr = LeftSide.$(`.${CSS.TableRow}[key="${row.key}"]`)
|
|
171
205
|
mergeTableRow(tr, Row(row))
|
|
172
206
|
previewMock()
|
|
173
207
|
|
|
174
208
|
function unChooseOld() {
|
|
175
|
-
return
|
|
209
|
+
return LeftSide.$(`a.${CSS.chosen}`)
|
|
176
210
|
?.classList.remove(CSS.chosen)
|
|
177
211
|
}
|
|
178
212
|
|
|
@@ -203,8 +237,7 @@ function renderRow(method, urlMask) {
|
|
|
203
237
|
}
|
|
204
238
|
|
|
205
239
|
|
|
206
|
-
|
|
207
|
-
function PreviewLink(method, urlMask, urlMaskDittoed, autofocus) {
|
|
240
|
+
function PreviewLink(method, urlMask, urlMaskDittoed) {
|
|
208
241
|
function onClick(event) {
|
|
209
242
|
event.preventDefault()
|
|
210
243
|
store.previewLink(method, urlMask)
|
|
@@ -215,7 +248,6 @@ function PreviewLink(method, urlMask, urlMaskDittoed, autofocus) {
|
|
|
215
248
|
r('a', {
|
|
216
249
|
className: classNames(CSS.PreviewLink, isChosen && CSS.chosen),
|
|
217
250
|
href: urlMask,
|
|
218
|
-
autofocus,
|
|
219
251
|
onClick
|
|
220
252
|
}, ditto
|
|
221
253
|
? [r('span', { className: CSS.dittoDir }, ditto), tail]
|
|
@@ -247,42 +279,6 @@ function MockSelector(row) {
|
|
|
247
279
|
}
|
|
248
280
|
|
|
249
281
|
|
|
250
|
-
function ProxyToggler(method, urlMask, checked) {
|
|
251
|
-
return ClickDragToggler({
|
|
252
|
-
className: CSS.ProxyToggler,
|
|
253
|
-
title: t`Proxy Toggler`,
|
|
254
|
-
body: CloudIcon(),
|
|
255
|
-
checked,
|
|
256
|
-
commit(checked) {
|
|
257
|
-
store.setProxied(method, urlMask, checked)
|
|
258
|
-
}
|
|
259
|
-
})
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
function StatusCodeToggler({ title, body, commit, checked, disabled }) {
|
|
265
|
-
return ClickDragToggler({
|
|
266
|
-
title,
|
|
267
|
-
disabled,
|
|
268
|
-
className: CSS.StatusCodeToggler,
|
|
269
|
-
commit,
|
|
270
|
-
checked,
|
|
271
|
-
body
|
|
272
|
-
})
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function DelayToggler({ checked, commit, className }) {
|
|
276
|
-
return ClickDragToggler({
|
|
277
|
-
checked,
|
|
278
|
-
commit,
|
|
279
|
-
className: classNames(CSS.DelayToggler, className),
|
|
280
|
-
canClickDrag: true,
|
|
281
|
-
title: t`Delay`,
|
|
282
|
-
body: TimerIcon()
|
|
283
|
-
})
|
|
284
|
-
}
|
|
285
|
-
|
|
286
282
|
function ClickDragToggler({ checked, commit, className, title, body }) {
|
|
287
283
|
function onPointerEnter(event) {
|
|
288
284
|
if (event.buttons === 1)
|
|
@@ -305,7 +301,7 @@ function ClickDragToggler({ checked, commit, className, title, body }) {
|
|
|
305
301
|
return
|
|
306
302
|
|
|
307
303
|
// Uncheck all other in the column.
|
|
308
|
-
for (const elem of
|
|
304
|
+
for (const elem of LeftSide.$$(selector))
|
|
309
305
|
if (elem !== this && elem.checked && !elem.disabled) {
|
|
310
306
|
elem.checked = false
|
|
311
307
|
elem.dispatchEvent(new Event('change'))
|
|
@@ -488,7 +484,6 @@ function columnSelectors() {
|
|
|
488
484
|
]
|
|
489
485
|
}
|
|
490
486
|
|
|
491
|
-
|
|
492
487
|
function initKeyboardNavigation() {
|
|
493
488
|
const rowSelectors = [
|
|
494
489
|
...columnSelectors(),
|
|
@@ -503,7 +498,7 @@ function initKeyboardNavigation() {
|
|
|
503
498
|
const sel = selectorForColumnOf(pivot)
|
|
504
499
|
if (sel) {
|
|
505
500
|
const offset = key === 'ArrowDown' ? +1 : -1
|
|
506
|
-
const siblings =
|
|
501
|
+
const siblings = LeftSide.$$(sel)
|
|
507
502
|
circularAdjacent(offset, siblings, pivot).focus()
|
|
508
503
|
}
|
|
509
504
|
break
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function TrieNode() {
|
|
2
|
+
this.brokers = []
|
|
3
|
+
this.tnChildren = new Map()
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {Partial<BrokerRowModel>[]} brokers
|
|
8
|
+
* @returns {Partial<BrokerRowModel>[]}
|
|
9
|
+
*/
|
|
10
|
+
export function dirStructure(brokers) {
|
|
11
|
+
return dfs(trie(brokers))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function trie(brokers) {
|
|
15
|
+
const root = new TrieNode()
|
|
16
|
+
for (const b of brokers) {
|
|
17
|
+
let node = root
|
|
18
|
+
for (const seg of b.urlMask.split('/')) { // TODO it should ignore query string
|
|
19
|
+
const segNode = node.tnChildren.get(seg) || new TrieNode()
|
|
20
|
+
node.tnChildren.set(seg, segNode)
|
|
21
|
+
node = segNode
|
|
22
|
+
}
|
|
23
|
+
node.brokers.push(b)
|
|
24
|
+
}
|
|
25
|
+
return root
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** @param {TrieNode} node */
|
|
29
|
+
function dfs(node) {
|
|
30
|
+
const childBrokers = []
|
|
31
|
+
for (const tnc of node.tnChildren.values())
|
|
32
|
+
childBrokers.push(...dfs(tnc))
|
|
33
|
+
|
|
34
|
+
const brokers = node.brokers.length
|
|
35
|
+
? [node.brokers[0], ...childBrokers, ...node.brokers.slice(1)]
|
|
36
|
+
: childBrokers
|
|
37
|
+
|
|
38
|
+
if (!brokers.length)
|
|
39
|
+
return []
|
|
40
|
+
|
|
41
|
+
const [head, ...rest] = brokers
|
|
42
|
+
if (node.brokers.length || !head.children.length) {
|
|
43
|
+
head.children.push(...rest)
|
|
44
|
+
return [head]
|
|
45
|
+
}
|
|
46
|
+
return brokers
|
|
47
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import { deepEqual } from 'node:assert/strict'
|
|
3
|
+
import { dirStructure } from './dirStructure.js'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const input = [
|
|
7
|
+
{ children: [], method: 'GET', urlMask: '/api/user' },
|
|
8
|
+
{ children: [], method: 'GET', urlMask: '/api/user/avatar' },
|
|
9
|
+
{ children: [], method: 'GET', urlMask: '/api/video/[id]' },
|
|
10
|
+
{ children: [], method: 'GET', urlMask: '/index.html' },
|
|
11
|
+
{ children: [], method: 'GET', urlMask: '/media/file-a.txt' },
|
|
12
|
+
{ children: [], method: 'GET', urlMask: '/media/file-b.txt' },
|
|
13
|
+
{ children: [], method: 'GET', urlMask: '/media/sub/file-aa.txt' },
|
|
14
|
+
{ children: [], method: 'GET', urlMask: '/media/sub/file-bb.txt' },
|
|
15
|
+
{ children: [], method: 'POST', urlMask: '/api/user' },
|
|
16
|
+
{ children: [], method: 'POST', urlMask: '/api/user/avatar/foo' },
|
|
17
|
+
{ children: [], method: 'PATCH', urlMask: '/api/user' }
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
const expected = [
|
|
22
|
+
{
|
|
23
|
+
urlMask: '/api/user',
|
|
24
|
+
method: 'GET',
|
|
25
|
+
children: [
|
|
26
|
+
{
|
|
27
|
+
urlMask: '/api/user/avatar',
|
|
28
|
+
method: 'GET',
|
|
29
|
+
children: [
|
|
30
|
+
{
|
|
31
|
+
urlMask: '/api/user/avatar/foo',
|
|
32
|
+
method: 'POST',
|
|
33
|
+
children: []
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}, {
|
|
37
|
+
urlMask: '/api/user',
|
|
38
|
+
method: 'POST',
|
|
39
|
+
children: []
|
|
40
|
+
}, {
|
|
41
|
+
urlMask: '/api/user',
|
|
42
|
+
method: 'PATCH',
|
|
43
|
+
children: []
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
urlMask: '/api/video/[id]',
|
|
49
|
+
method: 'GET',
|
|
50
|
+
children: []
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
urlMask: '/index.html',
|
|
54
|
+
method: 'GET',
|
|
55
|
+
children: []
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
urlMask: '/media/file-a.txt',
|
|
59
|
+
method: 'GET',
|
|
60
|
+
children: [
|
|
61
|
+
{
|
|
62
|
+
urlMask: '/media/file-b.txt',
|
|
63
|
+
method: 'GET',
|
|
64
|
+
children: []
|
|
65
|
+
}, {
|
|
66
|
+
urlMask: '/media/sub/file-aa.txt',
|
|
67
|
+
method: 'GET',
|
|
68
|
+
children: [
|
|
69
|
+
{
|
|
70
|
+
urlMask: '/media/sub/file-bb.txt',
|
|
71
|
+
method: 'GET',
|
|
72
|
+
children: []
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
test('acceptance', () => deepEqual(dirStructure(input), expected))
|
|
81
|
+
|
package/src/client/graphics.js
CHANGED
|
@@ -1,35 +1,25 @@
|
|
|
1
1
|
import { createSvgElement as s } from './dom-utils.js'
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
s('
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
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' })))
|
|
10
|
-
}
|
|
4
|
+
export const Logo = () =>
|
|
5
|
+
s('svg', { viewBox: '0 0 556 100' },
|
|
6
|
+
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' }),
|
|
7
|
+
s('path', { opacity: 1, fill: 'currentColor', 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' }),
|
|
8
|
+
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' }))
|
|
11
9
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
s('
|
|
15
|
-
s('path', { d: 'm11 5.6 0.14 7.2 6 3.7' })))
|
|
16
|
-
}
|
|
10
|
+
export const TimerIcon = () =>
|
|
11
|
+
s('svg', { viewBox: '0 0 24 24' },
|
|
12
|
+
s('path', { d: 'm11 5.6 0.14 7.2 6 3.7' }))
|
|
17
13
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
s('
|
|
21
|
-
|
|
22
|
-
s('path', { d: 'm6.1 9.1c2.8 0 5 2.3 5 5' })))
|
|
23
|
-
}
|
|
14
|
+
export const CloudIcon = () =>
|
|
15
|
+
s('svg', { viewBox: '0 0 24 24' },
|
|
16
|
+
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' }),
|
|
17
|
+
s('path', { d: 'm6.1 9.1c2.8 0 5 2.3 5 5' }))
|
|
24
18
|
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
s('
|
|
28
|
-
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' })))
|
|
29
|
-
}
|
|
19
|
+
export const HelpIcon = () =>
|
|
20
|
+
s('svg', { viewBox: '0 0 24 24' },
|
|
21
|
+
s('path', { d: 'M11.07 12.85c.77-1.39 2.25-2.21 3.11-3.44.91-1.29.4-3.7-2.18-3.7-1.69 0-2.52 1.28-2.87 2.34L6.54 6.96C7.25 4.83 9.18 3 11.99 3c2.35 0 3.96 1.07 4.78 2.41.7 1.15 1.11 3.3.03 4.9-1.2 1.77-2.35 2.31-2.97 3.45-.25.46-.35.76-.35 2.24h-2.89c-.01-.78-.13-2.05.48-3.15M14 20c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2' }))
|
|
30
22
|
|
|
31
|
-
export
|
|
32
|
-
|
|
33
|
-
s('
|
|
34
|
-
s('path', { d: 'M6 9l6 6 6-6' })))
|
|
35
|
-
}
|
|
23
|
+
export const ChevronDownIcon = () =>
|
|
24
|
+
s('svg', { viewBox: '0 0 24 24', stroke: 'currentColor', fill: 'none', 'stroke-width': '2' },
|
|
25
|
+
s('path', { d: 'M6 9l6 6 6-6' }))
|
package/src/server/Mockaton.js
CHANGED
|
@@ -5,8 +5,8 @@ import pkgJSON from '../../package.json' with { type: 'json' }
|
|
|
5
5
|
|
|
6
6
|
import { logger } from './utils/logger.js'
|
|
7
7
|
import { ServerResponse } from './utils/HttpServerResponse.js'
|
|
8
|
-
import { IncomingMessage, BodyReaderError, hasControlChars } from './utils/HttpIncomingMessage.js'
|
|
9
8
|
import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
|
|
9
|
+
import { IncomingMessage, BodyReaderError, hasControlChars } from './utils/HttpIncomingMessage.js'
|
|
10
10
|
|
|
11
11
|
import { API } from '../client/ApiConstants.js'
|
|
12
12
|
|
package/src/server/Watcher.js
CHANGED
|
@@ -35,7 +35,7 @@ export function init() {
|
|
|
35
35
|
|
|
36
36
|
/** @returns {boolean} registered */
|
|
37
37
|
export function registerMock(file, isFromWatcher = false) {
|
|
38
|
-
if (brokerByFilename(file)?.hasMock(file) ||
|
|
38
|
+
if (brokerByFilename(file)?.hasMock(file) ||
|
|
39
39
|
!isFileAllowed(basename(file)))
|
|
40
40
|
return false
|
|
41
41
|
|