atom.io 0.3.0 → 0.4.0
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/README.md +14 -8
- package/dist/index.d.ts +117 -62
- package/dist/index.js +577 -287
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +574 -285
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -6
- package/react/dist/index.d.ts +12 -17
- package/react/dist/index.js +25 -34
- package/react/dist/index.js.map +1 -1
- package/react/dist/index.mjs +21 -34
- package/react/dist/index.mjs.map +1 -1
- package/react-devtools/dist/index.css +26 -0
- package/react-devtools/dist/index.css.map +1 -0
- package/react-devtools/dist/index.d.ts +15 -0
- package/react-devtools/dist/index.js +1579 -0
- package/react-devtools/dist/index.js.map +1 -0
- package/react-devtools/dist/index.mjs +1551 -0
- package/react-devtools/dist/index.mjs.map +1 -0
- package/react-devtools/package.json +15 -0
- package/src/index.ts +14 -8
- package/src/internal/atom-internal.ts +10 -5
- package/src/internal/families-internal.ts +7 -7
- package/src/internal/get.ts +9 -9
- package/src/internal/index.ts +2 -1
- package/src/internal/meta/attach-meta.ts +17 -0
- package/src/internal/meta/index.ts +4 -0
- package/src/internal/meta/meta-state.ts +135 -0
- package/src/internal/meta/meta-timelines.ts +1 -0
- package/src/internal/meta/meta-transactions.ts +1 -0
- package/src/internal/operation.ts +14 -3
- package/src/internal/selector-internal.ts +37 -15
- package/src/internal/store.ts +35 -6
- package/src/internal/time-travel-internal.ts +89 -0
- package/src/internal/timeline-internal.ts +110 -93
- package/src/internal/transaction-internal.ts +14 -5
- package/src/{internal/logger.ts → logger.ts} +2 -2
- package/src/react/index.ts +28 -46
- package/src/react-devtools/AtomIODevtools.tsx +107 -0
- package/src/react-devtools/StateEditor.tsx +73 -0
- package/src/react-devtools/TokenList.tsx +49 -0
- package/src/react-devtools/devtools.scss +130 -0
- package/src/react-devtools/index.ts +1 -0
- package/src/react-explorer/AtomIOExplorer.tsx +208 -0
- package/src/react-explorer/explorer-effects.ts +20 -0
- package/src/react-explorer/explorer-states.ts +224 -0
- package/src/react-explorer/index.ts +23 -0
- package/src/react-explorer/space-states.ts +73 -0
- package/src/react-explorer/view-states.ts +43 -0
- package/src/selector.ts +11 -11
- package/src/subscribe.ts +3 -3
- package/src/timeline.ts +3 -12
- package/src/transaction.ts +9 -4
- package/src/web-effects/index.ts +1 -0
- package/src/web-effects/storage.ts +30 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import HAMT from "hamt_plus"
|
|
2
2
|
|
|
3
3
|
import type { KeyedStateUpdate, TransactionUpdate, Store } from "."
|
|
4
|
-
import { IMPLICIT, withdraw } from "."
|
|
5
|
-
import { setState } from ".."
|
|
4
|
+
import { target, IMPLICIT, withdraw } from "."
|
|
6
5
|
import type { AtomToken, TimelineOptions, TimelineToken, ƒn } from ".."
|
|
7
6
|
|
|
8
7
|
export type Timeline = {
|
|
@@ -12,8 +11,13 @@ export type Timeline = {
|
|
|
12
11
|
prev: () => void
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
export type
|
|
16
|
-
type: `
|
|
14
|
+
export type TimelineAtomUpdate = KeyedStateUpdate<unknown> & {
|
|
15
|
+
type: `atom_update`
|
|
16
|
+
}
|
|
17
|
+
export type TimelineSelectorUpdate = {
|
|
18
|
+
key: string
|
|
19
|
+
type: `selector_update`
|
|
20
|
+
atomUpdates: TimelineAtomUpdate[]
|
|
17
21
|
}
|
|
18
22
|
export type TimelineTransactionUpdate = TransactionUpdate<ƒn> & {
|
|
19
23
|
type: `transaction_update`
|
|
@@ -22,23 +26,39 @@ export type TimelineTransactionUpdate = TransactionUpdate<ƒn> & {
|
|
|
22
26
|
export type TimelineData = {
|
|
23
27
|
at: number
|
|
24
28
|
timeTraveling: boolean
|
|
25
|
-
history: (
|
|
29
|
+
history: (
|
|
30
|
+
| TimelineAtomUpdate
|
|
31
|
+
| TimelineSelectorUpdate
|
|
32
|
+
| TimelineTransactionUpdate
|
|
33
|
+
)[]
|
|
34
|
+
selectorTime: number | null
|
|
35
|
+
transactionKey: string | null
|
|
26
36
|
}
|
|
27
37
|
|
|
28
38
|
export function timeline__INTERNAL(
|
|
29
39
|
options: TimelineOptions,
|
|
30
40
|
store: Store = IMPLICIT.STORE
|
|
31
41
|
): TimelineToken {
|
|
32
|
-
let incompleteTransactionKey: string | null = null
|
|
33
42
|
const timelineData: TimelineData = {
|
|
34
43
|
at: 0,
|
|
35
44
|
timeTraveling: false,
|
|
36
45
|
history: [],
|
|
46
|
+
selectorTime: null,
|
|
47
|
+
transactionKey: null,
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
const subscribeToAtom = (token: AtomToken<any>) => {
|
|
40
51
|
const state = withdraw(token, store)
|
|
41
52
|
state.subject.subscribe((update) => {
|
|
53
|
+
const storeCurrentSelectorKey =
|
|
54
|
+
store.operation.open && store.operation.token.type === `selector`
|
|
55
|
+
? store.operation.token.key
|
|
56
|
+
: null
|
|
57
|
+
const storeCurrentSelectorTime =
|
|
58
|
+
store.operation.open && store.operation.token.type === `selector`
|
|
59
|
+
? store.operation.time
|
|
60
|
+
: null
|
|
61
|
+
|
|
42
62
|
const storeCurrentTransactionKey =
|
|
43
63
|
store.transactionStatus.phase === `applying`
|
|
44
64
|
? store.transactionStatus.key
|
|
@@ -49,8 +69,10 @@ export function timeline__INTERNAL(
|
|
|
49
69
|
`->`,
|
|
50
70
|
update.newValue,
|
|
51
71
|
storeCurrentTransactionKey
|
|
52
|
-
? `) in "${storeCurrentTransactionKey}"`
|
|
53
|
-
:
|
|
72
|
+
? `) in transaction "${storeCurrentTransactionKey}"`
|
|
73
|
+
: storeCurrentSelectorKey
|
|
74
|
+
? `) in selector "${storeCurrentSelectorKey}"`
|
|
75
|
+
: `)`
|
|
54
76
|
)
|
|
55
77
|
|
|
56
78
|
if (
|
|
@@ -61,15 +83,18 @@ export function timeline__INTERNAL(
|
|
|
61
83
|
{ key: storeCurrentTransactionKey, type: `transaction` },
|
|
62
84
|
store
|
|
63
85
|
)
|
|
64
|
-
if (
|
|
65
|
-
if (
|
|
86
|
+
if (timelineData.transactionKey !== storeCurrentTransactionKey) {
|
|
87
|
+
if (timelineData.transactionKey) {
|
|
66
88
|
store.config.logger?.error(
|
|
67
|
-
`Timeline "${options.key}" was unable to resolve transaction "${
|
|
89
|
+
`Timeline "${options.key}" was unable to resolve transaction "${timelineData.transactionKey}. This is probably a bug.`
|
|
68
90
|
)
|
|
69
91
|
}
|
|
70
|
-
|
|
92
|
+
timelineData.transactionKey = storeCurrentTransactionKey
|
|
71
93
|
const subscription = currentTransaction.subject.subscribe((update) => {
|
|
72
94
|
if (timelineData.timeTraveling === false) {
|
|
95
|
+
if (timelineData.at !== timelineData.history.length) {
|
|
96
|
+
timelineData.history.splice(timelineData.at)
|
|
97
|
+
}
|
|
73
98
|
timelineData.history.push({
|
|
74
99
|
type: `transaction_update`,
|
|
75
100
|
...update,
|
|
@@ -80,117 +105,109 @@ export function timeline__INTERNAL(
|
|
|
80
105
|
}
|
|
81
106
|
timelineData.at = timelineData.history.length
|
|
82
107
|
subscription.unsubscribe()
|
|
83
|
-
|
|
108
|
+
timelineData.transactionKey = null
|
|
84
109
|
store.config.logger?.info(
|
|
85
|
-
`⌛ timeline "${options.key}"
|
|
110
|
+
`⌛ timeline "${options.key}" got a transaction_update "${update.key}"`
|
|
86
111
|
)
|
|
87
112
|
})
|
|
88
113
|
}
|
|
114
|
+
} else if (storeCurrentSelectorKey) {
|
|
115
|
+
if (timelineData.timeTraveling === false) {
|
|
116
|
+
if (storeCurrentSelectorTime !== timelineData.selectorTime) {
|
|
117
|
+
const newSelectorUpdate: TimelineSelectorUpdate = {
|
|
118
|
+
type: `selector_update`,
|
|
119
|
+
key: storeCurrentSelectorKey,
|
|
120
|
+
atomUpdates: [],
|
|
121
|
+
}
|
|
122
|
+
newSelectorUpdate.atomUpdates.push({
|
|
123
|
+
key: token.key,
|
|
124
|
+
type: `atom_update`,
|
|
125
|
+
...update,
|
|
126
|
+
})
|
|
127
|
+
if (timelineData.at !== timelineData.history.length) {
|
|
128
|
+
timelineData.history.splice(timelineData.at)
|
|
129
|
+
}
|
|
130
|
+
timelineData.history.push(newSelectorUpdate)
|
|
131
|
+
|
|
132
|
+
store.config.logger?.info(
|
|
133
|
+
`⌛ timeline "${options.key}" got a selector_update "${storeCurrentSelectorKey}" with`,
|
|
134
|
+
newSelectorUpdate.atomUpdates.map((atomUpdate) => atomUpdate.key)
|
|
135
|
+
)
|
|
136
|
+
timelineData.at = timelineData.history.length
|
|
137
|
+
timelineData.selectorTime = storeCurrentSelectorTime
|
|
138
|
+
} else {
|
|
139
|
+
const latestUpdate = timelineData.history.at(-1)
|
|
140
|
+
if (latestUpdate?.type === `selector_update`) {
|
|
141
|
+
latestUpdate.atomUpdates.push({
|
|
142
|
+
key: token.key,
|
|
143
|
+
type: `atom_update`,
|
|
144
|
+
...update,
|
|
145
|
+
})
|
|
146
|
+
store.config.logger?.info(
|
|
147
|
+
` ⌛ timeline "${options.key}" set selector_update "${storeCurrentSelectorKey}" to`,
|
|
148
|
+
latestUpdate?.atomUpdates.map((atomUpdate) => atomUpdate.key)
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
89
153
|
} else {
|
|
90
154
|
if (timelineData.timeTraveling === false) {
|
|
155
|
+
timelineData.selectorTime = null
|
|
156
|
+
if (timelineData.at !== timelineData.history.length) {
|
|
157
|
+
timelineData.history.splice(timelineData.at)
|
|
158
|
+
}
|
|
91
159
|
timelineData.history.push({
|
|
92
|
-
type: `
|
|
160
|
+
type: `atom_update`,
|
|
93
161
|
key: token.key,
|
|
94
162
|
oldValue: update.oldValue,
|
|
95
163
|
newValue: update.newValue,
|
|
96
164
|
})
|
|
97
165
|
store.config.logger?.info(
|
|
98
|
-
`⌛ timeline "${options.key}"
|
|
166
|
+
`⌛ timeline "${options.key}" got a state_update to "${token.key}"`
|
|
99
167
|
)
|
|
100
168
|
timelineData.at = timelineData.history.length
|
|
101
169
|
}
|
|
102
170
|
}
|
|
103
171
|
})
|
|
104
172
|
}
|
|
105
|
-
|
|
173
|
+
const core = target(store)
|
|
106
174
|
for (const tokenOrFamily of options.atoms) {
|
|
175
|
+
const timelineKey = core.timelineAtoms.getRelatedId(tokenOrFamily.key)
|
|
176
|
+
if (timelineKey) {
|
|
177
|
+
store.config.logger?.error(
|
|
178
|
+
`❌ Failed to add atom "${tokenOrFamily.key}" to timeline "${options.key}" because it belongs to timeline "${timelineKey}"`
|
|
179
|
+
)
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
107
182
|
if (tokenOrFamily.type === `atom_family`) {
|
|
108
183
|
const family = tokenOrFamily
|
|
109
184
|
family.subject.subscribe((token) => subscribeToAtom(token))
|
|
110
185
|
} else {
|
|
111
186
|
const token = tokenOrFamily
|
|
187
|
+
if (`family` in token && token.family) {
|
|
188
|
+
const familyTimelineKey = core.timelineAtoms.getRelatedId(
|
|
189
|
+
token.family.key
|
|
190
|
+
)
|
|
191
|
+
if (familyTimelineKey) {
|
|
192
|
+
store.config.logger?.error(
|
|
193
|
+
`❌ Failed to add atom "${token.key}" to timeline "${options.key}" because its family "${token.family.key}" belongs to timeline "${familyTimelineKey}"`
|
|
194
|
+
)
|
|
195
|
+
continue
|
|
196
|
+
}
|
|
197
|
+
}
|
|
112
198
|
subscribeToAtom(token)
|
|
113
199
|
}
|
|
200
|
+
core.timelineAtoms = core.timelineAtoms.set({
|
|
201
|
+
atomKey: tokenOrFamily.key,
|
|
202
|
+
timelineKey: options.key,
|
|
203
|
+
})
|
|
114
204
|
}
|
|
115
205
|
|
|
116
206
|
store.timelineStore = HAMT.set(options.key, timelineData, store.timelineStore)
|
|
117
|
-
|
|
118
|
-
return {
|
|
207
|
+
const token: TimelineToken = {
|
|
119
208
|
key: options.key,
|
|
120
209
|
type: `timeline`,
|
|
121
210
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
export const redo__INTERNAL = (
|
|
125
|
-
token: TimelineToken,
|
|
126
|
-
store: Store = IMPLICIT.STORE
|
|
127
|
-
): void => {
|
|
128
|
-
const timelineData = store.timelineStore.get(token.key)
|
|
129
|
-
if (!timelineData) {
|
|
130
|
-
store.config.logger?.error(
|
|
131
|
-
`Tried to redo on timeline "${token.key}" has not been initialized.`
|
|
132
|
-
)
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
if (timelineData.at === timelineData.history.length) {
|
|
136
|
-
store.config.logger?.warn(
|
|
137
|
-
`Tried to redo on timeline "${token.key}" but there is nothing to redo.`
|
|
138
|
-
)
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
timelineData.timeTraveling = true
|
|
142
|
-
const update = timelineData.history[timelineData.at]
|
|
143
|
-
switch (update.type) {
|
|
144
|
-
case `state_update`: {
|
|
145
|
-
const { key, newValue } = update
|
|
146
|
-
setState({ key, type: `atom` }, newValue)
|
|
147
|
-
break
|
|
148
|
-
}
|
|
149
|
-
case `transaction_update`: {
|
|
150
|
-
for (const atomUpdate of update.atomUpdates) {
|
|
151
|
-
const { key, newValue } = atomUpdate
|
|
152
|
-
setState({ key, type: `atom` }, newValue)
|
|
153
|
-
}
|
|
154
|
-
break
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
++timelineData.at
|
|
158
|
-
timelineData.timeTraveling = false
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export const undo__INTERNAL = (
|
|
162
|
-
token: TimelineToken,
|
|
163
|
-
store: Store = IMPLICIT.STORE
|
|
164
|
-
): void => {
|
|
165
|
-
const timelineData = store.timelineStore.get(token.key)
|
|
166
|
-
if (!timelineData) {
|
|
167
|
-
store.config.logger?.error(
|
|
168
|
-
`Tried to undo on timeline "${token.key}" has not been initialized.`
|
|
169
|
-
)
|
|
170
|
-
return
|
|
171
|
-
}
|
|
172
|
-
if (timelineData.at === 0) {
|
|
173
|
-
store.config.logger?.warn(
|
|
174
|
-
`Tried to undo on timeline "${token.key}" but there is nothing to undo.`
|
|
175
|
-
)
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
timelineData.timeTraveling = true
|
|
179
|
-
--timelineData.at
|
|
180
|
-
const update = timelineData.history[timelineData.at]
|
|
181
|
-
switch (update.type) {
|
|
182
|
-
case `state_update`: {
|
|
183
|
-
const { key, oldValue } = update
|
|
184
|
-
setState({ key, type: `atom` }, oldValue)
|
|
185
|
-
break
|
|
186
|
-
}
|
|
187
|
-
case `transaction_update`: {
|
|
188
|
-
for (const atomUpdate of update.atomUpdates) {
|
|
189
|
-
const { key, oldValue } = atomUpdate
|
|
190
|
-
setState({ key, type: `atom` }, oldValue)
|
|
191
|
-
}
|
|
192
|
-
break
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
timelineData.timeTraveling = false
|
|
211
|
+
store.subject.timelineCreation.next(token)
|
|
212
|
+
return token
|
|
196
213
|
}
|
|
@@ -2,7 +2,7 @@ import HAMT from "hamt_plus"
|
|
|
2
2
|
import * as Rx from "rxjs"
|
|
3
3
|
|
|
4
4
|
import type { Store, StoreCore } from "."
|
|
5
|
-
import { deposit, withdraw, IMPLICIT } from "."
|
|
5
|
+
import { cacheValue, deposit, withdraw, IMPLICIT } from "."
|
|
6
6
|
import { getState, setState } from ".."
|
|
7
7
|
import type {
|
|
8
8
|
AtomToken,
|
|
@@ -75,14 +75,22 @@ export const applyTransaction = <ƒ extends ƒn>(
|
|
|
75
75
|
return
|
|
76
76
|
}
|
|
77
77
|
store.config.logger?.info(
|
|
78
|
-
|
|
78
|
+
`🛃 apply transaction "${store.transactionStatus.key}"`
|
|
79
79
|
)
|
|
80
80
|
store.transactionStatus.phase = `applying`
|
|
81
81
|
store.transactionStatus.output = output
|
|
82
82
|
const { atomUpdates } = store.transactionStatus
|
|
83
|
-
|
|
83
|
+
|
|
84
|
+
for (const { key, newValue } of atomUpdates) {
|
|
84
85
|
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
86
|
+
if (!HAMT.has(token.key, store.valueMap)) {
|
|
87
|
+
const atom = HAMT.get(token.key, store.transactionStatus.core.atoms)
|
|
88
|
+
store.atoms = HAMT.set(atom.key, atom, store.atoms)
|
|
89
|
+
store.valueMap = HAMT.set(atom.key, atom.default, store.valueMap)
|
|
90
|
+
store.config.logger?.info(`🔧`, `add atom "${atom.key}"`)
|
|
91
|
+
}
|
|
85
92
|
const state = withdraw(token, store)
|
|
93
|
+
|
|
86
94
|
setState(state, newValue, store)
|
|
87
95
|
}
|
|
88
96
|
const myTransaction = withdraw<ƒ>(
|
|
@@ -103,7 +111,7 @@ export const undoTransactionUpdate = <ƒ extends ƒn>(
|
|
|
103
111
|
store: Store
|
|
104
112
|
): void => {
|
|
105
113
|
store.config.logger?.info(` ⏮ undo transaction "${update.key}" (undo)`)
|
|
106
|
-
for (const { key, oldValue
|
|
114
|
+
for (const { key, oldValue } of update.atomUpdates) {
|
|
107
115
|
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
108
116
|
const state = withdraw(token, store)
|
|
109
117
|
setState(state, oldValue, store)
|
|
@@ -114,7 +122,7 @@ export const redoTransactionUpdate = <ƒ extends ƒn>(
|
|
|
114
122
|
store: Store
|
|
115
123
|
): void => {
|
|
116
124
|
store.config.logger?.info(` ⏭ redo transaction "${update.key}" (redo)`)
|
|
117
|
-
for (const { key,
|
|
125
|
+
for (const { key, newValue } of update.atomUpdates) {
|
|
118
126
|
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
119
127
|
const state = withdraw(token, store)
|
|
120
128
|
setState(state, newValue, store)
|
|
@@ -166,6 +174,7 @@ export function transaction__INTERNAL<ƒ extends ƒn>(
|
|
|
166
174
|
core.transactions
|
|
167
175
|
)
|
|
168
176
|
const token = deposit(newTransaction)
|
|
177
|
+
store.subject.transactionCreation.next(token)
|
|
169
178
|
return token
|
|
170
179
|
}
|
|
171
180
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { doNothing } from "~/packages/anvl/src/function"
|
|
2
2
|
|
|
3
|
-
import type { Store } from "./store"
|
|
4
|
-
import { IMPLICIT } from "./store"
|
|
3
|
+
import type { Store } from "./internal/store"
|
|
4
|
+
import { IMPLICIT } from "./internal/store"
|
|
5
5
|
|
|
6
6
|
export type Logger = Pick<Console, `error` | `info` | `warn`>
|
|
7
7
|
export const LOG_LEVELS: ReadonlyArray<keyof Logger> = [
|
package/src/react/index.ts
CHANGED
|
@@ -1,64 +1,46 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useSyncExternalStore } from "react"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import { subscribe, setState, __INTERNAL__ } from "atom.io"
|
|
6
|
-
import type { ReadonlyValueToken, StateToken } from "atom.io"
|
|
3
|
+
import { subscribe, setState, __INTERNAL__, getState } from "atom.io"
|
|
4
|
+
import type { ReadonlySelectorToken, StateToken } from "atom.io"
|
|
7
5
|
|
|
8
6
|
import type { Modifier } from "~/packages/anvl/src/function"
|
|
9
7
|
|
|
10
|
-
export type
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
export type StoreHooks = {
|
|
9
|
+
useI: <T>(token: StateToken<T>) => (next: Modifier<T> | T) => void
|
|
10
|
+
useO: <T>(token: ReadonlySelectorToken<T> | StateToken<T>) => T
|
|
11
|
+
useIO: <T>(token: StateToken<T>) => [T, (next: Modifier<T> | T) => void]
|
|
14
12
|
}
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
useEffect,
|
|
20
|
-
store = __INTERNAL__.IMPLICIT.STORE,
|
|
21
|
-
}: AtomStoreReactConfig) => {
|
|
14
|
+
export const composeStoreHooks = (
|
|
15
|
+
store: __INTERNAL__.Store = __INTERNAL__.IMPLICIT.STORE
|
|
16
|
+
): StoreHooks => {
|
|
22
17
|
function useI<T>(token: StateToken<T>): (next: Modifier<T> | T) => void {
|
|
23
18
|
const updateState = (next: Modifier<T> | T) => setState(token, next, store)
|
|
24
19
|
return updateState
|
|
25
20
|
}
|
|
26
21
|
|
|
27
|
-
function useO<T>(token:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const unsubscribe = subscribe(
|
|
33
|
-
token,
|
|
34
|
-
({ newValue, oldValue }) => {
|
|
35
|
-
if (oldValue !== newValue) {
|
|
36
|
-
dispatch(newValue)
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
store
|
|
40
|
-
)
|
|
41
|
-
return unsubscribe
|
|
42
|
-
}, [])
|
|
43
|
-
|
|
44
|
-
return current
|
|
22
|
+
function useO<T>(token: ReadonlySelectorToken<T> | StateToken<T>): T {
|
|
23
|
+
return useSyncExternalStore<T>(
|
|
24
|
+
(observe) => subscribe(token, observe, store),
|
|
25
|
+
() => getState(token, store)
|
|
26
|
+
)
|
|
45
27
|
}
|
|
46
28
|
|
|
47
29
|
function useIO<T>(token: StateToken<T>): [T, (next: Modifier<T> | T) => void] {
|
|
48
30
|
return [useO(token), useI(token)]
|
|
49
31
|
}
|
|
50
32
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return
|
|
33
|
+
return { useI, useO, useIO }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const { useI, useO, useIO } = composeStoreHooks()
|
|
37
|
+
|
|
38
|
+
export function useStore<T>(
|
|
39
|
+
token: StateToken<T>
|
|
40
|
+
): [T, (next: Modifier<T> | T) => void]
|
|
41
|
+
export function useStore<T>(token: ReadonlySelectorToken<T>): T
|
|
42
|
+
export function useStore<T>(
|
|
43
|
+
token: ReadonlySelectorToken<T> | StateToken<T>
|
|
44
|
+
): T | [T, (next: Modifier<T> | T) => void] {
|
|
45
|
+
return token.type === `readonly_selector` ? useO(token) : useIO(token)
|
|
64
46
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { FC } from "react"
|
|
2
|
+
import { useRef } from "react"
|
|
3
|
+
|
|
4
|
+
import { atom, __INTERNAL__ } from "atom.io"
|
|
5
|
+
import { useI, useO, useIO } from "atom.io/react"
|
|
6
|
+
import type { StoreHooks } from "atom.io/react"
|
|
7
|
+
import { LayoutGroup, motion, spring } from "framer-motion"
|
|
8
|
+
|
|
9
|
+
import { TokenList } from "./TokenList"
|
|
10
|
+
import { lazyLocalStorageEffect } from "../web-effects"
|
|
11
|
+
|
|
12
|
+
import "./devtools.scss"
|
|
13
|
+
|
|
14
|
+
const { atomTokenIndexState, selectorTokenIndexState } =
|
|
15
|
+
__INTERNAL__.META.attachMetaState()
|
|
16
|
+
|
|
17
|
+
const devtoolsAreOpenState = atom<boolean>({
|
|
18
|
+
key: `👁🗨_devtools_are_open`,
|
|
19
|
+
default: true,
|
|
20
|
+
effects: [lazyLocalStorageEffect(`👁🗨_devtools_are_open`)],
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export const composeDevtools = (storeHooks: StoreHooks): FC => {
|
|
24
|
+
const Devtools: FC = () => {
|
|
25
|
+
const constraintsRef = useRef(null)
|
|
26
|
+
|
|
27
|
+
const [devtoolsAreOpen, setDevtoolsAreOpen] =
|
|
28
|
+
storeHooks.useIO(devtoolsAreOpenState)
|
|
29
|
+
|
|
30
|
+
const mouseHasMoved = useRef(false)
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<motion.span
|
|
35
|
+
ref={constraintsRef}
|
|
36
|
+
className="atom_io_devtools_zone"
|
|
37
|
+
style={{
|
|
38
|
+
position: `fixed`,
|
|
39
|
+
top: 0,
|
|
40
|
+
left: 0,
|
|
41
|
+
right: 0,
|
|
42
|
+
bottom: 0,
|
|
43
|
+
pointerEvents: `none`,
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
<motion.main
|
|
47
|
+
drag
|
|
48
|
+
dragConstraints={constraintsRef}
|
|
49
|
+
className="atom_io_devtools"
|
|
50
|
+
transition={spring}
|
|
51
|
+
style={
|
|
52
|
+
devtoolsAreOpen
|
|
53
|
+
? {}
|
|
54
|
+
: {
|
|
55
|
+
backgroundColor: `#0000`,
|
|
56
|
+
borderColor: `#0000`,
|
|
57
|
+
maxHeight: 28,
|
|
58
|
+
maxWidth: 33,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
>
|
|
62
|
+
{devtoolsAreOpen ? (
|
|
63
|
+
<>
|
|
64
|
+
<motion.header>
|
|
65
|
+
<h1>atom.io</h1>
|
|
66
|
+
</motion.header>
|
|
67
|
+
<motion.main>
|
|
68
|
+
<LayoutGroup>
|
|
69
|
+
<section>
|
|
70
|
+
<h2>atoms</h2>
|
|
71
|
+
<TokenList
|
|
72
|
+
storeHooks={storeHooks}
|
|
73
|
+
tokenIndex={atomTokenIndexState}
|
|
74
|
+
/>
|
|
75
|
+
</section>
|
|
76
|
+
<section>
|
|
77
|
+
<h2>selectors</h2>
|
|
78
|
+
<TokenList
|
|
79
|
+
storeHooks={storeHooks}
|
|
80
|
+
tokenIndex={selectorTokenIndexState}
|
|
81
|
+
/>
|
|
82
|
+
</section>
|
|
83
|
+
</LayoutGroup>
|
|
84
|
+
</motion.main>
|
|
85
|
+
</>
|
|
86
|
+
) : null}
|
|
87
|
+
<footer>
|
|
88
|
+
<button
|
|
89
|
+
onMouseDown={() => (mouseHasMoved.current = false)}
|
|
90
|
+
onMouseMove={() => (mouseHasMoved.current = true)}
|
|
91
|
+
onMouseUp={() => {
|
|
92
|
+
if (!mouseHasMoved.current) {
|
|
93
|
+
setDevtoolsAreOpen((open) => !open)
|
|
94
|
+
}
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
👁🗨
|
|
98
|
+
</button>
|
|
99
|
+
</footer>
|
|
100
|
+
</motion.main>
|
|
101
|
+
</>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
return Devtools
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const AtomIODevtools = composeDevtools({ useI, useO, useIO })
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { FC } from "react"
|
|
2
|
+
|
|
3
|
+
import type { ReadonlySelectorToken, StateToken } from "atom.io"
|
|
4
|
+
import type { StoreHooks } from "atom.io/react"
|
|
5
|
+
|
|
6
|
+
import { isPlainJson } from "~/packages/anvl/src/json"
|
|
7
|
+
import { ElasticInput } from "~/packages/hamr/src/react-elastic-input"
|
|
8
|
+
import { JsonEditor } from "~/packages/hamr/src/react-json-editor"
|
|
9
|
+
|
|
10
|
+
export const StateEditor: FC<{
|
|
11
|
+
storeHooks: StoreHooks
|
|
12
|
+
token: StateToken<unknown>
|
|
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
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const ReadonlySelectorEditor: FC<{
|
|
36
|
+
storeHooks: StoreHooks
|
|
37
|
+
token: ReadonlySelectorToken<unknown>
|
|
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
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const StoreEditor: FC<{
|
|
66
|
+
storeHooks: StoreHooks
|
|
67
|
+
token: ReadonlySelectorToken<unknown> | StateToken<unknown>
|
|
68
|
+
}> = ({ storeHooks, token }) => {
|
|
69
|
+
if (token.type === `readonly_selector`) {
|
|
70
|
+
return <ReadonlySelectorEditor storeHooks={storeHooks} token={token} />
|
|
71
|
+
}
|
|
72
|
+
return <StateEditor storeHooks={storeHooks} token={token} />
|
|
73
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { FC } from "react"
|
|
2
|
+
import { Fragment } from "react"
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
AtomToken,
|
|
6
|
+
ReadonlySelectorToken,
|
|
7
|
+
SelectorToken,
|
|
8
|
+
__INTERNAL__,
|
|
9
|
+
} from "atom.io"
|
|
10
|
+
import type { StoreHooks } from "atom.io/react"
|
|
11
|
+
|
|
12
|
+
import { recordToEntries } from "~/packages/anvl/src/object"
|
|
13
|
+
|
|
14
|
+
import { StoreEditor } from "./StateEditor"
|
|
15
|
+
|
|
16
|
+
export const TokenList: FC<{
|
|
17
|
+
storeHooks: StoreHooks
|
|
18
|
+
tokenIndex: ReadonlySelectorToken<
|
|
19
|
+
__INTERNAL__.META.StateTokenIndex<
|
|
20
|
+
| AtomToken<unknown>
|
|
21
|
+
| ReadonlySelectorToken<unknown>
|
|
22
|
+
| SelectorToken<unknown>
|
|
23
|
+
>
|
|
24
|
+
>
|
|
25
|
+
}> = ({ storeHooks, tokenIndex }) => {
|
|
26
|
+
const tokenIds = storeHooks.useO(tokenIndex)
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
{Object.entries(tokenIds).map(([key, token]) => (
|
|
30
|
+
<Fragment key={key}>
|
|
31
|
+
{key.startsWith(`👁🗨_`) ? null : (
|
|
32
|
+
<div className="node">
|
|
33
|
+
{key}:
|
|
34
|
+
{`type` in token ? (
|
|
35
|
+
<StoreEditor storeHooks={storeHooks} token={token} />
|
|
36
|
+
) : (
|
|
37
|
+
recordToEntries(token.familyMembers).map(([key, token]) => (
|
|
38
|
+
<div key={key} className="node">
|
|
39
|
+
{key}:<StoreEditor storeHooks={storeHooks} token={token} />
|
|
40
|
+
</div>
|
|
41
|
+
))
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
</Fragment>
|
|
46
|
+
))}
|
|
47
|
+
</>
|
|
48
|
+
)
|
|
49
|
+
}
|