atom.io 0.33.12 → 0.33.13

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.
@@ -1,10 +1,17 @@
1
+ import { actUponStore, arbitrary } from "atom.io/internal"
1
2
  import { jsonRefinery } from "atom.io/introspection"
2
3
  import type { JsonTypes } from "atom.io/json"
3
4
  import { isJson } from "atom.io/json"
4
- import type { CSSProperties, FC, ReactElement } from "react"
5
+ import {
6
+ type CSSProperties,
7
+ type FC,
8
+ type ReactElement,
9
+ useContext,
10
+ } from "react"
5
11
 
6
12
  import { button } from "../Button"
7
13
  import { ElasticInput } from "../elastic-input"
14
+ import { DevtoolsContext } from "../store"
8
15
  import type { SetterOrUpdater } from "."
9
16
  import { SubEditors } from "."
10
17
  import type { JsonEditorComponents } from "./default-components"
@@ -46,6 +53,8 @@ export const JsonEditor_INTERNAL = <T,>({
46
53
  setIsOpen,
47
54
  testid,
48
55
  }: JsonEditorProps_INTERNAL<T>): ReactElement | null => {
56
+ const { openCloseAllTX, store } = useContext(DevtoolsContext)
57
+
49
58
  const dataIsJson = isJson(data)
50
59
  const refined = jsonRefinery.refine<unknown>(data) ?? {
51
60
  type: `non-json`,
@@ -58,6 +67,7 @@ export const JsonEditor_INTERNAL = <T,>({
58
67
  const disabled = isReadonly(path)
59
68
 
60
69
  const dataIsTree = refined.type === `array` || refined.type === `object`
70
+ const dataIsExpandable = dataIsTree && isOpen !== undefined && setIsOpen
61
71
 
62
72
  return isHidden(path) ? null : (
63
73
  <Components.ErrorBoundary>
@@ -67,6 +77,89 @@ export const JsonEditor_INTERNAL = <T,>({
67
77
  testid={testid}
68
78
  >
69
79
  <header>
80
+ <main>
81
+ {remove || dataIsExpandable ? (
82
+ <button.OpenClose
83
+ isOpen={isOpen ?? false}
84
+ testid={`${testid}-open-close`}
85
+ onShiftClick={() => {
86
+ actUponStore(store, openCloseAllTX, arbitrary())(path, isOpen)
87
+ return false
88
+ }}
89
+ setIsOpen={setIsOpen}
90
+ disabled={!dataIsExpandable}
91
+ />
92
+ ) : null}
93
+ {rename && (
94
+ <Components.KeyWrapper>
95
+ <ElasticInput
96
+ value={name}
97
+ onChange={(e) => {
98
+ rename(e.target.value)
99
+ }}
100
+ disabled={disabled}
101
+ data-testid={`${testid}-rename`}
102
+ />
103
+ </Components.KeyWrapper>
104
+ )}
105
+ {dataIsTree ? (
106
+ <>
107
+ {isOpen !== undefined && setIsOpen ? (
108
+ <span className="json_viewer">{JSON.stringify(data)}</span>
109
+ ) : null}
110
+ {recast ? (
111
+ <select
112
+ onChange={(e) => {
113
+ recast(e.target.value as keyof JsonTypes)
114
+ }}
115
+ value={refined.type}
116
+ disabled={disabled}
117
+ data-testid={`${testid}-recast`}
118
+ >
119
+ {Object.keys(SubEditors).map((type) => (
120
+ <option key={type} value={type}>
121
+ {type}
122
+ </option>
123
+ ))}
124
+ </select>
125
+ ) : null}
126
+ </>
127
+ ) : (
128
+ <>
129
+ <SubEditor
130
+ data={refined.data as never}
131
+ set={set}
132
+ remove={remove}
133
+ rename={rename}
134
+ path={path}
135
+ isReadonly={isReadonly}
136
+ isHidden={isHidden}
137
+ Components={Components}
138
+ testid={testid}
139
+ />
140
+ {recast && dataIsJson ? (
141
+ <select
142
+ onChange={
143
+ disabled
144
+ ? undefined
145
+ : (e) => {
146
+ recast(e.target.value as keyof JsonTypes)
147
+ }
148
+ }
149
+ value={refined.type}
150
+ disabled={disabled}
151
+ data-testid={`${testid}-recast`}
152
+ >
153
+ {Object.keys(SubEditors).map((type) => (
154
+ <option key={type} value={type}>
155
+ {type}
156
+ </option>
157
+ ))}
158
+ </select>
159
+ ) : null}
160
+ </>
161
+ )}
162
+ </main>
70
163
  {remove ? (
71
164
  <Components.Button
72
165
  disabled={disabled}
@@ -78,82 +171,6 @@ export const JsonEditor_INTERNAL = <T,>({
78
171
  <Components.DeleteIcon />
79
172
  </Components.Button>
80
173
  ) : null}
81
- {dataIsTree && isOpen !== undefined && setIsOpen ? (
82
- <button.OpenClose
83
- isOpen={isOpen}
84
- testid={`${testid}-open-close`}
85
- setIsOpen={setIsOpen}
86
- />
87
- ) : null}
88
- {rename && (
89
- <Components.KeyWrapper>
90
- <ElasticInput
91
- value={name}
92
- onChange={(e) => {
93
- rename(e.target.value)
94
- }}
95
- disabled={disabled}
96
- data-testid={`${testid}-rename`}
97
- />
98
- </Components.KeyWrapper>
99
- )}
100
- {dataIsTree ? (
101
- <>
102
- {recast ? (
103
- <select
104
- onChange={(e) => {
105
- recast(e.target.value as keyof JsonTypes)
106
- }}
107
- value={refined.type}
108
- disabled={disabled}
109
- data-testid={`${testid}-recast`}
110
- >
111
- {Object.keys(SubEditors).map((type) => (
112
- <option key={type} value={type}>
113
- {type}
114
- </option>
115
- ))}
116
- </select>
117
- ) : null}
118
- {isOpen !== undefined && setIsOpen ? (
119
- <span className="json_viewer">{JSON.stringify(data)}</span>
120
- ) : null}
121
- </>
122
- ) : (
123
- <>
124
- <SubEditor
125
- data={refined.data as never}
126
- set={set}
127
- remove={remove}
128
- rename={rename}
129
- path={path}
130
- isReadonly={isReadonly}
131
- isHidden={isHidden}
132
- Components={Components}
133
- testid={testid}
134
- />
135
- {recast && dataIsJson ? (
136
- <select
137
- onChange={
138
- disabled
139
- ? undefined
140
- : (e) => {
141
- recast(e.target.value as keyof JsonTypes)
142
- }
143
- }
144
- value={refined.type}
145
- disabled={disabled}
146
- data-testid={`${testid}-recast`}
147
- >
148
- {Object.keys(SubEditors).map((type) => (
149
- <option key={type} value={type}>
150
- {type}
151
- </option>
152
- ))}
153
- </select>
154
- ) : null}
155
- </>
156
- )}
157
174
  </header>
158
175
 
159
176
  {dataIsTree && isOpen !== false ? (
@@ -1,12 +1,22 @@
1
- import type { RegularAtomFamilyToken, RegularAtomToken } from "atom.io"
1
+ import type {
2
+ AtomToken,
3
+ RegularAtomFamilyToken,
4
+ RegularAtomToken,
5
+ SelectorToken,
6
+ TransactionToken,
7
+ } from "atom.io"
2
8
  import {
3
9
  createAtomFamily,
4
10
  createStandaloneAtom,
11
+ createTransaction,
5
12
  IMPLICIT,
6
13
  type Store,
7
14
  } from "atom.io/internal"
8
- import type { IntrospectionStates } from "atom.io/introspection"
9
- import { attachIntrospectionStates } from "atom.io/introspection"
15
+ import type {
16
+ IntrospectionStates,
17
+ WritableTokenIndex,
18
+ } from "atom.io/introspection"
19
+ import { attachIntrospectionStates, isPlainObject } from "atom.io/introspection"
10
20
  import { persistSync } from "atom.io/web"
11
21
  import type { Context } from "react"
12
22
  import { createContext } from "react"
@@ -17,7 +27,10 @@ export type DevtoolsStates = {
17
27
  devtoolsAreOpenState: RegularAtomToken<boolean>
18
28
  devtoolsViewSelectionState: RegularAtomToken<DevtoolsView>
19
29
  devtoolsViewOptionsState: RegularAtomToken<DevtoolsView[]>
20
- viewIsOpenAtoms: RegularAtomFamilyToken<boolean, string>
30
+ viewIsOpenAtoms: RegularAtomFamilyToken<boolean, readonly (number | string)[]>
31
+ openCloseAllTX: TransactionToken<
32
+ (path: readonly (number | string)[], current?: boolean) => void
33
+ >
21
34
  }
22
35
 
23
36
  export function attachDevtoolsStates(
@@ -52,13 +65,90 @@ export function attachDevtoolsStates(
52
65
  : [persistSync(window.localStorage, JSON, `🔍 Devtools View Options`)],
53
66
  })
54
67
 
55
- const viewIsOpenAtoms = createAtomFamily<boolean, string>(store, {
68
+ const viewIsOpenAtoms = createAtomFamily<
69
+ boolean,
70
+ readonly (number | string)[]
71
+ >(store, {
56
72
  key: `🔍 Devtools View Is Open`,
57
73
  default: false,
58
74
  effects: (key) =>
59
75
  typeof window === `undefined`
60
76
  ? []
61
- : [persistSync(window.localStorage, JSON, key + `:view-is-open`)],
77
+ : [persistSync(window.localStorage, JSON, `view-is-open:${key.join()}`)],
78
+ })
79
+
80
+ const openCloseAllTX: TransactionToken<
81
+ (path: readonly (number | string)[], current?: boolean) => void
82
+ > = createTransaction<
83
+ (path: readonly (number | string)[], current?: boolean) => void
84
+ >(store, {
85
+ key: `openCloseMultiView`,
86
+ do: ({ get, set }, path, current) => {
87
+ const currentView = get(devtoolsViewSelectionState)
88
+ let states:
89
+ | WritableTokenIndex<AtomToken<unknown>>
90
+ | WritableTokenIndex<SelectorToken<unknown>>
91
+ switch (currentView) {
92
+ case `atoms`:
93
+ states = get(introspectionStates.atomIndex)
94
+ break
95
+ case `selectors`:
96
+ states = get(introspectionStates.selectorIndex)
97
+ break
98
+ case `transactions`:
99
+ case `timelines`:
100
+ return
101
+ }
102
+
103
+ switch (path.length) {
104
+ case 1:
105
+ {
106
+ for (const [key] of states) {
107
+ set(viewIsOpenAtoms, [key], !current)
108
+ }
109
+ }
110
+ break
111
+ default: {
112
+ const item = states.get(path[0] as string)
113
+ let value: unknown
114
+ let segments: (number | string)[]
115
+ if (item) {
116
+ if (`familyMembers` in item) {
117
+ if (path.length === 2) {
118
+ for (const [subKey] of item.familyMembers) {
119
+ set(viewIsOpenAtoms, [path[0], subKey], !current)
120
+ }
121
+ return
122
+ }
123
+ // biome-ignore lint/style/noNonNullAssertion: fine here
124
+ const token = item.familyMembers.get(path[1] as string)!
125
+ value = get(token)
126
+ segments = path.slice(2, -1)
127
+ } else {
128
+ value = get(item)
129
+ segments = path.slice(1, -1)
130
+ }
131
+ for (const segment of segments) {
132
+ if (value && typeof value === `object`) {
133
+ value = value[segment as keyof typeof value]
134
+ }
135
+ }
136
+ const head = path.slice(0, -1)
137
+ if (Array.isArray(value)) {
138
+ for (let i = 0; i < value.length; i++) {
139
+ set(viewIsOpenAtoms, [...head, i], !current)
140
+ }
141
+ } else {
142
+ if (isPlainObject(value)) {
143
+ for (const key of Object.keys(value)) {
144
+ set(viewIsOpenAtoms, [...head, key], !current)
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ },
62
152
  })
63
153
 
64
154
  return {
@@ -67,6 +157,7 @@ export function attachDevtoolsStates(
67
157
  devtoolsViewSelectionState,
68
158
  devtoolsViewOptionsState,
69
159
  viewIsOpenAtoms,
160
+ openCloseAllTX,
70
161
  store,
71
162
  }
72
163
  }