UNPKG

12.2 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(atoms.flatMap((atom) => {
12 var _a, _b;
13 const mounted = (_a = store[DEV_GET_MOUNTED]) == null ? void 0 : _a.call(store, atom);
14 if (!mounted) {
15 return [];
16 }
17 const dependents = mounted.t;
18 const atomState = ((_b = store[DEV_GET_ATOM_STATE]) == null ? void 0 : _b.call(store, atom)) || {};
19 return [
20 [
21 atomToPrintable$1(atom),
22 {
23 ..."e" in atomState && { error: atomState.e },
24 ..."p" in atomState && { promise: atomState.p },
25 ..."v" in atomState && { value: atomState.v },
26 dependents: Array.from(dependents).map(atomToPrintable$1)
27 }
28 ]
29 ];
30}));
31const useAtomsDebugValue = (options) => {
32 var _a;
33 const enabled = (_a = options == null ? void 0 : options.enabled) != null ? _a : (import.meta.env && import.meta.env.MODE) !== "production";
34 const ScopeContext = SECRET_INTERNAL_getScopeContext(options == null ? void 0 : options.scope);
35 const { s: store } = useContext(ScopeContext);
36 const [atoms, setAtoms] = useState([]);
37 useEffect(() => {
38 var _a2;
39 if (!enabled) {
40 return;
41 }
42 const callback = () => {
43 var _a3;
44 setAtoms(Array.from(((_a3 = store[DEV_GET_MOUNTED_ATOMS]) == null ? void 0 : _a3.call(store)) || []));
45 };
46 const unsubscribe = (_a2 = store[DEV_SUBSCRIBE_STATE]) == null ? void 0 : _a2.call(store, callback);
47 callback();
48 return unsubscribe;
49 }, [enabled, store]);
50 useDebugValue([store, atoms], stateToPrintable);
51};
52
53function useAtomDevtools(anAtom, options, deprecatedScope) {
54 if (typeof options === "string" || typeof deprecatedScope !== "undefined") {
55 console.warn("DEPRECATED [useAtomDevtools] use DevtoolOptions");
56 options = {
57 name: options,
58 scope: deprecatedScope
59 };
60 }
61 const { enabled, name, scope } = options || {};
62 let extension;
63 try {
64 extension = (enabled != null ? enabled : (import.meta.env && import.meta.env.MODE) !== "production") && window.__REDUX_DEVTOOLS_EXTENSION__;
65 } catch {
66 }
67 if (!extension) {
68 if ((import.meta.env && import.meta.env.MODE) !== "production" && enabled) {
69 console.warn("Please install/enable Redux devtools extension");
70 }
71 }
72 const [value, setValue] = useAtom(anAtom, scope);
73 const lastValue = useRef(value);
74 const isTimeTraveling = useRef(false);
75 const devtools = useRef();
76 const atomName = name || anAtom.debugLabel || anAtom.toString();
77 useEffect(() => {
78 if (!extension) {
79 return;
80 }
81 const setValueIfWritable = (value2) => {
82 if (typeof setValue === "function") {
83 setValue(value2);
84 return;
85 }
86 console.warn("[Warn] you cannot do write operations (Time-travelling, etc) in read-only atoms\n", anAtom);
87 };
88 devtools.current = extension.connect({ name: atomName });
89 const unsubscribe = devtools.current.subscribe((message) => {
90 var _a, _b, _c, _d, _e, _f;
91 if (message.type === "ACTION" && message.payload) {
92 try {
93 setValueIfWritable(JSON.parse(message.payload));
94 } catch (e) {
95 console.error("please dispatch a serializable value that JSON.parse() support\n", e);
96 }
97 } else if (message.type === "DISPATCH" && message.state) {
98 if (((_a = message.payload) == null ? void 0 : _a.type) === "JUMP_TO_ACTION" || ((_b = message.payload) == null ? void 0 : _b.type) === "JUMP_TO_STATE") {
99 isTimeTraveling.current = true;
100 setValueIfWritable(JSON.parse(message.state));
101 }
102 } else if (message.type === "DISPATCH" && ((_c = message.payload) == null ? void 0 : _c.type) === "COMMIT") {
103 (_d = devtools.current) == null ? void 0 : _d.init(lastValue.current);
104 } else if (message.type === "DISPATCH" && ((_e = message.payload) == null ? void 0 : _e.type) === "IMPORT_STATE") {
105 const computedStates = ((_f = message.payload.nextLiftedState) == null ? void 0 : _f.computedStates) || [];
106 computedStates.forEach(({ state }, index) => {
107 var _a2;
108 if (index === 0) {
109 (_a2 = devtools.current) == null ? void 0 : _a2.init(state);
110 } else {
111 setValueIfWritable(state);
112 }
113 });
114 }
115 });
116 devtools.current.shouldInit = true;
117 return unsubscribe;
118 }, [anAtom, extension, atomName, setValue]);
119 useEffect(() => {
120 if (!devtools.current) {
121 return;
122 }
123 lastValue.current = value;
124 if (devtools.current.shouldInit) {
125 devtools.current.init(value);
126 devtools.current.shouldInit = false;
127 } else if (isTimeTraveling.current) {
128 isTimeTraveling.current = false;
129 } else {
130 devtools.current.send(`${atomName} - ${new Date().toLocaleString()}`, value);
131 }
132 }, [anAtom, extension, atomName, value]);
133}
134
135const createAtomsSnapshot = (store, atoms) => {
136 const tuples = atoms.map((atom) => {
137 var _a, _b;
138 const atomState = (_b = (_a = store[DEV_GET_ATOM_STATE]) == null ? void 0 : _a.call(store, atom)) != null ? _b : {};
139 return [atom, "v" in atomState ? atomState.v : void 0];
140 });
141 return new Map(tuples);
142};
143function useAtomsSnapshot(scope) {
144 const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
145 const scopeContainer = useContext(ScopeContext);
146 const store = scopeContainer.s;
147 if (!store[DEV_SUBSCRIBE_STATE]) {
148 throw new Error("useAtomsSnapshot can only be used in dev mode.");
149 }
150 const [atomsSnapshot, setAtomsSnapshot] = useState(() => /* @__PURE__ */ new Map());
151 useEffect(() => {
152 var _a;
153 const callback = () => {
154 var _a2;
155 const atoms = Array.from(((_a2 = store[DEV_GET_MOUNTED_ATOMS]) == null ? void 0 : _a2.call(store)) || []);
156 setAtomsSnapshot(createAtomsSnapshot(store, atoms));
157 };
158 const unsubscribe = (_a = store[DEV_SUBSCRIBE_STATE]) == null ? void 0 : _a.call(store, callback);
159 callback();
160 return unsubscribe;
161 }, [store]);
162 return atomsSnapshot;
163}
164
165function useGotoAtomsSnapshot(scope) {
166 const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
167 const scopeContainer = useContext(ScopeContext);
168 const store = scopeContainer.s;
169 if (!store[DEV_SUBSCRIBE_STATE]) {
170 throw new Error("useGotoAtomsSnapshot can only be used in dev mode.");
171 }
172 return useCallback((values) => {
173 store[RESTORE_ATOMS](values);
174 }, [store]);
175}
176
177const isEqualAtomsValues = (left, right) => left.size === right.size && Array.from(left).every(([left2, v]) => Object.is(right.get(left2), v));
178const isEqualAtomsDependents = (left, right) => left.size === right.size && Array.from(left).every(([a, dLeft]) => {
179 const dRight = right.get(a);
180 return dRight && dLeft.size === dRight.size && Array.from(dLeft).every((d) => dRight.has(d));
181});
182const atomToPrintable = (atom) => atom.debugLabel ? `${atom}:${atom.debugLabel}` : `${atom}`;
183const getDevtoolsState = (atomsSnapshot) => {
184 const values = {};
185 atomsSnapshot.values.forEach((v, atom) => {
186 values[atomToPrintable(atom)] = v;
187 });
188 const dependents = {};
189 atomsSnapshot.dependents.forEach((d, atom) => {
190 dependents[atomToPrintable(atom)] = Array.from(d).map(atomToPrintable);
191 });
192 return {
193 values,
194 dependents
195 };
196};
197function useAtomsDevtools(name, options) {
198 if (typeof options !== "undefined" && typeof options !== "object") {
199 console.warn("DEPRECATED [useAtomsDevtools] use DevtoolsOptions");
200 options = { scope: options };
201 }
202 const { enabled, scope } = options || {};
203 const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
204 const { s: store, w: versionedWrite } = useContext(ScopeContext);
205 let extension;
206 try {
207 extension = (enabled != null ? enabled : (import.meta.env && import.meta.env.MODE) !== "production") && window.__REDUX_DEVTOOLS_EXTENSION__;
208 } catch {
209 }
210 if (!extension) {
211 if ((import.meta.env && import.meta.env.MODE) !== "production" && enabled) {
212 console.warn("Please install/enable Redux devtools extension");
213 }
214 }
215 if (extension && !store[DEV_SUBSCRIBE_STATE]) {
216 throw new Error("useAtomsDevtools can only be used in dev mode.");
217 }
218 const [atomsSnapshot, setAtomsSnapshot] = useState(() => ({
219 values: /* @__PURE__ */ new Map(),
220 dependents: /* @__PURE__ */ new Map()
221 }));
222 useEffect(() => {
223 var _a;
224 if (!extension) {
225 return;
226 }
227 const callback = () => {
228 var _a2, _b, _c;
229 const values = /* @__PURE__ */ new Map();
230 const dependents = /* @__PURE__ */ new Map();
231 for (const atom of ((_a2 = store[DEV_GET_MOUNTED_ATOMS]) == null ? void 0 : _a2.call(store)) || []) {
232 const atomState = (_b = store[DEV_GET_ATOM_STATE]) == null ? void 0 : _b.call(store, atom);
233 if (atomState) {
234 if (atomState.r === atomState.i) {
235 return;
236 }
237 if ("v" in atomState) {
238 values.set(atom, atomState.v);
239 }
240 }
241 const mounted = (_c = store[DEV_GET_MOUNTED]) == null ? void 0 : _c.call(store, atom);
242 if (mounted) {
243 dependents.set(atom, mounted.t);
244 }
245 }
246 setAtomsSnapshot((prev) => {
247 if (isEqualAtomsValues(prev.values, values) && isEqualAtomsDependents(prev.dependents, dependents)) {
248 return prev;
249 }
250 return { values, dependents };
251 });
252 };
253 const unsubscribe = (_a = store[DEV_SUBSCRIBE_STATE]) == null ? void 0 : _a.call(store, callback);
254 callback();
255 return unsubscribe;
256 }, [extension, store]);
257 const goToSnapshot = useCallback((snapshot) => {
258 const { values } = snapshot;
259 if (versionedWrite) {
260 versionedWrite((version) => {
261 store[RESTORE_ATOMS](values, version);
262 });
263 } else {
264 store[RESTORE_ATOMS](values);
265 }
266 }, [store, versionedWrite]);
267 const isTimeTraveling = useRef(false);
268 const isRecording = useRef(true);
269 const devtools = useRef();
270 const snapshots = useRef([]);
271 useEffect(() => {
272 if (!extension) {
273 return;
274 }
275 const getSnapshotAt = (index = snapshots.current.length - 1) => {
276 const snapshot = snapshots.current[index >= 0 ? index : 0];
277 if (!snapshot) {
278 throw new Error("snaphost index out of bounds");
279 }
280 return snapshot;
281 };
282 const connection = extension.connect({ name });
283 const devtoolsUnsubscribe = connection.subscribe((message) => {
284 var _a;
285 switch (message.type) {
286 case "DISPATCH":
287 switch ((_a = message.payload) == null ? void 0 : _a.type) {
288 case "RESET":
289 break;
290 case "COMMIT":
291 connection.init(getDevtoolsState(getSnapshotAt()));
292 snapshots.current = [];
293 break;
294 case "JUMP_TO_ACTION":
295 case "JUMP_TO_STATE":
296 isTimeTraveling.current = true;
297 goToSnapshot(getSnapshotAt(message.payload.actionId - 1));
298 break;
299 case "PAUSE_RECORDING":
300 isRecording.current = !isRecording.current;
301 break;
302 }
303 }
304 });
305 devtools.current = connection;
306 devtools.current.shouldInit = true;
307 return devtoolsUnsubscribe;
308 }, [extension, goToSnapshot, name]);
309 useEffect(() => {
310 if (!devtools.current) {
311 return;
312 }
313 if (devtools.current.shouldInit) {
314 devtools.current.init(void 0);
315 devtools.current.shouldInit = false;
316 return;
317 }
318 if (isTimeTraveling.current) {
319 isTimeTraveling.current = false;
320 } else if (isRecording.current) {
321 snapshots.current.push(atomsSnapshot);
322 devtools.current.send({
323 type: `${snapshots.current.length}`,
324 updatedAt: new Date().toLocaleString()
325 }, getDevtoolsState(atomsSnapshot));
326 }
327 }, [atomsSnapshot]);
328}
329
330export { useAtomDevtools, useAtomsDebugValue, useAtomsDevtools, useAtomsSnapshot, useGotoAtomsSnapshot };