atom.io 0.6.2 → 0.6.3

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.
Files changed (110) hide show
  1. package/dist/index.d.mts +2 -2
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.js +7 -2
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +7 -2
  6. package/dist/index.mjs.map +1 -1
  7. package/json/dist/index.d.mts +18 -0
  8. package/json/dist/index.d.ts +18 -0
  9. package/json/dist/index.js +51 -0
  10. package/json/dist/index.js.map +1 -0
  11. package/json/dist/index.mjs +15 -0
  12. package/json/dist/index.mjs.map +1 -0
  13. package/package.json +13 -3
  14. package/react/dist/index.d.mts +24 -0
  15. package/react/dist/index.d.ts +24 -0
  16. package/react/dist/index.js +83 -0
  17. package/react/dist/index.js.map +1 -0
  18. package/react/dist/index.mjs +41 -0
  19. package/react/dist/index.mjs.map +1 -0
  20. package/react-devtools/dist/index.css +26 -0
  21. package/react-devtools/dist/index.css.map +1 -0
  22. package/react-devtools/dist/index.d.mts +15 -0
  23. package/react-devtools/dist/index.d.ts +15 -0
  24. package/react-devtools/dist/index.js +1596 -0
  25. package/react-devtools/dist/index.js.map +1 -0
  26. package/react-devtools/dist/index.mjs +1568 -0
  27. package/react-devtools/dist/index.mjs.map +1 -0
  28. package/realtime/dist/index.d.mts +27 -0
  29. package/realtime/dist/index.d.ts +27 -0
  30. package/realtime/dist/index.js +191 -0
  31. package/realtime/dist/index.js.map +1 -0
  32. package/realtime/dist/index.mjs +152 -0
  33. package/realtime/dist/index.mjs.map +1 -0
  34. package/realtime-react/dist/index.d.mts +45 -0
  35. package/realtime-react/dist/index.d.ts +45 -0
  36. package/realtime-react/dist/index.js +213 -0
  37. package/realtime-react/dist/index.js.map +1 -0
  38. package/realtime-react/dist/index.mjs +168 -0
  39. package/realtime-react/dist/index.mjs.map +1 -0
  40. package/realtime-testing/dist/index.d.mts +49 -0
  41. package/realtime-testing/dist/index.d.ts +49 -0
  42. package/realtime-testing/dist/index.js +153 -0
  43. package/realtime-testing/dist/index.js.map +1 -0
  44. package/realtime-testing/dist/index.mjs +117 -0
  45. package/realtime-testing/dist/index.mjs.map +1 -0
  46. package/realtime-testing/package.json +15 -0
  47. package/src/atom.ts +15 -15
  48. package/src/index.ts +59 -59
  49. package/src/internal/atom-internal.ts +36 -36
  50. package/src/internal/families-internal.ts +114 -114
  51. package/src/internal/get.ts +83 -83
  52. package/src/internal/is-default.ts +17 -17
  53. package/src/internal/meta/attach-meta.ts +7 -7
  54. package/src/internal/meta/meta-state.ts +115 -115
  55. package/src/internal/operation.ts +93 -93
  56. package/src/internal/selector/create-read-write-selector.ts +46 -46
  57. package/src/internal/selector/create-readonly-selector.ts +37 -37
  58. package/src/internal/selector/lookup-selector-sources.ts +9 -9
  59. package/src/internal/selector/register-selector.ts +44 -44
  60. package/src/internal/selector/trace-selector-atoms.ts +30 -30
  61. package/src/internal/selector/update-selector-atoms.ts +25 -25
  62. package/src/internal/selector-internal.ts +37 -37
  63. package/src/internal/set.ts +78 -78
  64. package/src/internal/store.ts +118 -118
  65. package/src/internal/subscribe-internal.ts +62 -62
  66. package/src/internal/time-travel-internal.ts +76 -76
  67. package/src/internal/timeline/add-atom-to-timeline.ts +158 -153
  68. package/src/internal/timeline-internal.ts +80 -80
  69. package/src/internal/transaction/abort-transaction.ts +8 -8
  70. package/src/internal/transaction/apply-transaction.ts +41 -41
  71. package/src/internal/transaction/build-transaction.ts +28 -28
  72. package/src/internal/transaction/index.ts +7 -7
  73. package/src/internal/transaction/redo-transaction.ts +13 -13
  74. package/src/internal/transaction/undo-transaction.ts +13 -13
  75. package/src/internal/transaction-internal.ts +48 -48
  76. package/src/json/select-json.ts +12 -12
  77. package/src/logger.ts +30 -30
  78. package/src/react/store-context.tsx +4 -4
  79. package/src/react/store-hooks.ts +18 -18
  80. package/src/react-devtools/AtomIODevtools.tsx +83 -82
  81. package/src/react-devtools/StateEditor.tsx +53 -53
  82. package/src/react-devtools/TokenList.tsx +47 -42
  83. package/src/react-explorer/AtomIOExplorer.tsx +197 -185
  84. package/src/react-explorer/explorer-effects.ts +11 -11
  85. package/src/react-explorer/explorer-states.ts +186 -193
  86. package/src/react-explorer/index.ts +11 -11
  87. package/src/react-explorer/space-states.ts +48 -50
  88. package/src/react-explorer/view-states.ts +25 -25
  89. package/src/realtime/hook-composition/expose-family.ts +81 -81
  90. package/src/realtime/hook-composition/expose-single.ts +26 -26
  91. package/src/realtime/hook-composition/expose-timeline.ts +60 -0
  92. package/src/realtime/hook-composition/index.ts +2 -2
  93. package/src/realtime/hook-composition/receive-state.ts +18 -18
  94. package/src/realtime/hook-composition/receive-transaction.ts +8 -8
  95. package/src/realtime-react/realtime-context.tsx +17 -17
  96. package/src/realtime-react/realtime-hooks.ts +17 -17
  97. package/src/realtime-react/realtime-state.ts +4 -4
  98. package/src/realtime-react/use-pull-family-member.ts +15 -15
  99. package/src/realtime-react/use-pull-family.ts +13 -13
  100. package/src/realtime-react/use-pull.ts +12 -12
  101. package/src/realtime-react/use-push.ts +15 -15
  102. package/src/realtime-react/use-server-action.ts +21 -21
  103. package/src/realtime-testing/index.ts +1 -0
  104. package/src/realtime-testing/setup-realtime-test.tsx +160 -0
  105. package/src/selector.ts +25 -25
  106. package/src/silo.ts +38 -38
  107. package/src/subscribe.ts +68 -68
  108. package/src/timeline.ts +13 -13
  109. package/src/transaction.ts +28 -28
  110. package/src/web-effects/storage.ts +17 -17
@@ -8,66 +8,66 @@ import { ElasticInput } from "~/packages/hamr/src/react-elastic-input"
8
8
  import { JsonEditor } from "~/packages/hamr/src/react-json-editor"
9
9
 
10
10
  export const StateEditor: FC<{
11
- storeHooks: StoreHooks
12
- token: StateToken<unknown>
11
+ storeHooks: StoreHooks
12
+ token: StateToken<unknown>
13
13
  }> = ({ storeHooks, token }) => {
14
- const [data, set] = storeHooks.useIO(token)
15
- return isPlainJson(data) ? (
16
- <JsonEditor data={data} set={set} schema={true} />
17
- ) : (
18
- <div className="json_editor">
19
- <ElasticInput
20
- value={
21
- data instanceof Set
22
- ? `Set { ${JSON.stringify([...data]).slice(1, -1)} }`
23
- : data instanceof Map
24
- ? `Map ` + JSON.stringify([...data])
25
- : Object.getPrototypeOf(data).constructor.name +
26
- ` ` +
27
- JSON.stringify(data)
28
- }
29
- disabled={true}
30
- />
31
- </div>
32
- )
14
+ const [data, set] = storeHooks.useIO(token)
15
+ return isPlainJson(data) ? (
16
+ <JsonEditor data={data} set={set} schema={true} />
17
+ ) : (
18
+ <div className="json_editor">
19
+ <ElasticInput
20
+ value={
21
+ data instanceof Set
22
+ ? `Set { ${JSON.stringify([...data]).slice(1, -1)} }`
23
+ : data instanceof Map
24
+ ? `Map ` + JSON.stringify([...data])
25
+ : Object.getPrototypeOf(data).constructor.name +
26
+ ` ` +
27
+ JSON.stringify(data)
28
+ }
29
+ disabled={true}
30
+ />
31
+ </div>
32
+ )
33
33
  }
34
34
 
35
35
  export const ReadonlySelectorEditor: FC<{
36
- storeHooks: StoreHooks
37
- token: ReadonlySelectorToken<unknown>
36
+ storeHooks: StoreHooks
37
+ token: ReadonlySelectorToken<unknown>
38
38
  }> = ({ storeHooks, token }) => {
39
- const data = storeHooks.useO(token)
40
- return isPlainJson(data) ? (
41
- <JsonEditor
42
- data={data}
43
- set={() => null}
44
- schema={true}
45
- isReadonly={() => true}
46
- />
47
- ) : (
48
- <div className="json_editor">
49
- <ElasticInput
50
- value={
51
- data instanceof Set
52
- ? `Set ` + JSON.stringify([...data])
53
- : data instanceof Map
54
- ? `Map ` + JSON.stringify([...data])
55
- : Object.getPrototypeOf(data).constructor.name +
56
- ` ` +
57
- JSON.stringify(data)
58
- }
59
- disabled={true}
60
- />
61
- </div>
62
- )
39
+ const data = storeHooks.useO(token)
40
+ return isPlainJson(data) ? (
41
+ <JsonEditor
42
+ data={data}
43
+ set={() => null}
44
+ schema={true}
45
+ isReadonly={() => true}
46
+ />
47
+ ) : (
48
+ <div className="json_editor">
49
+ <ElasticInput
50
+ value={
51
+ data instanceof Set
52
+ ? `Set ` + JSON.stringify([...data])
53
+ : data instanceof Map
54
+ ? `Map ` + JSON.stringify([...data])
55
+ : Object.getPrototypeOf(data).constructor.name +
56
+ ` ` +
57
+ JSON.stringify(data)
58
+ }
59
+ disabled={true}
60
+ />
61
+ </div>
62
+ )
63
63
  }
64
64
 
65
65
  export const StoreEditor: FC<{
66
- storeHooks: StoreHooks
67
- token: ReadonlySelectorToken<unknown> | StateToken<unknown>
66
+ storeHooks: StoreHooks
67
+ token: ReadonlySelectorToken<unknown> | StateToken<unknown>
68
68
  }> = ({ storeHooks, token }) => {
69
- if (token.type === `readonly_selector`) {
70
- return <ReadonlySelectorEditor storeHooks={storeHooks} token={token} />
71
- }
72
- return <StateEditor storeHooks={storeHooks} token={token} />
69
+ if (token.type === `readonly_selector`) {
70
+ return <ReadonlySelectorEditor storeHooks={storeHooks} token={token} />
71
+ }
72
+ return <StateEditor storeHooks={storeHooks} token={token} />
73
73
  }
@@ -2,10 +2,10 @@ import type { FC } from "react"
2
2
  import { Fragment } from "react"
3
3
 
4
4
  import type {
5
- AtomToken,
6
- ReadonlySelectorToken,
7
- SelectorToken,
8
- __INTERNAL__,
5
+ AtomToken,
6
+ ReadonlySelectorToken,
7
+ SelectorToken,
8
+ __INTERNAL__,
9
9
  } from "atom.io"
10
10
  import { getState } from "atom.io"
11
11
  import type { StoreHooks } from "atom.io/react"
@@ -15,43 +15,48 @@ import { recordToEntries } from "~/packages/anvl/src/object"
15
15
  import { StoreEditor } from "./StateEditor"
16
16
 
17
17
  export const TokenList: FC<{
18
- storeHooks: StoreHooks
19
- tokenIndex: ReadonlySelectorToken<
20
- __INTERNAL__.META.StateTokenIndex<
21
- | AtomToken<unknown>
22
- | ReadonlySelectorToken<unknown>
23
- | SelectorToken<unknown>
24
- >
25
- >
18
+ storeHooks: StoreHooks
19
+ tokenIndex: ReadonlySelectorToken<
20
+ __INTERNAL__.META.StateTokenIndex<
21
+ | AtomToken<unknown>
22
+ | ReadonlySelectorToken<unknown>
23
+ | SelectorToken<unknown>
24
+ >
25
+ >
26
26
  }> = ({ storeHooks, tokenIndex }) => {
27
- const tokenIds = storeHooks.useO(tokenIndex)
28
- return (
29
- <>
30
- {Object.entries(tokenIds).map(([key, token]) => (
31
- <Fragment key={key}>
32
- {key.startsWith(`👁‍🗨_`) ? null : (
33
- <div className="node">
34
- {`type` in token ? (
35
- <>
36
- <label onClick={() => console.log(token, getState(token))}>
37
- {key}
38
- </label>
39
- <StoreEditor storeHooks={storeHooks} token={token} />
40
- </>
41
- ) : (
42
- recordToEntries(token.familyMembers).map(([key, token]) => (
43
- <>
44
- <label>{key}</label>
45
- <div key={key} className="node">
46
- {key}:<StoreEditor storeHooks={storeHooks} token={token} />
47
- </div>
48
- </>
49
- ))
50
- )}
51
- </div>
52
- )}
53
- </Fragment>
54
- ))}
55
- </>
56
- )
27
+ const tokenIds = storeHooks.useO(tokenIndex)
28
+ return (
29
+ <>
30
+ {Object.entries(tokenIds).map(([key, token]) => {
31
+ let logState: () => void
32
+ return (
33
+ <Fragment key={key}>
34
+ {key.startsWith(`👁‍🗨_`) ? null : (
35
+ <div className="node">
36
+ {`type` in token
37
+ ? ((logState = () => console.log(token, getState(token))),
38
+ (
39
+ <>
40
+ <label onClick={logState} onKeyUp={logState}>
41
+ {key}
42
+ </label>
43
+ <StoreEditor storeHooks={storeHooks} token={token} />
44
+ </>
45
+ ))
46
+ : recordToEntries(token.familyMembers).map(([key, token]) => (
47
+ <>
48
+ <label>{key}</label>
49
+ <div key={key} className="node">
50
+ {key}:
51
+ <StoreEditor storeHooks={storeHooks} token={token} />
52
+ </div>
53
+ </>
54
+ ))}
55
+ </div>
56
+ )}
57
+ </Fragment>
58
+ )
59
+ })}
60
+ </>
61
+ )
57
62
  }
@@ -12,197 +12,209 @@ import { setState } from ".."
12
12
  import { runTransaction } from "../transaction"
13
13
 
14
14
  export type ExplorerOptions = {
15
- key: string
16
- Components?: {
17
- SpaceWrapper: WC
18
- CloseSpaceButton: FC<{ onClick: () => void }>
19
- }
20
- storeHooks: StoreHooks
15
+ key: string
16
+ Components?: {
17
+ SpaceWrapper: WC
18
+ CloseSpaceButton: FC<{ onClick: () => void }>
19
+ }
20
+ storeHooks: StoreHooks
21
21
  }
22
22
 
23
23
  const DEFAULT_COMPONENTS: ExplorerOptions[`Components`] = {
24
- SpaceWrapper: ({ children }) => <div>{children}</div>,
25
- CloseSpaceButton: ({ onClick }) => <button onClick={onClick}>X</button>,
24
+ SpaceWrapper: ({ children }) => <div>{children}</div>,
25
+ CloseSpaceButton: ({ onClick }) => (
26
+ <button type="button" onClick={onClick}>
27
+ X
28
+ </button>
29
+ ),
26
30
  }
27
31
 
28
32
  export const composeExplorer = ({
29
- key,
30
- Components,
31
- storeHooks: { useO, useIO },
33
+ key,
34
+ Components,
35
+ storeHooks: { useO, useIO },
32
36
  }: ExplorerOptions): ReturnType<typeof attachExplorerState> & {
33
- Explorer: FC<{ children: ReactNode }>
34
- useSetTitle: (viewId: string) => void
37
+ Explorer: FC<{ children: ReactNode }>
38
+ useSetTitle: (viewId: string) => void
35
39
  } => {
36
- const { SpaceWrapper, CloseSpaceButton } = {
37
- ...DEFAULT_COMPONENTS,
38
- ...Components,
39
- }
40
-
41
- const state = attachExplorerState(key)
42
-
43
- const {
44
- addSpace,
45
- addView,
46
- allViewsState,
47
- findSpaceFocusedViewState,
48
- findSpaceLayoutNode,
49
- findSpaceViewsState,
50
- findViewFocusedState,
51
- findViewState,
52
- removeSpace,
53
- removeView,
54
- spaceLayoutState,
55
- viewIndexState,
56
- } = state
57
-
58
- const View: FC<{
59
- children: ReactNode
60
- viewId: string
61
- }> = ({ children, viewId }) => {
62
- const location = useLocation()
63
- const viewState = findViewState(viewId)
64
- const [view, setView] = useIO(viewState)
65
- useEffect(() => {
66
- setView((view) => ({ ...view, location }))
67
- }, [location.key])
68
- return (
69
- <div className="view">
70
- <header>
71
- <h1>{view.title}</h1>
72
- <CloseSpaceButton onClick={() => runTransaction(removeView)(viewId)} />
73
- </header>
74
- <main>{children}</main>
75
- <footer>
76
- <nav>
77
- {location.pathname.split(`/`).map((pathPiece, idx, array) =>
78
- pathPiece === `` && idx === 1 ? null : (
79
- <Link
80
- to={array.slice(0, idx + 1).join(`/`)}
81
- key={`${pathPiece}_${viewId}`}
82
- >
83
- {idx === 0 ? `home` : pathPiece}/
84
- </Link>
85
- )
86
- )}
87
- </nav>
88
- </footer>
89
- </div>
90
- )
91
- }
92
-
93
- const Tab: FC<{ viewId: string; spaceId: string }> = ({ viewId, spaceId }) => {
94
- const view = useO(findViewState(viewId))
95
- const [spaceFocusedView, setSpaceFocusedView] = useIO(
96
- findSpaceFocusedViewState(spaceId)
97
- )
98
- return (
99
- <div
100
- className={`tab ${spaceFocusedView === viewId ? `focused` : ``}`}
101
- onClick={() => setSpaceFocusedView(viewId)}
102
- >
103
- {view.title}
104
- </div>
105
- )
106
- }
107
-
108
- const TabBar: FC<{
109
- spaceId: string
110
- viewIds: string[]
111
- }> = ({ spaceId, viewIds }) => {
112
- return (
113
- <nav className="tab-bar">
114
- {viewIds.map((viewId) => (
115
- <Tab key={viewId} viewId={viewId} spaceId={spaceId} />
116
- ))}
117
- </nav>
118
- )
119
- }
120
-
121
- const Space: FC<{
122
- children: ReactNode
123
- focusedViewId: string
124
- spaceId: string
125
- viewIds: string[]
126
- }> = ({ children, focusedViewId, spaceId, viewIds }) => {
127
- const view = useO(findViewState(focusedViewId))
128
- return (
129
- <div className="space">
130
- <ErrorBoundary>
131
- <MemoryRouter
132
- initialEntries={view.location ? [view.location.pathname] : []}
133
- >
134
- <TabBar spaceId={spaceId} viewIds={viewIds} />
135
- <View viewId={focusedViewId}>{children}</View>
136
- </MemoryRouter>
137
- </ErrorBoundary>
138
- </div>
139
- )
140
- }
141
-
142
- const Spaces: FC<{ children: ReactNode; spaceId?: string }> = ({
143
- children,
144
- spaceId = `root`,
145
- }) => {
146
- const spaceLayout = useO(findSpaceLayoutNode(spaceId))
147
- const viewIds = useO(findSpaceViewsState(spaceId))
148
- const focusedViewId = useO(findSpaceFocusedViewState(spaceId))
149
- console.log({ spaceLayout, viewIds, focusedViewId })
150
- return (
151
- <div className="spaces">
152
- {spaceLayout.childSpaceIds.length === 0 ? (
153
- focusedViewId ? (
154
- <Space
155
- focusedViewId={focusedViewId}
156
- spaceId={spaceId}
157
- viewIds={viewIds}
158
- >
159
- {children}
160
- </Space>
161
- ) : (
162
- `no view`
163
- )
164
- ) : (
165
- spaceLayout.childSpaceIds.map((childSpaceId) => (
166
- <Spaces key={childSpaceId} spaceId={childSpaceId}>
167
- {children}
168
- </Spaces>
169
- ))
170
- )}
171
- <button onClick={() => runTransaction(addView)({ spaceId })}>
172
- + View
173
- </button>
174
- <button onClick={() => runTransaction(addSpace)({ parentId: spaceId })}>
175
- + Space
176
- </button>
177
- </div>
178
- )
179
- }
180
-
181
- const Explorer: FC<{ children: ReactNode }> = ({ children }) => {
182
- return <Spaces>{children}</Spaces>
183
- }
184
-
185
- const useSetTitle = (title: string): void => {
186
- let location: ReturnType<typeof useLocation>
187
- try {
188
- location = useLocation()
189
- } catch (thrown) {
190
- console.warn(
191
- `Failed to set title to "${title}"; useSetTitle must be called within the children of Explorer`
192
- )
193
- return
194
- }
195
- const views = useO(allViewsState)
196
- const locationView = views.find(
197
- ([, view]) => view.location.key === location.key
198
- )
199
- const viewId = locationView?.[0] ?? null
200
- useEffect(() => {
201
- if (viewId) {
202
- setState(findViewState(viewId), (v) => ({ ...v, title }))
203
- }
204
- }, [viewId])
205
- }
206
-
207
- return { Explorer, useSetTitle, ...state }
40
+ const { SpaceWrapper, CloseSpaceButton } = {
41
+ ...DEFAULT_COMPONENTS,
42
+ ...Components,
43
+ }
44
+
45
+ const state = attachExplorerState(key)
46
+
47
+ const {
48
+ addSpace,
49
+ addView,
50
+ allViewsState,
51
+ findSpaceFocusedViewState,
52
+ findSpaceLayoutNode,
53
+ findSpaceViewsState,
54
+ findViewFocusedState,
55
+ findViewState,
56
+ removeSpace,
57
+ removeView,
58
+ spaceLayoutState,
59
+ viewIndexState,
60
+ } = state
61
+
62
+ const View: FC<{
63
+ children: ReactNode
64
+ viewId: string
65
+ }> = ({ children, viewId }) => {
66
+ const location = useLocation()
67
+ const viewState = findViewState(viewId)
68
+ const [view, setView] = useIO(viewState)
69
+ useEffect(() => {
70
+ setView((view) => ({ ...view, location }))
71
+ }, [location.key])
72
+ return (
73
+ <div className="view">
74
+ <header>
75
+ <h1>{view.title}</h1>
76
+ <CloseSpaceButton onClick={() => runTransaction(removeView)(viewId)} />
77
+ </header>
78
+ <main>{children}</main>
79
+ <footer>
80
+ <nav>
81
+ {location.pathname.split(`/`).map((pathPiece, idx, array) =>
82
+ pathPiece === `` && idx === 1 ? null : (
83
+ <Link
84
+ to={array.slice(0, idx + 1).join(`/`)}
85
+ key={`${pathPiece}_${viewId}`}
86
+ >
87
+ {idx === 0 ? `home` : pathPiece}/
88
+ </Link>
89
+ ),
90
+ )}
91
+ </nav>
92
+ </footer>
93
+ </div>
94
+ )
95
+ }
96
+
97
+ const Tab: FC<{ viewId: string; spaceId: string }> = ({ viewId, spaceId }) => {
98
+ const view = useO(findViewState(viewId))
99
+ const [spaceFocusedView, setSpaceFocusedView] = useIO(
100
+ findSpaceFocusedViewState(spaceId),
101
+ )
102
+ const handleClick = () => setSpaceFocusedView(viewId)
103
+ return (
104
+ <div
105
+ className={`tab ${spaceFocusedView === viewId ? `focused` : ``}`}
106
+ onClick={handleClick}
107
+ onKeyUp={handleClick}
108
+ >
109
+ {view.title}
110
+ </div>
111
+ )
112
+ }
113
+
114
+ const TabBar: FC<{
115
+ spaceId: string
116
+ viewIds: string[]
117
+ }> = ({ spaceId, viewIds }) => {
118
+ return (
119
+ <nav className="tab-bar">
120
+ {viewIds.map((viewId) => (
121
+ <Tab key={viewId} viewId={viewId} spaceId={spaceId} />
122
+ ))}
123
+ </nav>
124
+ )
125
+ }
126
+
127
+ const Space: FC<{
128
+ children: ReactNode
129
+ focusedViewId: string
130
+ spaceId: string
131
+ viewIds: string[]
132
+ }> = ({ children, focusedViewId, spaceId, viewIds }) => {
133
+ const view = useO(findViewState(focusedViewId))
134
+ return (
135
+ <div className="space">
136
+ <ErrorBoundary>
137
+ <MemoryRouter
138
+ initialEntries={view.location ? [view.location.pathname] : []}
139
+ >
140
+ <TabBar spaceId={spaceId} viewIds={viewIds} />
141
+ <View viewId={focusedViewId}>{children}</View>
142
+ </MemoryRouter>
143
+ </ErrorBoundary>
144
+ </div>
145
+ )
146
+ }
147
+
148
+ const Spaces: FC<{ children: ReactNode; spaceId?: string }> = ({
149
+ children,
150
+ spaceId = `root`,
151
+ }) => {
152
+ const spaceLayout = useO(findSpaceLayoutNode(spaceId))
153
+ const viewIds = useO(findSpaceViewsState(spaceId))
154
+ const focusedViewId = useO(findSpaceFocusedViewState(spaceId))
155
+ console.log({ spaceLayout, viewIds, focusedViewId })
156
+ return (
157
+ <div className="spaces">
158
+ {spaceLayout.childSpaceIds.length === 0 ? (
159
+ focusedViewId ? (
160
+ <Space
161
+ focusedViewId={focusedViewId}
162
+ spaceId={spaceId}
163
+ viewIds={viewIds}
164
+ >
165
+ {children}
166
+ </Space>
167
+ ) : (
168
+ `no view`
169
+ )
170
+ ) : (
171
+ spaceLayout.childSpaceIds.map((childSpaceId) => (
172
+ <Spaces key={childSpaceId} spaceId={childSpaceId}>
173
+ {children}
174
+ </Spaces>
175
+ ))
176
+ )}
177
+ <button
178
+ type="button"
179
+ onClick={() => runTransaction(addView)({ spaceId })}
180
+ >
181
+ + View
182
+ </button>
183
+ <button
184
+ type="button"
185
+ onClick={() => runTransaction(addSpace)({ parentId: spaceId })}
186
+ >
187
+ + Space
188
+ </button>
189
+ </div>
190
+ )
191
+ }
192
+
193
+ const Explorer: FC<{ children: ReactNode }> = ({ children }) => {
194
+ return <Spaces>{children}</Spaces>
195
+ }
196
+
197
+ const useSetTitle = (title: string): void => {
198
+ let location: ReturnType<typeof useLocation>
199
+ try {
200
+ location = useLocation()
201
+ } catch (thrown) {
202
+ console.warn(
203
+ `Failed to set title to "${title}"; useSetTitle must be called within the children of Explorer`,
204
+ )
205
+ return
206
+ }
207
+ const views = useO(allViewsState)
208
+ const locationView = views.find(
209
+ ([, view]) => view.location.key === location.key,
210
+ )
211
+ const viewId = locationView?.[0] ?? null
212
+ useEffect(() => {
213
+ if (viewId) {
214
+ setState(findViewState(viewId), (v) => ({ ...v, title }))
215
+ }
216
+ }, [viewId])
217
+ }
218
+
219
+ return { Explorer, useSetTitle, ...state }
208
220
  }
@@ -6,15 +6,15 @@ import { parseJson, stringifyJson } from "~/packages/anvl/src/json"
6
6
  import { persistAtom } from "../web-effects"
7
7
 
8
8
  export const persistStringSetAtom = persistAtom<Set<string>>(localStorage)({
9
- stringify: (set) => stringifyJson([...set]),
10
- parse: (string) => {
11
- try {
12
- const json = parseJson(string)
13
- const array = isArray(isString)(json) ? json : []
14
- return new Set(array)
15
- } catch (thrown) {
16
- console.error(`Error parsing spaceIndexState from localStorage`)
17
- return new Set()
18
- }
19
- },
9
+ stringify: (set) => stringifyJson([...set]),
10
+ parse: (string) => {
11
+ try {
12
+ const json = parseJson(string)
13
+ const array = isArray(isString)(json) ? json : []
14
+ return new Set(array)
15
+ } catch (thrown) {
16
+ console.error(`Error parsing spaceIndexState from localStorage`)
17
+ return new Set()
18
+ }
19
+ },
20
20
  })