UNPKG

11.8 kBJavaScriptView Raw
1import { useContext, useState, useEffect, useDebugValue, useRef, useCallback } from 'react';
2import { SECRET_INTERNAL_getScopeContext, useAtom } from 'jotai';
3
4const RESTORE_ATOMS = "h";
5const DEV_SUBSCRIBE_STATE = "n";
6const DEV_GET_MOUNTED_ATOMS = "l";
7const DEV_GET_ATOM_STATE = "a";
8const DEV_GET_MOUNTED = "m";
9
10const atomToPrintable$1 = (atom) => atom.debugLabel || atom.toString();
11const 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);
33const 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
55function 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
146const isEqualAtomsValues = (left, right) => left.size === right.size && Array.from(left).every(([left2, v]) => Object.is(right.get(left2), v));
147const isEqualAtomsDependents = (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});
151function useAtomsSnapshot(scope) {
152 const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
153 const scopeContainer = useContext(ScopeContext);
154 const store = scopeContainer.s;
155 const [atomsSnapshot, setAtomsSnapshot] = useState(() => ({
156 values: /* @__PURE__ */ new Map(),
157 dependents: /* @__PURE__ */ new Map()
158 }));
159 useEffect(() => {
160 if (!store[DEV_SUBSCRIBE_STATE])
161 return;
162 let prevValues = /* @__PURE__ */ new Map();
163 let prevDependents = /* @__PURE__ */ new Map();
164 const invalidatedAtoms = /* @__PURE__ */ new Set();
165 const callback = () => {
166 const values = /* @__PURE__ */ new Map();
167 const dependents = /* @__PURE__ */ new Map();
168 let hasNewInvalidatedAtoms = false;
169 for (const atom of store[DEV_GET_MOUNTED_ATOMS]() || []) {
170 const atomState = store[DEV_GET_ATOM_STATE](atom);
171 if (atomState) {
172 if (!atomState.y) {
173 if ("p" in atomState) {
174 return;
175 }
176 if (!invalidatedAtoms.has(atom)) {
177 invalidatedAtoms.add(atom);
178 hasNewInvalidatedAtoms = true;
179 }
180 }
181 if ("v" in atomState) {
182 values.set(atom, atomState.v);
183 }
184 }
185 const mounted = store[DEV_GET_MOUNTED](atom);
186 if (mounted) {
187 dependents.set(atom, mounted.t);
188 }
189 }
190 if (hasNewInvalidatedAtoms) {
191 return;
192 }
193 if (isEqualAtomsValues(prevValues, values) && isEqualAtomsDependents(prevDependents, dependents)) {
194 return;
195 }
196 prevValues = values;
197 prevDependents = dependents;
198 invalidatedAtoms.clear();
199 setAtomsSnapshot({ values, dependents });
200 };
201 const unsubscribe = store[DEV_SUBSCRIBE_STATE](callback);
202 callback();
203 return unsubscribe;
204 }, [store]);
205 return atomsSnapshot;
206}
207
208function useGotoAtomsSnapshot(scope) {
209 const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
210 const { s: store, w: versionedWrite } = useContext(ScopeContext);
211 return useCallback(
212 (snapshot) => {
213 if (!store[DEV_SUBSCRIBE_STATE])
214 return;
215 const restoreAtoms = (values) => {
216 if (versionedWrite) {
217 versionedWrite((version) => {
218 store[RESTORE_ATOMS](values, version);
219 });
220 } else {
221 store[RESTORE_ATOMS](values);
222 }
223 };
224 if (isIterable(snapshot)) {
225 if ((import.meta.env && import.meta.env.MODE) !== "production") {
226 console.warn(
227 "snapshot as iterable is deprecated. use an object instead."
228 );
229 }
230 restoreAtoms(snapshot);
231 return;
232 }
233 restoreAtoms(snapshot.values);
234 },
235 [store, versionedWrite]
236 );
237}
238const isIterable = (item) => {
239 return typeof item[Symbol.iterator] === "function";
240};
241
242const atomToPrintable = (atom) => atom.debugLabel ? `${atom}:${atom.debugLabel}` : `${atom}`;
243const getDevtoolsState = (atomsSnapshot) => {
244 const values = {};
245 atomsSnapshot.values.forEach((v, atom) => {
246 values[atomToPrintable(atom)] = v;
247 });
248 const dependents = {};
249 atomsSnapshot.dependents.forEach((d, atom) => {
250 dependents[atomToPrintable(atom)] = Array.from(d).map(atomToPrintable);
251 });
252 return {
253 values,
254 dependents
255 };
256};
257function useAtomsDevtools(name, options) {
258 if (typeof options !== "undefined" && typeof options !== "object") {
259 console.warn("DEPRECATED [useAtomsDevtools] use DevtoolsOptions");
260 options = { scope: options };
261 }
262 const { enabled, scope } = options || {};
263 let extension;
264 try {
265 extension = (enabled != null ? enabled : (import.meta.env && import.meta.env.MODE) !== "production") && window.__REDUX_DEVTOOLS_EXTENSION__;
266 } catch {
267 }
268 if (!extension) {
269 if ((import.meta.env && import.meta.env.MODE) !== "production" && enabled) {
270 console.warn("Please install/enable Redux devtools extension");
271 }
272 }
273 const atomsSnapshot = useAtomsSnapshot(scope);
274 const goToSnapshot = useGotoAtomsSnapshot(scope);
275 const isTimeTraveling = useRef(false);
276 const isRecording = useRef(true);
277 const devtools = useRef();
278 const snapshots = useRef([]);
279 useEffect(() => {
280 if (!extension) {
281 return;
282 }
283 const getSnapshotAt = (index = snapshots.current.length - 1) => {
284 const snapshot = snapshots.current[index >= 0 ? index : 0];
285 if (!snapshot) {
286 throw new Error("snaphost index out of bounds");
287 }
288 return snapshot;
289 };
290 const connection = extension.connect({ name });
291 const devtoolsUnsubscribe = connection.subscribe((message) => {
292 var _a;
293 switch (message.type) {
294 case "DISPATCH":
295 switch ((_a = message.payload) == null ? void 0 : _a.type) {
296 case "RESET":
297 break;
298 case "COMMIT":
299 connection.init(getDevtoolsState(getSnapshotAt()));
300 snapshots.current = [];
301 break;
302 case "JUMP_TO_ACTION":
303 case "JUMP_TO_STATE":
304 isTimeTraveling.current = true;
305 goToSnapshot(getSnapshotAt(message.payload.actionId - 1));
306 break;
307 case "PAUSE_RECORDING":
308 isRecording.current = !isRecording.current;
309 break;
310 }
311 }
312 });
313 devtools.current = connection;
314 devtools.current.shouldInit = true;
315 return devtoolsUnsubscribe;
316 }, [extension, goToSnapshot, name]);
317 useEffect(() => {
318 if (!devtools.current) {
319 return;
320 }
321 if (devtools.current.shouldInit) {
322 devtools.current.init(void 0);
323 devtools.current.shouldInit = false;
324 return;
325 }
326 if (isTimeTraveling.current) {
327 isTimeTraveling.current = false;
328 } else if (isRecording.current) {
329 snapshots.current.push(atomsSnapshot);
330 devtools.current.send(
331 {
332 type: `${snapshots.current.length}`,
333 updatedAt: new Date().toLocaleString()
334 },
335 getDevtoolsState(atomsSnapshot)
336 );
337 }
338 }, [atomsSnapshot]);
339}
340
341export { useAtomDevtools, useAtomsDebugValue, useAtomsDevtools, useAtomsSnapshot, useGotoAtomsSnapshot };