1 | import { useContext, useState, useEffect, useDebugValue, useRef, useCallback } from 'react';
|
2 | import { SECRET_INTERNAL_getScopeContext, useAtom } from 'jotai';
|
3 |
|
4 | const RESTORE_ATOMS = "h";
|
5 | const DEV_SUBSCRIBE_STATE = "n";
|
6 | const DEV_GET_MOUNTED_ATOMS = "l";
|
7 | const DEV_GET_ATOM_STATE = "a";
|
8 | const DEV_GET_MOUNTED = "m";
|
9 |
|
10 | const atomToPrintable$1 = (atom) => atom.debugLabel || atom.toString();
|
11 | const stateToPrintable = ([store, atoms]) => Object.fromEntries(
|
12 | atoms.flatMap((atom) => {
|
13 | var _a, _b;
|
14 | const mounted = (_a = store[DEV_GET_MOUNTED]) == null ? void 0 : _a.call(store, atom);
|
15 | if (!mounted) {
|
16 | return [];
|
17 | }
|
18 | const dependents = mounted.t;
|
19 | const atomState = ((_b = store[DEV_GET_ATOM_STATE]) == null ? void 0 : _b.call(store, atom)) || {};
|
20 | return [
|
21 | [
|
22 | atomToPrintable$1(atom),
|
23 | {
|
24 | ..."e" in atomState && { error: atomState.e },
|
25 | ..."p" in atomState && { promise: atomState.p },
|
26 | ..."v" in atomState && { value: atomState.v },
|
27 | dependents: Array.from(dependents).map(atomToPrintable$1)
|
28 | }
|
29 | ]
|
30 | ];
|
31 | })
|
32 | );
|
33 | const useAtomsDebugValue = (options) => {
|
34 | var _a;
|
35 | const enabled = (_a = options == null ? void 0 : options.enabled) != null ? _a : (import.meta.env && import.meta.env.MODE) !== "production";
|
36 | const ScopeContext = SECRET_INTERNAL_getScopeContext(options == null ? void 0 : options.scope);
|
37 | const { s: store } = useContext(ScopeContext);
|
38 | const [atoms, setAtoms] = useState([]);
|
39 | useEffect(() => {
|
40 | var _a2;
|
41 | if (!enabled) {
|
42 | return;
|
43 | }
|
44 | const callback = () => {
|
45 | var _a3;
|
46 | setAtoms(Array.from(((_a3 = store[DEV_GET_MOUNTED_ATOMS]) == null ? void 0 : _a3.call(store)) || []));
|
47 | };
|
48 | const unsubscribe = (_a2 = store[DEV_SUBSCRIBE_STATE]) == null ? void 0 : _a2.call(store, callback);
|
49 | callback();
|
50 | return unsubscribe;
|
51 | }, [enabled, store]);
|
52 | useDebugValue([store, atoms], stateToPrintable);
|
53 | };
|
54 |
|
55 | function useAtomDevtools(anAtom, options, deprecatedScope) {
|
56 | if (typeof options === "string" || typeof deprecatedScope !== "undefined") {
|
57 | console.warn("DEPRECATED [useAtomDevtools] use DevtoolOptions");
|
58 | options = {
|
59 | name: options,
|
60 | scope: deprecatedScope
|
61 | };
|
62 | }
|
63 | const { enabled, name, scope } = options || {};
|
64 | let extension;
|
65 | try {
|
66 | extension = (enabled != null ? enabled : (import.meta.env && import.meta.env.MODE) !== "production") && window.__REDUX_DEVTOOLS_EXTENSION__;
|
67 | } catch {
|
68 | }
|
69 | if (!extension) {
|
70 | if ((import.meta.env && import.meta.env.MODE) !== "production" && enabled) {
|
71 | console.warn("Please install/enable Redux devtools extension");
|
72 | }
|
73 | }
|
74 | const [value, setValue] = useAtom(anAtom, scope);
|
75 | const lastValue = useRef(value);
|
76 | const isTimeTraveling = useRef(false);
|
77 | const devtools = useRef();
|
78 | const atomName = name || anAtom.debugLabel || anAtom.toString();
|
79 | useEffect(() => {
|
80 | if (!extension) {
|
81 | return;
|
82 | }
|
83 | const setValueIfWritable = (value2) => {
|
84 | if (typeof setValue === "function") {
|
85 | setValue(value2);
|
86 | return;
|
87 | }
|
88 | console.warn(
|
89 | "[Warn] you cannot do write operations (Time-travelling, etc) in read-only atoms\n",
|
90 | anAtom
|
91 | );
|
92 | };
|
93 | devtools.current = extension.connect({ name: atomName });
|
94 | const unsubscribe = devtools.current.subscribe((message) => {
|
95 | var _a, _b, _c, _d, _e, _f;
|
96 | if (message.type === "ACTION" && message.payload) {
|
97 | try {
|
98 | setValueIfWritable(JSON.parse(message.payload));
|
99 | } catch (e) {
|
100 | console.error(
|
101 | "please dispatch a serializable value that JSON.parse() support\n",
|
102 | e
|
103 | );
|
104 | }
|
105 | } else if (message.type === "DISPATCH" && message.state) {
|
106 | if (((_a = message.payload) == null ? void 0 : _a.type) === "JUMP_TO_ACTION" || ((_b = message.payload) == null ? void 0 : _b.type) === "JUMP_TO_STATE") {
|
107 | isTimeTraveling.current = true;
|
108 | setValueIfWritable(JSON.parse(message.state));
|
109 | }
|
110 | } else if (message.type === "DISPATCH" && ((_c = message.payload) == null ? void 0 : _c.type) === "COMMIT") {
|
111 | (_d = devtools.current) == null ? void 0 : _d.init(lastValue.current);
|
112 | } else if (message.type === "DISPATCH" && ((_e = message.payload) == null ? void 0 : _e.type) === "IMPORT_STATE") {
|
113 | const computedStates = ((_f = message.payload.nextLiftedState) == null ? void 0 : _f.computedStates) || [];
|
114 | computedStates.forEach(({ state }, index) => {
|
115 | var _a2;
|
116 | if (index === 0) {
|
117 | (_a2 = devtools.current) == null ? void 0 : _a2.init(state);
|
118 | } else {
|
119 | setValueIfWritable(state);
|
120 | }
|
121 | });
|
122 | }
|
123 | });
|
124 | devtools.current.shouldInit = true;
|
125 | return unsubscribe;
|
126 | }, [anAtom, extension, atomName, setValue]);
|
127 | useEffect(() => {
|
128 | if (!devtools.current) {
|
129 | return;
|
130 | }
|
131 | lastValue.current = value;
|
132 | if (devtools.current.shouldInit) {
|
133 | devtools.current.init(value);
|
134 | devtools.current.shouldInit = false;
|
135 | } else if (isTimeTraveling.current) {
|
136 | isTimeTraveling.current = false;
|
137 | } else {
|
138 | devtools.current.send(
|
139 | `${atomName} - ${new Date().toLocaleString()}`,
|
140 | value
|
141 | );
|
142 | }
|
143 | }, [anAtom, extension, atomName, value]);
|
144 | }
|
145 |
|
146 | const isEqualAtomsValues$1 = (left, right) => left.size === right.size && Array.from(left).every(([left2, v]) => Object.is(right.get(left2), v));
|
147 | const isEqualAtomsDependents$1 = (left, right) => left.size === right.size && Array.from(left).every(([a, dLeft]) => {
|
148 | const dRight = right.get(a);
|
149 | return dRight && dLeft.size === dRight.size && Array.from(dLeft).every((d) => dRight.has(d));
|
150 | });
|
151 | function useAtomsSnapshot(scope) {
|
152 | const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
|
153 | const scopeContainer = useContext(ScopeContext);
|
154 | const store = scopeContainer.s;
|
155 | if (!store[DEV_SUBSCRIBE_STATE]) {
|
156 | throw new Error("useAtomsSnapshot can only be used in dev mode.");
|
157 | }
|
158 | const [atomsSnapshot, setAtomsSnapshot] = useState(() => ({
|
159 | values: new Map(),
|
160 | dependents: new Map()
|
161 | }));
|
162 | useEffect(() => {
|
163 | var _a;
|
164 | let prevValues = new Map();
|
165 | let prevDependents = new Map();
|
166 | const invalidatedAtoms = new Set();
|
167 | const callback = () => {
|
168 | var _a2, _b, _c;
|
169 | const values = new Map();
|
170 | const dependents = new Map();
|
171 | let hasNewInvalidatedAtoms = false;
|
172 | for (const atom of ((_a2 = store[DEV_GET_MOUNTED_ATOMS]) == null ? void 0 : _a2.call(store)) || []) {
|
173 | const atomState = (_b = store[DEV_GET_ATOM_STATE]) == null ? void 0 : _b.call(store, atom);
|
174 | if (atomState) {
|
175 | if (!atomState.y) {
|
176 | if ("p" in atomState) {
|
177 | return;
|
178 | }
|
179 | if (!invalidatedAtoms.has(atom)) {
|
180 | invalidatedAtoms.add(atom);
|
181 | hasNewInvalidatedAtoms = true;
|
182 | }
|
183 | }
|
184 | if ("v" in atomState) {
|
185 | values.set(atom, atomState.v);
|
186 | }
|
187 | }
|
188 | const mounted = (_c = store[DEV_GET_MOUNTED]) == null ? void 0 : _c.call(store, atom);
|
189 | if (mounted) {
|
190 | dependents.set(atom, mounted.t);
|
191 | }
|
192 | }
|
193 | if (hasNewInvalidatedAtoms) {
|
194 | return;
|
195 | }
|
196 | if (isEqualAtomsValues$1(prevValues, values) && isEqualAtomsDependents$1(prevDependents, dependents)) {
|
197 | return;
|
198 | }
|
199 | prevValues = values;
|
200 | prevDependents = dependents;
|
201 | invalidatedAtoms.clear();
|
202 | setAtomsSnapshot({ values, dependents });
|
203 | };
|
204 | const unsubscribe = (_a = store[DEV_SUBSCRIBE_STATE]) == null ? void 0 : _a.call(store, callback);
|
205 | callback();
|
206 | return unsubscribe;
|
207 | }, [store]);
|
208 | return atomsSnapshot;
|
209 | }
|
210 |
|
211 | function useGotoAtomsSnapshot(scope) {
|
212 | const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
|
213 | const { s: store, w: versionedWrite } = useContext(ScopeContext);
|
214 | if (!store[DEV_SUBSCRIBE_STATE]) {
|
215 | throw new Error("useGotoAtomsSnapshot can only be used in dev mode.");
|
216 | }
|
217 | return useCallback(
|
218 | (snapshot) => {
|
219 | const restoreAtoms = (values) => {
|
220 | if (versionedWrite) {
|
221 | versionedWrite((version) => {
|
222 | store[RESTORE_ATOMS](values, version);
|
223 | });
|
224 | } else {
|
225 | store[RESTORE_ATOMS](values);
|
226 | }
|
227 | };
|
228 | if (isIterable(snapshot)) {
|
229 | if ((import.meta.env && import.meta.env.MODE) !== "production") {
|
230 | console.warn(
|
231 | "snapshot as iterable is deprecated. use an object instead."
|
232 | );
|
233 | }
|
234 | restoreAtoms(snapshot);
|
235 | return;
|
236 | }
|
237 | restoreAtoms(snapshot.values);
|
238 | },
|
239 | [store, versionedWrite]
|
240 | );
|
241 | }
|
242 | const isIterable = (item) => {
|
243 | return typeof item[Symbol.iterator] === "function";
|
244 | };
|
245 |
|
246 | const isEqualAtomsValues = (left, right) => left.size === right.size && Array.from(left).every(([left2, v]) => Object.is(right.get(left2), v));
|
247 | const isEqualAtomsDependents = (left, right) => left.size === right.size && Array.from(left).every(([a, dLeft]) => {
|
248 | const dRight = right.get(a);
|
249 | return dRight && dLeft.size === dRight.size && Array.from(dLeft).every((d) => dRight.has(d));
|
250 | });
|
251 | const atomToPrintable = (atom) => atom.debugLabel ? `${atom}:${atom.debugLabel}` : `${atom}`;
|
252 | const getDevtoolsState = (atomsSnapshot) => {
|
253 | const values = {};
|
254 | atomsSnapshot.values.forEach((v, atom) => {
|
255 | values[atomToPrintable(atom)] = v;
|
256 | });
|
257 | const dependents = {};
|
258 | atomsSnapshot.dependents.forEach((d, atom) => {
|
259 | dependents[atomToPrintable(atom)] = Array.from(d).map(atomToPrintable);
|
260 | });
|
261 | return {
|
262 | values,
|
263 | dependents
|
264 | };
|
265 | };
|
266 | function useAtomsDevtools(name, options) {
|
267 | if (typeof options !== "undefined" && typeof options !== "object") {
|
268 | console.warn("DEPRECATED [useAtomsDevtools] use DevtoolsOptions");
|
269 | options = { scope: options };
|
270 | }
|
271 | const { enabled, scope } = options || {};
|
272 | const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
|
273 | const { s: store, w: versionedWrite } = useContext(ScopeContext);
|
274 | let extension;
|
275 | try {
|
276 | extension = (enabled != null ? enabled : (import.meta.env && import.meta.env.MODE) !== "production") && window.__REDUX_DEVTOOLS_EXTENSION__;
|
277 | } catch {
|
278 | }
|
279 | if (!extension) {
|
280 | if ((import.meta.env && import.meta.env.MODE) !== "production" && enabled) {
|
281 | console.warn("Please install/enable Redux devtools extension");
|
282 | }
|
283 | }
|
284 | if (extension && !store[DEV_SUBSCRIBE_STATE]) {
|
285 | throw new Error("useAtomsDevtools can only be used in dev mode.");
|
286 | }
|
287 | const [atomsSnapshot, setAtomsSnapshot] = useState(() => ({
|
288 | values: new Map(),
|
289 | dependents: new Map()
|
290 | }));
|
291 | useEffect(() => {
|
292 | var _a;
|
293 | if (!extension) {
|
294 | return;
|
295 | }
|
296 | let prevValues = new Map();
|
297 | let prevDependents = new Map();
|
298 | const invalidatedAtoms = new Set();
|
299 | const callback = () => {
|
300 | var _a2, _b, _c;
|
301 | const values = new Map();
|
302 | const dependents = new Map();
|
303 | let hasNewInvalidatedAtoms = false;
|
304 | for (const atom of ((_a2 = store[DEV_GET_MOUNTED_ATOMS]) == null ? void 0 : _a2.call(store)) || []) {
|
305 | const atomState = (_b = store[DEV_GET_ATOM_STATE]) == null ? void 0 : _b.call(store, atom);
|
306 | if (atomState) {
|
307 | if (!atomState.y) {
|
308 | if ("p" in atomState) {
|
309 | return;
|
310 | }
|
311 | if (!invalidatedAtoms.has(atom)) {
|
312 | invalidatedAtoms.add(atom);
|
313 | hasNewInvalidatedAtoms = true;
|
314 | }
|
315 | }
|
316 | if ("v" in atomState) {
|
317 | values.set(atom, atomState.v);
|
318 | }
|
319 | }
|
320 | const mounted = (_c = store[DEV_GET_MOUNTED]) == null ? void 0 : _c.call(store, atom);
|
321 | if (mounted) {
|
322 | dependents.set(atom, mounted.t);
|
323 | }
|
324 | }
|
325 | if (hasNewInvalidatedAtoms) {
|
326 | return;
|
327 | }
|
328 | if (isEqualAtomsValues(prevValues, values) && isEqualAtomsDependents(prevDependents, dependents)) {
|
329 | return;
|
330 | }
|
331 | prevValues = values;
|
332 | prevDependents = dependents;
|
333 | invalidatedAtoms.clear();
|
334 | setAtomsSnapshot({ values, dependents });
|
335 | };
|
336 | const unsubscribe = (_a = store[DEV_SUBSCRIBE_STATE]) == null ? void 0 : _a.call(store, callback);
|
337 | callback();
|
338 | return unsubscribe;
|
339 | }, [extension, store]);
|
340 | const goToSnapshot = useCallback(
|
341 | (snapshot) => {
|
342 | const { values } = snapshot;
|
343 | if (versionedWrite) {
|
344 | versionedWrite((version) => {
|
345 | store[RESTORE_ATOMS](values, version);
|
346 | });
|
347 | } else {
|
348 | store[RESTORE_ATOMS](values);
|
349 | }
|
350 | },
|
351 | [store, versionedWrite]
|
352 | );
|
353 | const isTimeTraveling = useRef(false);
|
354 | const isRecording = useRef(true);
|
355 | const devtools = useRef();
|
356 | const snapshots = useRef([]);
|
357 | useEffect(() => {
|
358 | if (!extension) {
|
359 | return;
|
360 | }
|
361 | const getSnapshotAt = (index = snapshots.current.length - 1) => {
|
362 | const snapshot = snapshots.current[index >= 0 ? index : 0];
|
363 | if (!snapshot) {
|
364 | throw new Error("snaphost index out of bounds");
|
365 | }
|
366 | return snapshot;
|
367 | };
|
368 | const connection = extension.connect({ name });
|
369 | const devtoolsUnsubscribe = connection.subscribe((message) => {
|
370 | var _a;
|
371 | switch (message.type) {
|
372 | case "DISPATCH":
|
373 | switch ((_a = message.payload) == null ? void 0 : _a.type) {
|
374 | case "RESET":
|
375 | break;
|
376 | case "COMMIT":
|
377 | connection.init(getDevtoolsState(getSnapshotAt()));
|
378 | snapshots.current = [];
|
379 | break;
|
380 | case "JUMP_TO_ACTION":
|
381 | case "JUMP_TO_STATE":
|
382 | isTimeTraveling.current = true;
|
383 | goToSnapshot(getSnapshotAt(message.payload.actionId - 1));
|
384 | break;
|
385 | case "PAUSE_RECORDING":
|
386 | isRecording.current = !isRecording.current;
|
387 | break;
|
388 | }
|
389 | }
|
390 | });
|
391 | devtools.current = connection;
|
392 | devtools.current.shouldInit = true;
|
393 | return devtoolsUnsubscribe;
|
394 | }, [extension, goToSnapshot, name]);
|
395 | useEffect(() => {
|
396 | if (!devtools.current) {
|
397 | return;
|
398 | }
|
399 | if (devtools.current.shouldInit) {
|
400 | devtools.current.init(void 0);
|
401 | devtools.current.shouldInit = false;
|
402 | return;
|
403 | }
|
404 | if (isTimeTraveling.current) {
|
405 | isTimeTraveling.current = false;
|
406 | } else if (isRecording.current) {
|
407 | snapshots.current.push(atomsSnapshot);
|
408 | devtools.current.send(
|
409 | {
|
410 | type: `${snapshots.current.length}`,
|
411 | updatedAt: new Date().toLocaleString()
|
412 | },
|
413 | getDevtoolsState(atomsSnapshot)
|
414 | );
|
415 | }
|
416 | }, [atomsSnapshot]);
|
417 | }
|
418 |
|
419 | export { useAtomDevtools, useAtomsDebugValue, useAtomsDevtools, useAtomsSnapshot, useGotoAtomsSnapshot };
|