mockaton 12.3.0 → 12.3.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 CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "12.3.0",
5
+ "version": "12.3.1",
6
6
  "exports": {
7
7
  ".": {
8
8
  "import": "./index.js",
@@ -196,6 +196,7 @@ export const store = {
196
196
  const response = await api.toggle500(method, urlMask)
197
197
  if (!response.ok) throw response
198
198
  store.setBroker(await response.json())
199
+ store.setChosenLink(method, urlMask)
199
200
  store.renderRow(method, urlMask)
200
201
  }
201
202
  catch (error) { store.onError(error) }
@@ -206,6 +207,7 @@ export const store = {
206
207
  const response = await api.setRouteIsProxied(method, urlMask, checked)
207
208
  if (!response.ok) throw response
208
209
  store.setBroker(await response.json())
210
+ store.setChosenLink(method, urlMask)
209
211
  store.renderRow(method, urlMask)
210
212
  }
211
213
  catch (error) { store.onError(error) }
package/src/client/app.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createElement as r,
3
3
  createSvgElement as s,
4
- classNames, restoreFocus, Fragment, adoptCSS
4
+ className, restoreFocus, Fragment, adoptCSS
5
5
  } from './dom-utils.js'
6
6
 
7
7
  import { store } from './app-store.js'
@@ -23,9 +23,7 @@ initKeyboardNavigation()
23
23
 
24
24
  let mounted = false
25
25
  function render() {
26
- restoreFocus(() => {
27
- document.body.replaceChildren(...App())
28
- })
26
+ restoreFocus(() => document.body.replaceChildren(...App()))
29
27
  if (store.hasChosenLink)
30
28
  previewMock()
31
29
  mounted = true
@@ -43,10 +41,10 @@ function App() {
43
41
  style: { width: leftSideRef.width },
44
42
  className: CSS.leftSide
45
43
  },
46
- r('div', classNames(CSS.SubToolbar),
44
+ r('div', className(CSS.SubToolbar),
47
45
  GroupByMethod(),
48
46
  BulkSelector()),
49
- r('div', classNames(CSS.Table),
47
+ r('div', className(CSS.Table),
50
48
  MockList(),
51
49
  StaticFilesList())),
52
50
  r('div', { className: CSS.rightSide },
@@ -65,7 +63,7 @@ function Header() {
65
63
  },
66
64
  Logo()),
67
65
  r('div', null,
68
- r('div', classNames(CSS.GlobalDelayWrap),
66
+ r('div', className(CSS.GlobalDelayWrap),
69
67
  GlobalDelayField(),
70
68
  GlobalDelayJitterField()),
71
69
  CookieSelector(),
@@ -89,7 +87,7 @@ function GlobalDelayField() {
89
87
  onWheel.timer = setTimeout(onChange.bind(this), 300)
90
88
  }
91
89
  return (
92
- r('label', classNames(CSS.Field, CSS.GlobalDelayField),
90
+ r('label', className(CSS.Field, CSS.GlobalDelayField),
93
91
  r('span', null, t`Delay (ms)`),
94
92
  r('input', {
95
93
  type: 'number',
@@ -118,7 +116,7 @@ function GlobalDelayJitterField() {
118
116
  onWheel.timer = setTimeout(onChange.bind(this), 300)
119
117
  }
120
118
  return (
121
- r('label', classNames(CSS.Field, CSS.GlobalDelayJitterField),
119
+ r('label', className(CSS.Field, CSS.GlobalDelayJitterField),
122
120
  r('span', null, t`Max Jitter %`),
123
121
  r('input', {
124
122
  type: 'number',
@@ -138,7 +136,7 @@ function CookieSelector() {
138
136
  const disabled = cookies.length <= 1
139
137
  const list = cookies.length ? cookies : [[t`None`, true]]
140
138
  return (
141
- r('label', classNames(CSS.Field, CSS.CookieSelector),
139
+ r('label', className(CSS.Field, CSS.CookieSelector),
142
140
  r('span', null, t`Cookie`),
143
141
  r('select', {
144
142
  autocomplete: 'off',
@@ -162,7 +160,7 @@ function ProxyFallbackField() {
162
160
  store.setProxyFallback(this.value.trim())
163
161
  }
164
162
  return (
165
- r('div', classNames(CSS.Field, CSS.FallbackBackend),
163
+ r('div', className(CSS.Field, CSS.FallbackBackend),
166
164
  r('label', null,
167
165
  r('span', null, t`Fallback`),
168
166
  r('input', {
@@ -177,7 +175,7 @@ function ProxyFallbackField() {
177
175
 
178
176
  function SaveProxiedCheckbox(ref) {
179
177
  return (
180
- r('label', classNames(CSS.SaveProxiedCheckbox),
178
+ r('label', className(CSS.SaveProxiedCheckbox),
181
179
  r('input', {
182
180
  ref,
183
181
  type: 'checkbox',
@@ -185,7 +183,7 @@ function SaveProxiedCheckbox(ref) {
185
183
  checked: store.collectProxied,
186
184
  onChange() { store.setCollectProxied(this.checked) }
187
185
  }),
188
- r('span', classNames(CSS.checkboxBody), t`Save Mocks`)))
186
+ r('span', className(CSS.checkboxBody), t`Save Mocks`)))
189
187
  }
190
188
 
191
189
 
@@ -222,7 +220,7 @@ function BulkSelector() {
222
220
  }
223
221
  const disabled = !comments.length
224
222
  return (
225
- r('label', classNames(CSS.BulkSelector),
223
+ r('label', className(CSS.BulkSelector),
226
224
  r('span', null, t`Bulk Select`),
227
225
  r('select', {
228
226
  autocomplete: 'off',
@@ -241,13 +239,13 @@ function BulkSelector() {
241
239
 
242
240
  function GroupByMethod() {
243
241
  return (
244
- r('label', classNames(CSS.GroupByMethod),
242
+ r('label', className(CSS.GroupByMethod),
245
243
  r('input', {
246
244
  type: 'checkbox',
247
245
  checked: store.groupByMethod,
248
246
  onChange: store.toggleGroupByMethod
249
247
  }),
250
- r('span', classNames(CSS.checkboxBody), t`Group by Method`)))
248
+ r('span', className(CSS.checkboxBody), t`Group by Method`)))
251
249
  }
252
250
 
253
251
 
@@ -258,10 +256,10 @@ function MockList() {
258
256
  return r('div', null, t`No mocks found`)
259
257
 
260
258
  if (store.groupByMethod)
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)))
259
+ return Object.keys(store.brokersByMethod).map(method => Fragment(
260
+ r('div', className(CSS.TableHeading, store.canProxy && CSS.canProxy),
261
+ method),
262
+ store.brokersAsRowsByMethod(method).map(Row)))
265
263
 
266
264
  return store.brokersAsRowsByMethod('*').map(Row)
267
265
  }
@@ -275,29 +273,27 @@ function Row(row, i) {
275
273
  return (
276
274
  r('div', {
277
275
  key: row.key,
278
- ...classNames(CSS.TableRow,
276
+ ...className(CSS.TableRow,
279
277
  mounted && row.isNew && CSS.animIn)
280
278
  },
281
279
  store.canProxy && ProxyToggler(method, urlMask, row.proxied),
282
280
 
283
281
  DelayToggler({
284
282
  checked: row.delayed,
285
- commit(checked) {
286
- store.setDelayed(method, urlMask, checked)
287
- },
283
+ commit(checked) { store.setDelayed(method, urlMask, checked) },
288
284
  }),
289
285
 
290
286
  StatusCodeToggler({
291
287
  title: t`Internal Server Error`,
292
- body: t`500`,
288
+ label: t`500`,
293
289
  disabled: row.opts.length === 1 && row.status === 500,
294
290
  checked: !row.proxied && row.status === 500,
295
- commit() {
291
+ onChange() {
296
292
  store.toggle500(method, urlMask)
297
293
  }
298
294
  }),
299
295
 
300
- !store.groupByMethod && r('span', classNames(CSS.Method), method),
296
+ !store.groupByMethod && r('span', className(CSS.Method), method),
301
297
 
302
298
  PreviewLink(method, urlMask, row.urlMaskDittoed, i === 0),
303
299
 
@@ -305,58 +301,38 @@ function Row(row, i) {
305
301
  }
306
302
 
307
303
  function renderRow(method, urlMask) {
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
- }
304
+ restoreFocus(() => {
305
+ unChooseOld()
306
+ const row = store.brokerAsRow(method, urlMask)
307
+ trFor(row.key).replaceWith(Row(row))
308
+ previewMock()
309
+ })
310
+
311
+ function trFor(key) {
312
+ return leftSideRef.elem.querySelector(`.${CSS.TableRow}[key="${key}"]`)
313
+ }
314
+ function unChooseOld() {
315
+ return leftSideRef.elem.querySelector(`a.${CSS.chosen}`)
316
+ ?.classList.remove(CSS.chosen)
333
317
  }
334
318
  }
335
319
 
336
320
 
337
-
338
321
  function PreviewLink(method, urlMask, urlMaskDittoed, autofocus) {
339
322
  function onClick(event) {
340
- unChooseOld()
341
323
  event.preventDefault()
342
324
  store.previewLink(method, urlMask)
343
- previewMock()
344
325
  }
345
- function unChooseOld() {
346
- return leftSideRef.elem.querySelector(`a.${CSS.chosen}`)
347
- ?.classList.remove(CSS.chosen)
348
- }
349
-
350
326
  const isChosen = store.chosenLink.method === method && store.chosenLink.urlMask === urlMask
351
327
  const [ditto, tail] = urlMaskDittoed
352
328
  return (
353
329
  r('a', {
354
- ...classNames(CSS.PreviewLink, isChosen && CSS.chosen),
330
+ ...className(CSS.PreviewLink, isChosen && CSS.chosen),
355
331
  href: urlMask,
356
332
  autofocus,
357
333
  onClick
358
334
  }, ditto
359
- ? [r('span', classNames(CSS.dittoDir), ditto), tail]
335
+ ? [r('span', className(CSS.dittoDir), ditto), tail]
360
336
  : tail))
361
337
  }
362
338
 
@@ -376,7 +352,7 @@ function MockSelector(row) {
376
352
  },
377
353
  'aria-label': t`Mock Selector`,
378
354
  disabled: row.opts.length < 2,
379
- ...classNames(
355
+ ...className(
380
356
  CSS.MockSelector,
381
357
  row.selectedIdx > 0 && CSS.nonDefault,
382
358
  row.selectedFileIs4xx && CSS.status4xx)
@@ -386,15 +362,17 @@ function MockSelector(row) {
386
362
 
387
363
 
388
364
  function ProxyToggler(method, urlMask, checked) {
389
- return ClickDragToggler({
390
- className: CSS.ProxyToggler,
391
- title: t`Proxy Toggler`,
392
- checked,
393
- commit(checked) {
394
- store.setProxied(method, urlMask, checked)
395
- },
396
- body: CloudIcon()
397
- })
365
+ return (
366
+ r('label', {
367
+ className: CSS.ProxyToggler,
368
+ title: t`Proxy Toggler`
369
+ },
370
+ r('input', {
371
+ type: 'checkbox',
372
+ checked,
373
+ onChange() { store.setProxied(method, urlMask, this.checked) },
374
+ }),
375
+ CloudIcon()))
398
376
  }
399
377
 
400
378
 
@@ -407,7 +385,7 @@ function StaticFilesList() {
407
385
  ? null
408
386
  : Fragment(
409
387
  r('div',
410
- classNames(CSS.TableHeading,
388
+ className(CSS.TableHeading,
411
389
  store.canProxy && CSS.canProxy,
412
390
  !store.groupByMethod && CSS.nonGroupedByMethod),
413
391
  store.groupByMethod
@@ -423,7 +401,7 @@ function StaticRow(row) {
423
401
  return (
424
402
  r('div', {
425
403
  key: row.key,
426
- ...classNames(CSS.TableRow,
404
+ ...className(CSS.TableRow,
427
405
  mounted && row.isNew && CSS.animIn)
428
406
  },
429
407
 
@@ -437,48 +415,42 @@ function StaticRow(row) {
437
415
 
438
416
  StatusCodeToggler({
439
417
  title: t`Not Found`,
440
- body: t`404`,
418
+ label: t`404`,
441
419
  checked: row.status === 404,
442
- commit(checked) {
443
- store.setStaticRouteStatus(row.urlMask, checked
420
+ onChange() {
421
+ store.setStaticRouteStatus(row.urlMask, this.checked
444
422
  ? 404
445
423
  : 200)
446
424
  }
447
425
  }),
448
426
 
449
- !groupByMethod && r('span', classNames(CSS.Method), 'GET'),
427
+ !groupByMethod && r('span', className(CSS.Method), 'GET'),
450
428
 
451
429
  r('a', {
452
430
  href: row.urlMask,
453
431
  target: '_blank',
454
432
  className: CSS.PreviewLink,
455
433
  }, ditto
456
- ? [r('span', classNames(CSS.dittoDir), ditto), tail]
434
+ ? [r('span', className(CSS.dittoDir), ditto), tail]
457
435
  : tail)))
458
436
  }
459
437
 
460
- function StatusCodeToggler({ title, body, commit, checked, disabled }) {
461
- return ClickDragToggler({
462
- title,
463
- disabled,
464
- className: CSS.StatusCodeToggler,
465
- commit,
466
- checked,
467
- body
468
- })
438
+ function StatusCodeToggler({ title, label, onChange, checked }) {
439
+ return (
440
+ r('label', {
441
+ title,
442
+ className: CSS.StatusCodeToggler
443
+ },
444
+ r('input', {
445
+ type: 'checkbox',
446
+ checked,
447
+ onChange
448
+ }),
449
+ r('span', className(CSS.checkboxBody), label)))
469
450
  }
470
451
 
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
- })
479
- }
480
452
 
481
- function ClickDragToggler({ checked, commit, className, title, body }) {
453
+ function DelayToggler({ checked, commit, optClassName }) {
482
454
  function onPointerEnter(event) {
483
455
  if (event.buttons === 1)
484
456
  onPointerDown.call(this)
@@ -496,7 +468,10 @@ function ClickDragToggler({ checked, commit, className, title, body }) {
496
468
  commit(this.checked)
497
469
  }
498
470
  return (
499
- r('label', { ...classNames(CSS.Toggler, className), title },
471
+ r('label', {
472
+ ...className(CSS.DelayToggler, optClassName),
473
+ title: t`Delay`
474
+ },
500
475
  r('input', {
501
476
  type: 'checkbox',
502
477
  checked,
@@ -505,7 +480,7 @@ function ClickDragToggler({ checked, commit, className, title, body }) {
505
480
  onClick,
506
481
  onChange
507
482
  }),
508
- r('span', classNames(CSS.checkboxBody), body)))
483
+ TimerIcon()))
509
484
  }
510
485
 
511
486
  function Resizer(ref) {
@@ -560,7 +535,7 @@ const payloadViewerCodeRef = {}
560
535
 
561
536
  function PayloadViewer() {
562
537
  return (
563
- r('div', classNames(CSS.PayloadViewer),
538
+ r('div', className(CSS.PayloadViewer),
564
539
  RightToolbar(),
565
540
  r('pre', null,
566
541
  r('code', { ref: payloadViewerCodeRef },
@@ -569,7 +544,7 @@ function PayloadViewer() {
569
544
 
570
545
  function RightToolbar() {
571
546
  return (
572
- r('div', classNames(CSS.SubToolbar),
547
+ r('div', className(CSS.SubToolbar),
573
548
  r('h2', { ref: payloadViewerTitleRef },
574
549
  !store.hasChosenLink && t`Preview`)))
575
550
  }
@@ -600,7 +575,7 @@ function PayloadViewerTitleWhenProxied(response) {
600
575
  const SPINNER_DELAY = 80
601
576
  function PayloadViewerProgressBar() {
602
577
  return (
603
- r('div', classNames(CSS.ProgressBar),
578
+ r('div', className(CSS.ProgressBar),
604
579
  r('div', {
605
580
  style: {
606
581
  animationDuration: store.delay - SPINNER_DELAY + 'ms'
@@ -1,4 +1,4 @@
1
- export function classNames(...args) {
1
+ export function className(...args) {
2
2
  return {
3
3
  className: args.filter(Boolean).join(' ')
4
4
  }
@@ -400,7 +400,7 @@ main {
400
400
  padding-bottom: 4px;
401
401
  padding-left: 1px;
402
402
  border-top: 24px solid transparent;
403
- margin-left: 71px;
403
+ margin-left: 74px;
404
404
  font-weight: bold;
405
405
  text-align: left;
406
406
 
@@ -409,7 +409,7 @@ main {
409
409
  }
410
410
 
411
411
  &.canProxy {
412
- margin-left: 100px;
412
+ margin-left: 110px;
413
413
  }
414
414
  &.nonGroupedByMethod {
415
415
  margin-left: 122px;
@@ -505,11 +505,11 @@ main {
505
505
  }
506
506
  }
507
507
 
508
-
509
- .Toggler {
508
+ .DelayToggler,
509
+ .ProxyToggler {
510
510
  display: flex;
511
511
 
512
- input {
512
+ > input {
513
513
  /* For click drag target */
514
514
  position: absolute;
515
515
  width: 22px;
@@ -518,85 +518,136 @@ main {
518
518
 
519
519
  &:focus-visible {
520
520
  outline: 0;
521
- & + .checkboxBody {
521
+ & + svg {
522
522
  outline: 2px solid var(--colorAccent)
523
523
  }
524
524
  }
525
+ }
525
526
 
526
- &:disabled + .checkboxBody {
527
- cursor: not-allowed;
528
- opacity: 0.7;
529
- }
527
+ > svg {
528
+ fill: none;
529
+ stroke: var(--colorSecondaryAction);
530
+ }
531
+ }
530
532
 
531
- &:checked + .checkboxBody {
533
+ .DelayToggler {
534
+ > input {
535
+ &:checked + svg {
532
536
  border-color: var(--colorAccent);
533
537
  fill: var(--colorAccent);
534
538
  background: var(--colorAccent);
535
539
  stroke: var(--colorBackground);
536
540
  }
537
541
 
538
- &:enabled:hover:not(:checked) + .checkboxBody {
542
+ &:enabled:hover:not(:checked) + svg {
539
543
  border-color: var(--colorHover);
540
544
  background: var(--colorHover);
541
545
  stroke: var(--colorText);
542
546
  }
543
547
  }
544
548
 
545
- .checkboxBody {
546
- display: flex;
549
+ > svg {
547
550
  width: 22px;
548
551
  height: 22px;
549
- align-items: center;
550
- justify-content: center;
551
552
  border: 1px solid var(--colorSecondaryActionBorder);
552
- fill: none;
553
- stroke: var(--colorSecondaryAction);
554
553
  stroke-width: 2.5px;
555
554
  border-radius: 50%;
556
555
  }
557
556
 
558
557
  &.canProxy {
559
- margin-left: 30px;
558
+ margin-left: 36px;
560
559
  }
561
560
  }
562
561
 
563
- .DelayToggler {
564
- }
565
-
566
562
  .ProxyToggler {
563
+ border: 1px solid var(--colorSecondaryActionBorder);
567
564
  margin-right: 8px;
565
+ border-radius: var(--radius);
568
566
 
569
- .checkboxBody {
570
- svg {
571
- width: 16px;
567
+ &:has(input:checked),
568
+ &:has(input:disabled) {
569
+ background: transparent;
570
+ box-shadow: none;
571
+ }
572
+
573
+ > input {
574
+ &:checked + svg {
575
+ fill: var(--colorAccent);
576
+ stroke: var(--colorAccent);
577
+
578
+ path:last-of-type { /* inner cloud curve */
579
+ stroke: var(--colorBackground);
580
+ }
581
+ transform: scale(1.1);
572
582
  }
583
+
584
+ &:enabled:hover:not(:checked) + svg {
585
+ fill: var(--colorHover);
586
+ stroke: var(--colorText);
587
+ }
588
+
589
+ &:disabled + svg {
590
+ stroke-opacity: 0.4;
591
+ cursor: not-allowed;
592
+ box-shadow: none;
593
+ fill: transparent;
594
+
595
+ path:last-of-type {
596
+ stroke-opacity: 0;
597
+ stroke: transparent;
598
+ }
599
+ }
600
+ }
601
+
602
+ > svg {
603
+ width: 26px;
604
+ padding: 1px 4px;
605
+ stroke-width: 2px;
606
+ border-radius: var(--radius);
573
607
  }
574
608
  }
575
609
 
576
610
  .StatusCodeToggler {
611
+ display: flex;
577
612
  margin-right: 10px;
578
613
  margin-left: 8px;
614
+ cursor: pointer;
579
615
 
580
- input {
581
- width: 26px;
616
+ > input {
617
+ appearance: none;
582
618
 
583
- &:not(:checked):enabled:hover + .checkboxBody {
584
- border-color: var(--colorRed);
585
- color: var(--colorRed);
619
+ &:focus-visible {
620
+ outline: 0;
621
+ & + .checkboxBody {
622
+ outline: 2px solid var(--colorAccent)
623
+ }
624
+ }
625
+
626
+ &:disabled + .checkboxBody {
627
+ cursor: not-allowed;
628
+ opacity: 0.7;
586
629
  }
587
630
  &:checked + .checkboxBody {
588
631
  border-color: var(--colorRed);
589
632
  color: white;
590
633
  background: var(--colorRed);
591
634
  }
635
+ &:not(:checked):enabled:hover + .checkboxBody {
636
+ border-color: var(--colorRed);
637
+ color: var(--colorRed);
638
+ }
639
+ &:enabled:active + .checkboxBody {
640
+ cursor: grabbing;
641
+ }
592
642
  }
593
643
 
594
- .checkboxBody {
595
- width: 27px;
644
+ > .checkboxBody {
596
645
  padding: 4px;
646
+ border: 1px solid var(--colorSecondaryActionBorder);
597
647
  font-size: 10px;
598
648
  font-weight: bold;
599
649
  color: var(--colorSecondaryAction);
650
+ border-radius: var(--radius);
600
651
  }
601
652
  }
602
653