mockaton 10.6.1 → 10.6.2

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": "10.6.1",
5
+ "version": "10.6.2",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "start": "node src/cli.js",
24
24
  "watch": "node --watch src/cli.js",
25
25
  "pixaton": "node --test --import=./pixaton-tests/_setup.js --experimental-test-isolation=none 'pixaton-tests/**/*.test.js'",
26
- "outdated": "npm outdated --parseable | awk -F: '{ printf \"npm i %-30s ;# %s\\n\", $4, $2 }'"
26
+ "outdated": "npm outdated --parseable | awk -F: '{ printf \"npm i %s ;# %s\\n\", $4, $2 }'"
27
27
  },
28
28
  "devDependencies": {
29
29
  "pixaton": "1.1.3",
package/src/Dashboard.js CHANGED
@@ -69,6 +69,8 @@ const state = /** @type {State} */ {
69
69
  return Boolean(state.proxyFallback)
70
70
  },
71
71
 
72
+ leftSideWidth: window.innerWidth / 2,
73
+
72
74
  groupByMethod: initPreference('groupByMethod'),
73
75
  toggleGroupByMethod() {
74
76
  state.groupByMethod = !state.groupByMethod
@@ -76,10 +78,7 @@ const state = /** @type {State} */ {
76
78
  updateState()
77
79
  },
78
80
 
79
- leftSideWidth: window.innerWidth / 2,
80
-
81
81
  chosenLink: { method: '', urlMask: '' },
82
- clearChosenLink() { state.setChosenLink('', '') },
83
82
  setChosenLink(method, urlMask) {
84
83
  state.chosenLink = { method, urlMask }
85
84
  }
@@ -88,7 +87,7 @@ const state = /** @type {State} */ {
88
87
 
89
88
  const mockaton = new Commander(location.origin)
90
89
  updateState()
91
- initLongPoll()
90
+ initRealTimeUpdates()
92
91
  initKeyboardNavigation()
93
92
 
94
93
  async function updateState() {
@@ -96,13 +95,18 @@ async function updateState() {
96
95
  const response = await mockaton.getState()
97
96
  if (!response.ok)
98
97
  throw response.status
98
+
99
99
  Object.assign(state, await response.json())
100
+
101
+ const focusedElem = selectorFor(document.activeElement)
100
102
  document.body.replaceChildren(...App())
103
+ if (focusedElem)
104
+ document.querySelector(focusedElem)?.focus()
101
105
 
102
- findChosenLink()?.focus()
103
106
  const { method, urlMask } = state.chosenLink
104
107
  if (method && urlMask)
105
108
  await previewMock(method, urlMask)
109
+
106
110
  }
107
111
  catch (error) {
108
112
  onError(error)
@@ -167,7 +171,7 @@ function SettingsMenuTrigger() {
167
171
 
168
172
  function SettingsMenu(id) {
169
173
  const { groupByMethod, toggleGroupByMethod } = state
170
-
174
+
171
175
  const firstInputRef = useRef()
172
176
  function onToggle(event) {
173
177
  if (event.newState === 'open')
@@ -225,13 +229,11 @@ function BulkSelector() {
225
229
  // But this way is easier to implement, with a few hacks.
226
230
  const firstOption = t`Pick Comment…`
227
231
  function onChange() {
228
- state.clearChosenLink()
229
232
  const value = this.value
230
233
  this.value = firstOption // Hack
231
234
  mockaton.bulkSelectByComment(value)
232
235
  .then(parseError)
233
236
  .then(updateState)
234
- .then(() => focus(`.${CSS.BulkSelector}`))
235
237
  .catch(onError)
236
238
  }
237
239
  const disabled = !comments.length
@@ -296,7 +298,6 @@ function ProxyFallbackField() {
296
298
  mockaton.setProxyFallback(this.value.trim())
297
299
  .then(parseError)
298
300
  .then(updateState)
299
- .then(() => focus(`.${CSS.FallbackBackend} input`))
300
301
  .catch(onError)
301
302
  }
302
303
  return (
@@ -334,11 +335,9 @@ function SaveProxiedCheckbox(ref) {
334
335
 
335
336
  function ResetButton() {
336
337
  function onClick() {
337
- state.clearChosenLink()
338
338
  mockaton.reset()
339
339
  .then(parseError)
340
340
  .then(updateState)
341
- .then(() => focus(`.${CSS.ResetButton}`))
342
341
  .catch(onError)
343
342
  }
344
343
  return (
@@ -374,7 +373,7 @@ function MockList() {
374
373
  function Row({ method, urlMask, urlMaskDittoed, broker }, i) {
375
374
  const { canProxy, groupByMethod } = state
376
375
  return (
377
- r('tr', { 'data-method': method, 'data-urlMask': urlMask },
376
+ r('tr', { key: method + '::' + urlMask },
378
377
  canProxy && r('td', null, ProxyToggler(broker)),
379
378
  r('td', null, DelayRouteToggler(broker)),
380
379
  r('td', null, InternalServerErrorToggler(broker)),
@@ -627,6 +626,7 @@ function ClickDragToggler({ checked, commit, focusGroup }) {
627
626
  }
628
627
  function onPointerDown() {
629
628
  this.checked = !this.checked
629
+ this.focus()
630
630
  commit(this.checked)
631
631
  }
632
632
  function onClick(event) {
@@ -655,38 +655,48 @@ function ClickDragToggler({ checked, commit, focusGroup }) {
655
655
 
656
656
 
657
657
  function Resizer() {
658
+ let raf = 0
659
+ let initialX = 0
660
+ let panelWidth = 0
661
+
662
+ function onPointerDown(event) {
663
+ initialX = event.clientX
664
+ panelWidth = leftSideRef.current.clientWidth
665
+ addEventListener('pointerup', onUp, { once: true })
666
+ addEventListener('pointermove', onMove)
667
+ Object.assign(document.body.style, {
668
+ cursor: 'col-resize',
669
+ userSelect: 'none',
670
+ pointerEvents: 'none'
671
+ })
672
+ }
673
+
674
+ function onMove(event) {
675
+ const MIN_LEFT_WIDTH = 380
676
+ raf = raf || requestAnimationFrame(() => {
677
+ state.leftSideWidth = Math.max(panelWidth - (initialX - event.clientX), MIN_LEFT_WIDTH)
678
+ leftSideRef.current.style.width = state.leftSideWidth + 'px'
679
+ raf = 0
680
+ })
681
+ }
682
+
683
+ function onUp() {
684
+ removeEventListener('pointermove', onMove)
685
+ cancelAnimationFrame(raf)
686
+ raf = 0
687
+ Object.assign(document.body.style, {
688
+ cursor: 'auto',
689
+ userSelect: 'auto',
690
+ pointerEvents: 'auto'
691
+ })
692
+ }
693
+
658
694
  return (
659
695
  r('div', {
660
696
  className: CSS.Resizer,
661
- onPointerDown: Resizer.onPointerDown
697
+ onPointerDown
662
698
  }))
663
699
  }
664
- Resizer.raf = 0
665
- Resizer.initialX = 0
666
- Resizer.panelWidth = 0
667
- Resizer.onPointerDown = function (event) {
668
- Resizer.initialX = event.clientX
669
- Resizer.panelWidth = leftSideRef.current.clientWidth
670
- addEventListener('pointerup', Resizer.onUp, { once: true })
671
- addEventListener('pointermove', Resizer.onMove)
672
- document.body.style.userSelect = 'none'
673
- document.body.style.cursor = 'col-resize'
674
- }
675
- Resizer.onMove = function (event) {
676
- const MIN_LEFT_WIDTH = 380
677
- Resizer.raf = Resizer.raf || requestAnimationFrame(() => {
678
- state.leftSideWidth = Math.max(Resizer.panelWidth - (Resizer.initialX - event.clientX), MIN_LEFT_WIDTH)
679
- leftSideRef.current.style.width = state.leftSideWidth + 'px'
680
- Resizer.raf = 0
681
- })
682
- }
683
- Resizer.onUp = function () {
684
- removeEventListener('pointermove', Resizer.onMove)
685
- cancelAnimationFrame(Resizer.raf)
686
- Resizer.raf = 0
687
- document.body.style.userSelect = 'auto'
688
- document.body.style.cursor = 'auto'
689
- }
690
700
 
691
701
 
692
702
  /** # Payload Preview */
@@ -758,7 +768,9 @@ async function previewMock(method, urlMask) {
758
768
  async function updatePayloadViewer(method, urlMask, response) {
759
769
  const mime = response.headers.get('content-type') || ''
760
770
 
761
- const file = mockSelectorFor(method, urlMask).value
771
+ const file = mockSelectorFor(method, urlMask)?.value
772
+ if (!file)
773
+ return // e.g. selected was deleted
762
774
  if (file === STR_PROXIED)
763
775
  payloadViewerTitleRef.current.replaceChildren(PayloadViewerTitleWhenProxied({
764
776
  status: response.status,
@@ -796,15 +808,10 @@ function isXML(mime) {
796
808
 
797
809
 
798
810
  function mockSelectorFor(method, urlMask) {
799
- return trFor(method, urlMask)?.querySelector(`select.${CSS.MockSelector}`)
800
- }
801
- function trFor(method, urlMask) {
802
- return document.querySelector(`tr[data-method="${method}"][data-urlMask="${urlMask}"]`)
811
+ const tr = document.querySelector(`tr[key="${method}::${urlMask}"]`)
812
+ return tr?.querySelector(`.${CSS.MockSelector}`)
803
813
  }
804
814
 
805
- function focus(selector) {
806
- document.querySelector(selector)?.focus()
807
- }
808
815
 
809
816
  function initKeyboardNavigation() {
810
817
  addEventListener('keydown', onKeyDown)
@@ -825,7 +832,7 @@ function initKeyboardNavigation() {
825
832
  case 'ArrowLeft': {
826
833
  if (pivot.hasAttribute('data-focus-group') || pivot.classList.contains(CSS.MockSelector)) {
827
834
  const offset = event.key === 'ArrowRight' ? +1 : -1
828
- rowFocusable(pivot, offset)?.focus()
835
+ rowFocusable(pivot, offset).focus()
829
836
  }
830
837
  break
831
838
  }
@@ -850,7 +857,7 @@ function initKeyboardNavigation() {
850
857
  }
851
858
  }
852
859
 
853
- /** # Misc */
860
+ /** # Error */
854
861
 
855
862
  async function parseError(response) {
856
863
  if (response.ok)
@@ -882,6 +889,9 @@ function showErrorToast(msg) {
882
889
  }, msg))
883
890
  }
884
891
 
892
+
893
+ /** # Icons */
894
+
885
895
  function TimerIcon() {
886
896
  return (
887
897
  s('svg', { viewBox: '0 0 24 24' },
@@ -902,43 +912,43 @@ function SettingsIcon() {
902
912
  }
903
913
 
904
914
  /**
905
- * # Poll UI Sync Version
906
- * The version increments when a mock file is added or removed
915
+ * # Long polls UI sync version
916
+ * The version increments when a mock file is added, removed, or renamed.
907
917
  */
918
+ function initRealTimeUpdates() {
919
+ let oldSyncVersion = -1
920
+ let controller = new AbortController()
908
921
 
909
- function initLongPoll() {
910
- poll.oldSyncVersion = -1
911
- poll.controller = new AbortController()
912
922
  poll()
913
923
  document.addEventListener('visibilitychange', () => {
914
924
  if (document.hidden) {
915
- poll.controller.abort('_hidden_tab_')
916
- poll.controller = new AbortController()
925
+ controller.abort('_hidden_tab_')
926
+ controller = new AbortController()
917
927
  }
918
928
  else
919
929
  poll()
920
930
  })
921
- }
922
931
 
923
- async function poll() {
924
- try {
925
- const response = await mockaton.getSyncVersion(poll.oldSyncVersion, poll.controller.signal)
926
- if (response.ok) {
927
- const syncVersion = await response.json()
928
- const skipUpdate = poll.oldSyncVersion === -1
929
- if (poll.oldSyncVersion !== syncVersion) { // because it could be < or >
930
- poll.oldSyncVersion = syncVersion
931
- if (!skipUpdate)
932
- await updateState()
932
+ async function poll() {
933
+ try {
934
+ const response = await mockaton.getSyncVersion(oldSyncVersion, controller.signal)
935
+ if (response.ok) {
936
+ const syncVersion = await response.json()
937
+ const skipUpdate = oldSyncVersion === -1
938
+ if (oldSyncVersion !== syncVersion) { // because it could be < or >
939
+ oldSyncVersion = syncVersion
940
+ if (!skipUpdate)
941
+ await updateState()
942
+ }
943
+ poll()
933
944
  }
934
- poll()
945
+ else
946
+ throw response.status
947
+ }
948
+ catch (error) {
949
+ if (error !== '_hidden_tab_')
950
+ setTimeout(poll, 3000)
935
951
  }
936
- else
937
- throw response.status
938
- }
939
- catch (error) {
940
- if (error !== '_hidden_tab_')
941
- setTimeout(poll, 3000)
942
952
  }
943
953
  }
944
954
 
@@ -951,7 +961,6 @@ function className(...args) {
951
961
  }
952
962
  }
953
963
 
954
-
955
964
  function createElement(tag, props, ...children) {
956
965
  const node = document.createElement(tag)
957
966
  for (const [k, v] of Object.entries(props || {}))
@@ -998,6 +1007,30 @@ function deferred(cb) {
998
1007
  : setTimeout(cb, 100) // Safari
999
1008
  }
1000
1009
 
1010
+ function selectorFor(elem) {
1011
+ if (!(elem instanceof Element))
1012
+ return
1013
+
1014
+ const path = []
1015
+ while (elem) {
1016
+ let mod = ''
1017
+ if (elem.hasAttribute('key'))
1018
+ mod = `[key="${elem.getAttribute('key')}"]`
1019
+ else {
1020
+ let i = 0
1021
+ let sib = elem
1022
+ while ((sib = sib.previousElementSibling))
1023
+ if (sib.nodeName === elem.nodeName)
1024
+ i++
1025
+ if (i)
1026
+ mod = `:nth-of-type(${i + 1})`
1027
+ }
1028
+ path.push(elem.nodeName + mod)
1029
+ elem = elem.parentElement
1030
+ }
1031
+ return path.reverse().join('>')
1032
+ }
1033
+
1001
1034
 
1002
1035
  // When false, the URL will be updated with param=false
1003
1036
  function initPreference(param) {
@@ -9,23 +9,23 @@ export const CSP = [
9
9
  export const DashboardHtml = `<!DOCTYPE html>
10
10
  <html lang="en-US">
11
11
  <head>
12
- <meta charset="UTF-8">
13
- <base href="${API.dashboard}/">
14
-
15
- <link rel="stylesheet" href="Dashboard.css">
16
- <script type="module" src="Dashboard.js"></script>
17
-
12
+ <meta charset="UTF-8">
13
+ <base href="${API.dashboard}/">
14
+
15
+ <link rel="stylesheet" href="Dashboard.css">
16
+ <script type="module" src="Dashboard.js"></script>
17
+
18
18
  <link rel="preload" href="${API.state}" as="fetch" crossorigin>
19
19
 
20
20
  <link rel="modulepreload" href="ApiConstants.js">
21
21
  <link rel="modulepreload" href="ApiCommander.js">
22
22
  <link rel="modulepreload" href="Filename.js">
23
23
  <link rel="preload" href="Logo.svg" as="image">
24
-
25
- <link rel="icon" href="data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m235 33.7v202c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-151c-0.115-4.49-6.72-5.88-8.46-0.87l-48.3 155c-2.22 7.01-7.72 10.1-16 9.9-3.63-0.191-7.01-1.14-9.66-2.89-2.89-1.72-4.83-4.34-5.57-7.72-11.1-37-22.6-74.3-34.1-111-4.34-14-8.95-31.4-14-48.3-1.82-4.83-8.16-5.32-8.46 1.16v156c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-207c0-5.74 2.62-13.2 9.39-16.3 7.5-3.14 15-4.05 21.8-3.8 3.14 0 6.03 0.686 8.95 1.46 3.14 0.797 6.03 1.98 8.7 3.63 2.65 1.38 5.32 3.14 7.5 5.57 2.22 2.22 3.87 4.83 5.07 7.72l45.8 157c4.63-15.9 32.4-117 33.3-121 4.12-13.8 7.72-26.5 10.9-38.7 1.16-2.65 2.89-5.32 5.07-7.5 2.15-2.15 4.58-4.12 7.5-5.32 2.65-1.57 5.57-2.89 8.46-3.63 3.14-0.797 9.44-0.988 12.1-0.988 11.6 1.07 29.4 9.14 29.4 27z' fill='%23808080'/%3E%3C/svg%3E">
26
- <meta name="viewport" content="width=device-width, initial-scale=1">
27
- <meta name="description" content="HTTP Mock Server">
28
- <title>Mockaton</title>
24
+
25
+ <link rel="icon" href="data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m235 33.7v202c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-151c-0.115-4.49-6.72-5.88-8.46-0.87l-48.3 155c-2.22 7.01-7.72 10.1-16 9.9-3.63-0.191-7.01-1.14-9.66-2.89-2.89-1.72-4.83-4.34-5.57-7.72-11.1-37-22.6-74.3-34.1-111-4.34-14-8.95-31.4-14-48.3-1.82-4.83-8.16-5.32-8.46 1.16v156c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-207c0-5.74 2.62-13.2 9.39-16.3 7.5-3.14 15-4.05 21.8-3.8 3.14 0 6.03 0.686 8.95 1.46 3.14 0.797 6.03 1.98 8.7 3.63 2.65 1.38 5.32 3.14 7.5 5.57 2.22 2.22 3.87 4.83 5.07 7.72l45.8 157c4.63-15.9 32.4-117 33.3-121 4.12-13.8 7.72-26.5 10.9-38.7 1.16-2.65 2.89-5.32 5.07-7.5 2.15-2.15 4.58-4.12 7.5-5.32 2.65-1.57 5.57-2.89 8.46-3.63 3.14-0.797 9.44-0.988 12.1-0.988 11.6 1.07 29.4 9.14 29.4 27z' fill='%23808080'/%3E%3C/svg%3E">
26
+ <meta name="viewport" content="width=device-width, initial-scale=1">
27
+ <meta name="description" content="HTTP Mock Server">
28
+ <title>Mockaton</title>
29
29
  </head>
30
30
  <body>
31
31
  </body>