electron-state-sync 1.1.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/CLAUDE.md +254 -0
- package/LICENSE +21 -0
- package/README.md +753 -0
- package/README.zh-CN.md +743 -0
- package/bun.lock +542 -0
- package/bunfig.toml +7 -0
- package/dist/jotai.cjs +106 -0
- package/dist/jotai.d.cts +48 -0
- package/dist/jotai.d.cts.map +1 -0
- package/dist/jotai.d.ts +48 -0
- package/dist/jotai.d.ts.map +1 -0
- package/dist/jotai.js +105 -0
- package/dist/jotai.js.map +1 -0
- package/dist/main.cjs +177 -0
- package/dist/main.d.cts +26 -0
- package/dist/main.d.cts.map +1 -0
- package/dist/main.d.ts +26 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +177 -0
- package/dist/main.js.map +1 -0
- package/dist/preact.cjs +46 -0
- package/dist/preact.d.cts +11 -0
- package/dist/preact.d.cts.map +1 -0
- package/dist/preact.d.ts +11 -0
- package/dist/preact.d.ts.map +1 -0
- package/dist/preact.js +46 -0
- package/dist/preact.js.map +1 -0
- package/dist/preload.cjs +51 -0
- package/dist/preload.d.cts +20 -0
- package/dist/preload.d.cts.map +1 -0
- package/dist/preload.d.ts +20 -0
- package/dist/preload.d.ts.map +1 -0
- package/dist/preload.js +51 -0
- package/dist/preload.js.map +1 -0
- package/dist/react-query.cjs +113 -0
- package/dist/react-query.d.cts +58 -0
- package/dist/react-query.d.cts.map +1 -0
- package/dist/react-query.d.ts +58 -0
- package/dist/react-query.d.ts.map +1 -0
- package/dist/react-query.js +112 -0
- package/dist/react-query.js.map +1 -0
- package/dist/react.cjs +46 -0
- package/dist/react.d.cts +11 -0
- package/dist/react.d.cts.map +1 -0
- package/dist/react.d.ts +11 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +46 -0
- package/dist/react.js.map +1 -0
- package/dist/redux.cjs +148 -0
- package/dist/redux.d.cts +80 -0
- package/dist/redux.d.cts.map +1 -0
- package/dist/redux.d.ts +80 -0
- package/dist/redux.d.ts.map +1 -0
- package/dist/redux.js +146 -0
- package/dist/redux.js.map +1 -0
- package/dist/renderer/index.cjs +7 -0
- package/dist/renderer/index.d.cts +23 -0
- package/dist/renderer/index.d.cts.map +1 -0
- package/dist/renderer/index.d.ts +23 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +3 -0
- package/dist/renderer-C7zF3UQm.js +57 -0
- package/dist/renderer-C7zF3UQm.js.map +1 -0
- package/dist/renderer-D3YziJ_U.cjs +86 -0
- package/dist/solid.cjs +74 -0
- package/dist/solid.d.cts +13 -0
- package/dist/solid.d.cts.map +1 -0
- package/dist/solid.d.ts +13 -0
- package/dist/solid.d.ts.map +1 -0
- package/dist/solid.js +74 -0
- package/dist/solid.js.map +1 -0
- package/dist/svelte.cjs +63 -0
- package/dist/svelte.d.cts +14 -0
- package/dist/svelte.d.cts.map +1 -0
- package/dist/svelte.d.ts +14 -0
- package/dist/svelte.d.ts.map +1 -0
- package/dist/svelte.js +63 -0
- package/dist/svelte.js.map +1 -0
- package/dist/types-7wPPX0ty.d.ts +37 -0
- package/dist/types-7wPPX0ty.d.ts.map +1 -0
- package/dist/types-C18dHgLI.d.cts +37 -0
- package/dist/types-C18dHgLI.d.cts.map +1 -0
- package/dist/vue.cjs +69 -0
- package/dist/vue.d.cts +15 -0
- package/dist/vue.d.cts.map +1 -0
- package/dist/vue.d.ts +15 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/vue.js +70 -0
- package/dist/vue.js.map +1 -0
- package/dist/zustand.cjs +193 -0
- package/dist/zustand.d.cts +61 -0
- package/dist/zustand.d.cts.map +1 -0
- package/dist/zustand.d.ts +61 -0
- package/dist/zustand.d.ts.map +1 -0
- package/dist/zustand.js +191 -0
- package/dist/zustand.js.map +1 -0
- package/package.json +162 -0
package/bunfig.toml
ADDED
package/dist/jotai.cjs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const require_renderer = require('./renderer-D3YziJ_U.cjs');
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let jotai = require("jotai");
|
|
4
|
+
|
|
5
|
+
//#region src/renderer/jotai.ts
|
|
6
|
+
const createChannelOptions = (options) => {
|
|
7
|
+
const globalConfig = require_renderer.getGlobalConfig();
|
|
8
|
+
return {
|
|
9
|
+
baseChannel: options.baseChannel ?? globalConfig.baseChannel,
|
|
10
|
+
name: options.name
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
const syncedAtoms = /* @__PURE__ */ new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Create a synced atom that synchronizes with Electron main process
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { syncStateAtom, useAtom } from 'electron-state-sync/jotai';
|
|
20
|
+
*
|
|
21
|
+
* const countAtom = syncStateAtom(0, { name: 'counter' });
|
|
22
|
+
*
|
|
23
|
+
* function App() {
|
|
24
|
+
* const [count, setCount] = useAtom(countAtom);
|
|
25
|
+
* return <div onClick={() => setCount(5)}>{count}</div>;
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
const syncStateAtom = (initialValue, options) => {
|
|
30
|
+
const globalConfig = require_renderer.getGlobalConfig();
|
|
31
|
+
const bridge = require_renderer.resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
|
|
32
|
+
const channelOptions = createChannelOptions(options);
|
|
33
|
+
const baseAtom = (0, jotai.atom)(initialValue);
|
|
34
|
+
const wrapper = { atom: baseAtom };
|
|
35
|
+
const atomKey = `${channelOptions.baseChannel}:${channelOptions.name}`;
|
|
36
|
+
syncedAtoms.set(atomKey, wrapper);
|
|
37
|
+
let pendingRemoteValue = initialValue;
|
|
38
|
+
let hasPendingRemoteValue = false;
|
|
39
|
+
const applyRemoteValue = (value) => {
|
|
40
|
+
if (wrapper.setAtom) {
|
|
41
|
+
wrapper.setAtom(value);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
pendingRemoteValue = value;
|
|
45
|
+
hasPendingRemoteValue = true;
|
|
46
|
+
};
|
|
47
|
+
baseAtom.onMount = (setAtom) => {
|
|
48
|
+
wrapper.setAtom = setAtom;
|
|
49
|
+
if (hasPendingRemoteValue) {
|
|
50
|
+
setAtom(pendingRemoteValue);
|
|
51
|
+
hasPendingRemoteValue = false;
|
|
52
|
+
}
|
|
53
|
+
return () => {
|
|
54
|
+
wrapper.setAtom = void 0;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
wrapper.unsubscribe = bridge.subscribe(channelOptions, applyRemoteValue);
|
|
58
|
+
bridge.get(channelOptions).then(applyRemoteValue);
|
|
59
|
+
return (0, jotai.atom)((get) => get(baseAtom), (get, set, value) => {
|
|
60
|
+
const currentValue = get(baseAtom);
|
|
61
|
+
const nextValue = typeof value === "function" ? value(currentValue) : value;
|
|
62
|
+
set(baseAtom, nextValue);
|
|
63
|
+
bridge.set(channelOptions, nextValue);
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Hook to access sync status for a synced atom
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* import { useSyncStateStatus } from 'electron-state-sync/jotai';
|
|
72
|
+
*
|
|
73
|
+
* function App() {
|
|
74
|
+
* const { isSynced } = useSyncStateStatus({ name: 'counter' });
|
|
75
|
+
* return isSynced ? <div>Synced</div> : <div>Loading...</div>;
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
const useSyncStateStatus = (options) => {
|
|
80
|
+
const [isSynced, setIsSynced] = (0, react.useState)(false);
|
|
81
|
+
const globalConfig = require_renderer.getGlobalConfig();
|
|
82
|
+
const bridge = require_renderer.resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
|
|
83
|
+
const channelOptions = (0, react.useMemo)(() => createChannelOptions(options), [options.baseChannel, options.name]);
|
|
84
|
+
(0, react.useEffect)(() => {
|
|
85
|
+
bridge.get(channelOptions).then(() => {
|
|
86
|
+
setIsSynced(true);
|
|
87
|
+
});
|
|
88
|
+
}, [bridge, channelOptions]);
|
|
89
|
+
return { isSynced };
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Cleanup function for synced atoms
|
|
93
|
+
*
|
|
94
|
+
* Call this when unmounting components to prevent memory leaks
|
|
95
|
+
*/
|
|
96
|
+
const cleanupSyncedAtom = (options) => {
|
|
97
|
+
const channelOptions = createChannelOptions(options);
|
|
98
|
+
const atomKey = `${channelOptions.baseChannel}:${channelOptions.name}`;
|
|
99
|
+
syncedAtoms.get(atomKey)?.unsubscribe?.();
|
|
100
|
+
syncedAtoms.delete(atomKey);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
exports.cleanupSyncedAtom = cleanupSyncedAtom;
|
|
105
|
+
exports.syncStateAtom = syncStateAtom;
|
|
106
|
+
exports.useSyncStateStatus = useSyncStateStatus;
|
package/dist/jotai.d.cts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { a as SyncStateChannelOptions, t as SyncStateBridge } from "./types-C18dHgLI.cjs";
|
|
2
|
+
import { PrimitiveAtom } from "jotai";
|
|
3
|
+
|
|
4
|
+
//#region src/renderer/jotai.d.ts
|
|
5
|
+
interface SyncStateJotaiOptions extends SyncStateChannelOptions {
|
|
6
|
+
bridge?: SyncStateBridge;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create a synced atom that synchronizes with Electron main process
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { syncStateAtom, useAtom } from 'electron-state-sync/jotai';
|
|
14
|
+
*
|
|
15
|
+
* const countAtom = syncStateAtom(0, { name: 'counter' });
|
|
16
|
+
*
|
|
17
|
+
* function App() {
|
|
18
|
+
* const [count, setCount] = useAtom(countAtom);
|
|
19
|
+
* return <div onClick={() => setCount(5)}>{count}</div>;
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare const syncStateAtom: <StateValue>(initialValue: StateValue, options: SyncStateJotaiOptions) => PrimitiveAtom<StateValue>;
|
|
24
|
+
/**
|
|
25
|
+
* Hook to access sync status for a synced atom
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { useSyncStateStatus } from 'electron-state-sync/jotai';
|
|
30
|
+
*
|
|
31
|
+
* function App() {
|
|
32
|
+
* const { isSynced } = useSyncStateStatus({ name: 'counter' });
|
|
33
|
+
* return isSynced ? <div>Synced</div> : <div>Loading...</div>;
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare const useSyncStateStatus: (options: SyncStateJotaiOptions) => {
|
|
38
|
+
isSynced: boolean;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Cleanup function for synced atoms
|
|
42
|
+
*
|
|
43
|
+
* Call this when unmounting components to prevent memory leaks
|
|
44
|
+
*/
|
|
45
|
+
declare const cleanupSyncedAtom: (options: SyncStateJotaiOptions) => void;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { SyncStateJotaiOptions, cleanupSyncedAtom, syncStateAtom, useSyncStateStatus };
|
|
48
|
+
//# sourceMappingURL=jotai.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jotai.d.cts","names":[],"sources":["../src/renderer/jotai.ts"],"sourcesContent":[],"mappings":";;;;UAQiB,qBAAA,SAA8B;EAA9B,MAAA,CAAA,EACN,eAD4B;AAsCvC;;;;;;AAoFA;AAuBA;;;;;;;;;cA3Ga,0CACG,qBACL,0BACR,cAAc;;;;;;;;;;;;;;cAiFJ,8BAA+B;;;;;;;;cAuB/B,6BAA8B"}
|
package/dist/jotai.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { a as SyncStateChannelOptions, t as SyncStateBridge } from "./types-7wPPX0ty.js";
|
|
2
|
+
import { PrimitiveAtom } from "jotai";
|
|
3
|
+
|
|
4
|
+
//#region src/renderer/jotai.d.ts
|
|
5
|
+
interface SyncStateJotaiOptions extends SyncStateChannelOptions {
|
|
6
|
+
bridge?: SyncStateBridge;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create a synced atom that synchronizes with Electron main process
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { syncStateAtom, useAtom } from 'electron-state-sync/jotai';
|
|
14
|
+
*
|
|
15
|
+
* const countAtom = syncStateAtom(0, { name: 'counter' });
|
|
16
|
+
*
|
|
17
|
+
* function App() {
|
|
18
|
+
* const [count, setCount] = useAtom(countAtom);
|
|
19
|
+
* return <div onClick={() => setCount(5)}>{count}</div>;
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare const syncStateAtom: <StateValue>(initialValue: StateValue, options: SyncStateJotaiOptions) => PrimitiveAtom<StateValue>;
|
|
24
|
+
/**
|
|
25
|
+
* Hook to access sync status for a synced atom
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { useSyncStateStatus } from 'electron-state-sync/jotai';
|
|
30
|
+
*
|
|
31
|
+
* function App() {
|
|
32
|
+
* const { isSynced } = useSyncStateStatus({ name: 'counter' });
|
|
33
|
+
* return isSynced ? <div>Synced</div> : <div>Loading...</div>;
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare const useSyncStateStatus: (options: SyncStateJotaiOptions) => {
|
|
38
|
+
isSynced: boolean;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Cleanup function for synced atoms
|
|
42
|
+
*
|
|
43
|
+
* Call this when unmounting components to prevent memory leaks
|
|
44
|
+
*/
|
|
45
|
+
declare const cleanupSyncedAtom: (options: SyncStateJotaiOptions) => void;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { SyncStateJotaiOptions, cleanupSyncedAtom, syncStateAtom, useSyncStateStatus };
|
|
48
|
+
//# sourceMappingURL=jotai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jotai.d.ts","names":[],"sources":["../src/renderer/jotai.ts"],"sourcesContent":[],"mappings":";;;;UAQiB,qBAAA,SAA8B;EAA9B,MAAA,CAAA,EACN,eAD4B;AAsCvC;;;;;;AAoFA;AAuBA;;;;;;;;;cA3Ga,0CACG,qBACL,0BACR,cAAc;;;;;;;;;;;;;;cAiFJ,8BAA+B;;;;;;;;cAuB/B,6BAA8B"}
|
package/dist/jotai.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { a as resolveSyncStateBridge, t as getGlobalConfig } from "./renderer-C7zF3UQm.js";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { atom } from "jotai";
|
|
4
|
+
|
|
5
|
+
//#region src/renderer/jotai.ts
|
|
6
|
+
const createChannelOptions = (options) => {
|
|
7
|
+
const globalConfig = getGlobalConfig();
|
|
8
|
+
return {
|
|
9
|
+
baseChannel: options.baseChannel ?? globalConfig.baseChannel,
|
|
10
|
+
name: options.name
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
const syncedAtoms = /* @__PURE__ */ new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Create a synced atom that synchronizes with Electron main process
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { syncStateAtom, useAtom } from 'electron-state-sync/jotai';
|
|
20
|
+
*
|
|
21
|
+
* const countAtom = syncStateAtom(0, { name: 'counter' });
|
|
22
|
+
*
|
|
23
|
+
* function App() {
|
|
24
|
+
* const [count, setCount] = useAtom(countAtom);
|
|
25
|
+
* return <div onClick={() => setCount(5)}>{count}</div>;
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
const syncStateAtom = (initialValue, options) => {
|
|
30
|
+
const globalConfig = getGlobalConfig();
|
|
31
|
+
const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
|
|
32
|
+
const channelOptions = createChannelOptions(options);
|
|
33
|
+
const baseAtom = atom(initialValue);
|
|
34
|
+
const wrapper = { atom: baseAtom };
|
|
35
|
+
const atomKey = `${channelOptions.baseChannel}:${channelOptions.name}`;
|
|
36
|
+
syncedAtoms.set(atomKey, wrapper);
|
|
37
|
+
let pendingRemoteValue = initialValue;
|
|
38
|
+
let hasPendingRemoteValue = false;
|
|
39
|
+
const applyRemoteValue = (value) => {
|
|
40
|
+
if (wrapper.setAtom) {
|
|
41
|
+
wrapper.setAtom(value);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
pendingRemoteValue = value;
|
|
45
|
+
hasPendingRemoteValue = true;
|
|
46
|
+
};
|
|
47
|
+
baseAtom.onMount = (setAtom) => {
|
|
48
|
+
wrapper.setAtom = setAtom;
|
|
49
|
+
if (hasPendingRemoteValue) {
|
|
50
|
+
setAtom(pendingRemoteValue);
|
|
51
|
+
hasPendingRemoteValue = false;
|
|
52
|
+
}
|
|
53
|
+
return () => {
|
|
54
|
+
wrapper.setAtom = void 0;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
wrapper.unsubscribe = bridge.subscribe(channelOptions, applyRemoteValue);
|
|
58
|
+
bridge.get(channelOptions).then(applyRemoteValue);
|
|
59
|
+
return atom((get) => get(baseAtom), (get, set, value) => {
|
|
60
|
+
const currentValue = get(baseAtom);
|
|
61
|
+
const nextValue = typeof value === "function" ? value(currentValue) : value;
|
|
62
|
+
set(baseAtom, nextValue);
|
|
63
|
+
bridge.set(channelOptions, nextValue);
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Hook to access sync status for a synced atom
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* import { useSyncStateStatus } from 'electron-state-sync/jotai';
|
|
72
|
+
*
|
|
73
|
+
* function App() {
|
|
74
|
+
* const { isSynced } = useSyncStateStatus({ name: 'counter' });
|
|
75
|
+
* return isSynced ? <div>Synced</div> : <div>Loading...</div>;
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
const useSyncStateStatus = (options) => {
|
|
80
|
+
const [isSynced, setIsSynced] = useState(false);
|
|
81
|
+
const globalConfig = getGlobalConfig();
|
|
82
|
+
const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
|
|
83
|
+
const channelOptions = useMemo(() => createChannelOptions(options), [options.baseChannel, options.name]);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
bridge.get(channelOptions).then(() => {
|
|
86
|
+
setIsSynced(true);
|
|
87
|
+
});
|
|
88
|
+
}, [bridge, channelOptions]);
|
|
89
|
+
return { isSynced };
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Cleanup function for synced atoms
|
|
93
|
+
*
|
|
94
|
+
* Call this when unmounting components to prevent memory leaks
|
|
95
|
+
*/
|
|
96
|
+
const cleanupSyncedAtom = (options) => {
|
|
97
|
+
const channelOptions = createChannelOptions(options);
|
|
98
|
+
const atomKey = `${channelOptions.baseChannel}:${channelOptions.name}`;
|
|
99
|
+
syncedAtoms.get(atomKey)?.unsubscribe?.();
|
|
100
|
+
syncedAtoms.delete(atomKey);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
export { cleanupSyncedAtom, syncStateAtom, useSyncStateStatus };
|
|
105
|
+
//# sourceMappingURL=jotai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jotai.js","names":[],"sources":["../src/renderer/jotai.ts"],"sourcesContent":["import { atom, type Getter, type PrimitiveAtom, type Setter } from \"jotai\";\nimport { useEffect, useMemo, useState } from \"react\";\n\nimport type { SyncStateChannelOptions } from \"../channels\";\nimport type { SyncStateBridge } from \"../types\";\nimport { resolveSyncStateBridge } from \"./bridge\";\nimport { getGlobalConfig } from \"./index\";\n\nexport interface SyncStateJotaiOptions extends SyncStateChannelOptions {\n bridge?: SyncStateBridge;\n}\n\ninterface SyncedAtomWrapper<StateValue> {\n atom: PrimitiveAtom<StateValue>;\n // 保存基础 atom 的 setter,用于应用远程更新\n setAtom?: (value: StateValue) => void;\n // 记录订阅的清理函数,供外部释放\n unsubscribe?: () => void;\n}\n\nconst createChannelOptions = (options: SyncStateJotaiOptions): SyncStateChannelOptions => {\n const globalConfig = getGlobalConfig();\n return {\n baseChannel: options.baseChannel ?? globalConfig.baseChannel,\n name: options.name,\n };\n};\n\n// Store synced atoms for cleanup\nconst syncedAtoms = new Map<string, SyncedAtomWrapper<unknown>>();\n\n/**\n * Create a synced atom that synchronizes with Electron main process\n *\n * @example\n * ```typescript\n * import { syncStateAtom, useAtom } from 'electron-state-sync/jotai';\n *\n * const countAtom = syncStateAtom(0, { name: 'counter' });\n *\n * function App() {\n * const [count, setCount] = useAtom(countAtom);\n * return <div onClick={() => setCount(5)}>{count}</div>;\n * }\n * ```\n */\nexport const syncStateAtom = <StateValue>(\n initialValue: StateValue,\n options: SyncStateJotaiOptions,\n): PrimitiveAtom<StateValue> => {\n const globalConfig = getGlobalConfig();\n const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);\n const channelOptions = createChannelOptions(options);\n\n // Create wrapper to track sync state\n const baseAtom = atom(initialValue);\n const wrapper: SyncedAtomWrapper<StateValue> = {\n atom: baseAtom,\n };\n\n // Store for cleanup\n const atomKey = `${channelOptions.baseChannel}:${channelOptions.name}`;\n syncedAtoms.set(atomKey, wrapper as SyncedAtomWrapper<unknown>);\n\n // 待应用的远程值(atom 挂载前缓存)\n let pendingRemoteValue = initialValue;\n // 是否存在待处理的远程更新\n let hasPendingRemoteValue = false;\n\n // 应用主进程的远程更新\n const applyRemoteValue = (value: StateValue): void => {\n if (wrapper.setAtom) {\n wrapper.setAtom(value);\n return;\n }\n pendingRemoteValue = value;\n hasPendingRemoteValue = true;\n };\n\n // 基础 atom 挂载时,补发缓存的远程值\n baseAtom.onMount = (setAtom) => {\n wrapper.setAtom = setAtom;\n if (hasPendingRemoteValue) {\n setAtom(pendingRemoteValue);\n hasPendingRemoteValue = false;\n }\n return () => {\n wrapper.setAtom = undefined;\n };\n };\n\n // Subscribe to remote updates from main process\n const unsubscribe = bridge.subscribe<StateValue>(channelOptions, applyRemoteValue);\n wrapper.unsubscribe = unsubscribe;\n\n // Initialize from main process\n void bridge.get<StateValue>(channelOptions).then(applyRemoteValue);\n\n // Create read-write atom with sync logic\n const syncedAtom = atom(\n (get: Getter) => get(baseAtom),\n (get: Getter, set: Setter, value: StateValue | ((prev: StateValue) => StateValue)) => {\n const currentValue = get(baseAtom);\n const nextValue =\n typeof value === \"function\"\n ? (value as (prev: StateValue) => StateValue)(currentValue)\n : value;\n\n // Local update - sync to main process\n set(baseAtom, nextValue);\n void bridge.set(channelOptions, nextValue);\n },\n );\n\n return syncedAtom as PrimitiveAtom<StateValue>;\n};\n\n/**\n * Hook to access sync status for a synced atom\n *\n * @example\n * ```typescript\n * import { useSyncStateStatus } from 'electron-state-sync/jotai';\n *\n * function App() {\n * const { isSynced } = useSyncStateStatus({ name: 'counter' });\n * return isSynced ? <div>Synced</div> : <div>Loading...</div>;\n * }\n * ```\n */\nexport const useSyncStateStatus = (options: SyncStateJotaiOptions): { isSynced: boolean } => {\n const [isSynced, setIsSynced] = useState(false);\n const globalConfig = getGlobalConfig();\n const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);\n const channelOptions = useMemo(\n () => createChannelOptions(options),\n [options.baseChannel, options.name],\n );\n\n useEffect(() => {\n void bridge.get(channelOptions).then(() => {\n setIsSynced(true);\n });\n }, [bridge, channelOptions]);\n\n return { isSynced };\n};\n\n/**\n * Cleanup function for synced atoms\n *\n * Call this when unmounting components to prevent memory leaks\n */\nexport const cleanupSyncedAtom = (options: SyncStateJotaiOptions): void => {\n const channelOptions = createChannelOptions(options);\n const atomKey = `${channelOptions.baseChannel}:${channelOptions.name}`;\n // 清理订阅避免内存泄漏\n const wrapper = syncedAtoms.get(atomKey);\n wrapper?.unsubscribe?.();\n syncedAtoms.delete(atomKey);\n};\n"],"mappings":";;;;;AAoBA,MAAM,wBAAwB,YAA4D;CACxF,MAAM,eAAe,iBAAiB;AACtC,QAAO;EACL,aAAa,QAAQ,eAAe,aAAa;EACjD,MAAM,QAAQ;EACf;;AAIH,MAAM,8BAAc,IAAI,KAAyC;;;;;;;;;;;;;;;;AAiBjE,MAAa,iBACX,cACA,YAC8B;CAC9B,MAAM,eAAe,iBAAiB;CACtC,MAAM,SAAS,uBAAuB,QAAQ,UAAU,aAAa,OAAO;CAC5E,MAAM,iBAAiB,qBAAqB,QAAQ;CAGpD,MAAM,WAAW,KAAK,aAAa;CACnC,MAAM,UAAyC,EAC7C,MAAM,UACP;CAGD,MAAM,UAAU,GAAG,eAAe,YAAY,GAAG,eAAe;AAChE,aAAY,IAAI,SAAS,QAAsC;CAG/D,IAAI,qBAAqB;CAEzB,IAAI,wBAAwB;CAG5B,MAAM,oBAAoB,UAA4B;AACpD,MAAI,QAAQ,SAAS;AACnB,WAAQ,QAAQ,MAAM;AACtB;;AAEF,uBAAqB;AACrB,0BAAwB;;AAI1B,UAAS,WAAW,YAAY;AAC9B,UAAQ,UAAU;AAClB,MAAI,uBAAuB;AACzB,WAAQ,mBAAmB;AAC3B,2BAAwB;;AAE1B,eAAa;AACX,WAAQ,UAAU;;;AAMtB,SAAQ,cADY,OAAO,UAAsB,gBAAgB,iBAAiB;AAIlF,CAAK,OAAO,IAAgB,eAAe,CAAC,KAAK,iBAAiB;AAkBlE,QAfmB,MAChB,QAAgB,IAAI,SAAS,GAC7B,KAAa,KAAa,UAA2D;EACpF,MAAM,eAAe,IAAI,SAAS;EAClC,MAAM,YACJ,OAAO,UAAU,aACZ,MAA2C,aAAa,GACzD;AAGN,MAAI,UAAU,UAAU;AACxB,EAAK,OAAO,IAAI,gBAAgB,UAAU;GAE7C;;;;;;;;;;;;;;;AAkBH,MAAa,sBAAsB,YAA0D;CAC3F,MAAM,CAAC,UAAU,eAAe,SAAS,MAAM;CAC/C,MAAM,eAAe,iBAAiB;CACtC,MAAM,SAAS,uBAAuB,QAAQ,UAAU,aAAa,OAAO;CAC5E,MAAM,iBAAiB,cACf,qBAAqB,QAAQ,EACnC,CAAC,QAAQ,aAAa,QAAQ,KAAK,CACpC;AAED,iBAAgB;AACd,EAAK,OAAO,IAAI,eAAe,CAAC,WAAW;AACzC,eAAY,KAAK;IACjB;IACD,CAAC,QAAQ,eAAe,CAAC;AAE5B,QAAO,EAAE,UAAU;;;;;;;AAQrB,MAAa,qBAAqB,YAAyC;CACzE,MAAM,iBAAiB,qBAAqB,QAAQ;CACpD,MAAM,UAAU,GAAG,eAAe,YAAY,GAAG,eAAe;AAGhE,CADgB,YAAY,IAAI,QAAQ,EAC/B,eAAe;AACxB,aAAY,OAAO,QAAQ"}
|
package/dist/main.cjs
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
let electron = require("electron");
|
|
2
|
+
|
|
3
|
+
//#region src/channels.ts
|
|
4
|
+
const createSyncStateChannels = (options) => {
|
|
5
|
+
const channelPrefix = `${options.baseChannel ?? "state"}:${options.name}`;
|
|
6
|
+
return {
|
|
7
|
+
getChannel: `${channelPrefix}:get`,
|
|
8
|
+
setChannel: `${channelPrefix}:set`,
|
|
9
|
+
subscribeChannel: `${channelPrefix}:subscribe`,
|
|
10
|
+
unsubscribeChannel: `${channelPrefix}:unsubscribe`,
|
|
11
|
+
updateChannel: `${channelPrefix}:update`
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/types.ts
|
|
17
|
+
var SyncStateError = class SyncStateError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
stateName;
|
|
20
|
+
baseChannel;
|
|
21
|
+
cause;
|
|
22
|
+
constructor(code, message, context) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.name = "SyncStateError";
|
|
26
|
+
this.stateName = context?.stateName;
|
|
27
|
+
this.baseChannel = context?.baseChannel;
|
|
28
|
+
this.cause = context?.cause;
|
|
29
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, SyncStateError);
|
|
30
|
+
}
|
|
31
|
+
getFullMessage() {
|
|
32
|
+
const parts = [this.message];
|
|
33
|
+
if (this.stateName) parts.push(`\n State name: "${this.stateName}"`);
|
|
34
|
+
if (this.baseChannel) parts.push(`\n Channel prefix: "${this.baseChannel}"`);
|
|
35
|
+
if (this.cause) parts.push(`\n Original error: ${this.cause.message}`);
|
|
36
|
+
return parts.join("");
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/main.ts
|
|
42
|
+
let globalConfig = {};
|
|
43
|
+
const initSyncStateMain = (config) => {
|
|
44
|
+
globalConfig = { ...config };
|
|
45
|
+
};
|
|
46
|
+
const createReadonlyErrorMessage = (stateName, baseChannel) => `syncState renderer write is disabled by main process. State "${stateName}" has channel prefix "${baseChannel}". Please set allowRendererSet: true in main process to enable renderer write.`;
|
|
47
|
+
const createInvalidValueErrorMessage = (stateName, baseChannel) => `syncState renderer write value validation failed. State "${stateName}" has channel prefix "${baseChannel}". Please check if the value conforms to resolveRendererValue validation rules.`;
|
|
48
|
+
const INVALID_INITIAL_VALUE_WARNING_MESSAGE = "syncState Warning: Initial value contains non-serializable content (such as functions, Symbols, BigInts, etc.), which will prevent the value from being transmitted to renderer process via IPC";
|
|
49
|
+
const checkType = (val) => {
|
|
50
|
+
if (val === null || val === void 0) return true;
|
|
51
|
+
if (typeof val === "string") return true;
|
|
52
|
+
if (typeof val === "number") return true;
|
|
53
|
+
if (typeof val === "boolean") return true;
|
|
54
|
+
if (typeof val === "function") return false;
|
|
55
|
+
if (typeof val === "symbol") return false;
|
|
56
|
+
if (typeof val === "bigint") return false;
|
|
57
|
+
if (Array.isArray(val)) return val.every((item) => checkType(item));
|
|
58
|
+
if (typeof val === "object") try {
|
|
59
|
+
JSON.stringify(val);
|
|
60
|
+
return Object.values(val).every((item) => checkType(item));
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
};
|
|
66
|
+
const validateSerializable = (value) => checkType(value);
|
|
67
|
+
const createSyncStateStore = (initialValue) => {
|
|
68
|
+
let state$1 = initialValue;
|
|
69
|
+
const getState = () => state$1;
|
|
70
|
+
const setState = (value) => {
|
|
71
|
+
state$1 = value;
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
getState,
|
|
75
|
+
setState
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const createSubscriptions = (channels, getState) => {
|
|
79
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
80
|
+
const broadcast = (value) => {
|
|
81
|
+
for (const subscriber of subscribers) if (subscriber.isDestroyed()) subscribers.delete(subscriber);
|
|
82
|
+
else subscriber.send(channels.updateChannel, value);
|
|
83
|
+
};
|
|
84
|
+
const handleSubscribe = ({ sender }) => {
|
|
85
|
+
subscribers.add(sender);
|
|
86
|
+
sender.once("destroyed", () => {
|
|
87
|
+
subscribers.delete(sender);
|
|
88
|
+
});
|
|
89
|
+
sender.send(channels.updateChannel, getState());
|
|
90
|
+
};
|
|
91
|
+
const handleUnsubscribe = ({ sender }) => {
|
|
92
|
+
subscribers.delete(sender);
|
|
93
|
+
};
|
|
94
|
+
const clear = () => {
|
|
95
|
+
subscribers.clear();
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
broadcast,
|
|
99
|
+
clear,
|
|
100
|
+
handleSubscribe,
|
|
101
|
+
handleUnsubscribe
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
const createSetStateAndBroadcast = (setState, broadcast) => (value) => {
|
|
105
|
+
setState(value);
|
|
106
|
+
broadcast(value);
|
|
107
|
+
};
|
|
108
|
+
const createInvokeHandlers = (getState, setStateAndBroadcast, options) => {
|
|
109
|
+
const handleGet = () => getState();
|
|
110
|
+
const ensureRendererWritable = () => {
|
|
111
|
+
if (options.allowRendererSet === false) throw new SyncStateError("RENDERER_READONLY", createReadonlyErrorMessage(options.name, options.baseChannel ?? "state"), {
|
|
112
|
+
baseChannel: options.baseChannel ?? "state",
|
|
113
|
+
stateName: options.name
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
const resolveRendererValue = (value) => {
|
|
117
|
+
if (!options.resolveRendererValue) return value;
|
|
118
|
+
try {
|
|
119
|
+
return options.resolveRendererValue(value);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
let message = "";
|
|
122
|
+
let cause = void 0;
|
|
123
|
+
if (error instanceof Error) {
|
|
124
|
+
const { message: errorMessage } = error;
|
|
125
|
+
message = errorMessage;
|
|
126
|
+
cause = error;
|
|
127
|
+
} else message = createInvalidValueErrorMessage(options.name, options.baseChannel ?? "state");
|
|
128
|
+
throw new SyncStateError("RENDERER_INVALID_VALUE", message, {
|
|
129
|
+
baseChannel: options.baseChannel ?? "state",
|
|
130
|
+
cause,
|
|
131
|
+
stateName: options.name
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const handleSet = (_event, value) => {
|
|
136
|
+
ensureRendererWritable();
|
|
137
|
+
setStateAndBroadcast(resolveRendererValue(value));
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
handleGet,
|
|
141
|
+
handleSet
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
const registerIpcHandlers = (channels, invokeHandlers, subscriptions) => {
|
|
145
|
+
electron.ipcMain.handle(channels.getChannel, invokeHandlers.handleGet);
|
|
146
|
+
electron.ipcMain.handle(channels.setChannel, invokeHandlers.handleSet);
|
|
147
|
+
electron.ipcMain.on(channels.subscribeChannel, subscriptions.handleSubscribe);
|
|
148
|
+
electron.ipcMain.on(channels.unsubscribeChannel, subscriptions.handleUnsubscribe);
|
|
149
|
+
return () => {
|
|
150
|
+
electron.ipcMain.removeHandler(channels.getChannel);
|
|
151
|
+
electron.ipcMain.removeHandler(channels.setChannel);
|
|
152
|
+
electron.ipcMain.removeListener(channels.subscribeChannel, subscriptions.handleSubscribe);
|
|
153
|
+
electron.ipcMain.removeListener(channels.unsubscribeChannel, subscriptions.handleUnsubscribe);
|
|
154
|
+
subscriptions.clear();
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
const state = (options) => {
|
|
158
|
+
const mergedOptions = {
|
|
159
|
+
allowRendererSet: globalConfig.allowRendererSet,
|
|
160
|
+
baseChannel: globalConfig.baseChannel,
|
|
161
|
+
...options
|
|
162
|
+
};
|
|
163
|
+
if (!validateSerializable(mergedOptions.initialValue)) console.warn(`${INVALID_INITIAL_VALUE_WARNING_MESSAGE}\n State name: "${mergedOptions.name}"`);
|
|
164
|
+
const channels = createSyncStateChannels(mergedOptions);
|
|
165
|
+
const store = createSyncStateStore(mergedOptions.initialValue);
|
|
166
|
+
const subscriptions = createSubscriptions(channels, store.getState);
|
|
167
|
+
const setStateAndBroadcast = createSetStateAndBroadcast(store.setState, subscriptions.broadcast);
|
|
168
|
+
return {
|
|
169
|
+
dispose: registerIpcHandlers(channels, createInvokeHandlers(store.getState, setStateAndBroadcast, mergedOptions), subscriptions),
|
|
170
|
+
get: store.getState,
|
|
171
|
+
set: setStateAndBroadcast
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
//#endregion
|
|
176
|
+
exports.initSyncStateMain = initSyncStateMain;
|
|
177
|
+
exports.state = state;
|
package/dist/main.d.cts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/channels.d.ts
|
|
2
|
+
interface SyncStateChannelOptions {
|
|
3
|
+
name: string;
|
|
4
|
+
baseChannel?: string;
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/main.d.ts
|
|
8
|
+
interface SyncStateMainGlobalConfig {
|
|
9
|
+
baseChannel?: string;
|
|
10
|
+
allowRendererSet?: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare const initSyncStateMain: (config: SyncStateMainGlobalConfig) => void;
|
|
13
|
+
interface SyncStateMainOptions<StateValue> extends SyncStateChannelOptions {
|
|
14
|
+
initialValue: StateValue;
|
|
15
|
+
allowRendererSet?: boolean;
|
|
16
|
+
resolveRendererValue?: (value: StateValue) => StateValue;
|
|
17
|
+
}
|
|
18
|
+
interface SyncStateMainHandle<StateValue> {
|
|
19
|
+
get: () => StateValue;
|
|
20
|
+
set: (value: StateValue) => void;
|
|
21
|
+
dispose: () => void;
|
|
22
|
+
}
|
|
23
|
+
declare const state: <StateValue>(options: SyncStateMainOptions<StateValue>) => SyncStateMainHandle<StateValue>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { SyncStateMainGlobalConfig, SyncStateMainHandle, SyncStateMainOptions, initSyncStateMain, state };
|
|
26
|
+
//# sourceMappingURL=main.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.cts","names":[],"sources":["../src/channels.ts","../src/main.ts"],"sourcesContent":[],"mappings":";UAAiB,uBAAA;EAAA,IAAA,EAAA,MAAA;;;;;AAAA,UCSA,yBAAA,CDTuB;;;;ACSvB,cAOJ,iBAP6B,EAAA,CAAA,MAAA,EAOA,yBAPA,EAAA,GAAA,IAAA;AAO7B,UAII,oBAJyB,CAAA,UAAA,CAAA,SAIgB,uBAJS,CAAA;EAIlD,YAAA,EACD,UADqB;EACrB,gBAAA,CAAA,EAAA,OAAA;EAEiB,oBAAA,CAAA,EAAA,CAAA,KAAA,EAAA,UAAA,EAAA,GAAe,UAAf;;AAHyB,UAMzC,mBANyC,CAAA,UAAA,CAAA,CAAA;EAAuB,GAAA,EAAA,GAAA,GAOpE,UAPoE;EAMhE,GAAA,EAAA,CAAA,KAAA,EAEF,UAFqB,EAAA,GAAA,IAAA;EAuNvB,OAAA,EA0BZ,GAAA,GAAA,IAAA;;AAzBU,cADE,KACF,EAAA,CAAA,UAAA,CAAA,CAAA,OAAA,EAAA,oBAAA,CAAqB,UAArB,CAAA,EAAA,GACR,mBADQ,CACY,UADZ,CAAA"}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/channels.d.ts
|
|
2
|
+
interface SyncStateChannelOptions {
|
|
3
|
+
name: string;
|
|
4
|
+
baseChannel?: string;
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/main.d.ts
|
|
8
|
+
interface SyncStateMainGlobalConfig {
|
|
9
|
+
baseChannel?: string;
|
|
10
|
+
allowRendererSet?: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare const initSyncStateMain: (config: SyncStateMainGlobalConfig) => void;
|
|
13
|
+
interface SyncStateMainOptions<StateValue> extends SyncStateChannelOptions {
|
|
14
|
+
initialValue: StateValue;
|
|
15
|
+
allowRendererSet?: boolean;
|
|
16
|
+
resolveRendererValue?: (value: StateValue) => StateValue;
|
|
17
|
+
}
|
|
18
|
+
interface SyncStateMainHandle<StateValue> {
|
|
19
|
+
get: () => StateValue;
|
|
20
|
+
set: (value: StateValue) => void;
|
|
21
|
+
dispose: () => void;
|
|
22
|
+
}
|
|
23
|
+
declare const state: <StateValue>(options: SyncStateMainOptions<StateValue>) => SyncStateMainHandle<StateValue>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { SyncStateMainGlobalConfig, SyncStateMainHandle, SyncStateMainOptions, initSyncStateMain, state };
|
|
26
|
+
//# sourceMappingURL=main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","names":[],"sources":["../src/channels.ts","../src/main.ts"],"sourcesContent":[],"mappings":";UAAiB,uBAAA;EAAA,IAAA,EAAA,MAAA;;;;;AAAA,UCSA,yBAAA,CDTuB;;;;ACSvB,cAOJ,iBAP6B,EAAA,CAAA,MAAA,EAOA,yBAPA,EAAA,GAAA,IAAA;AAO7B,UAII,oBAJyB,CAAA,UAAA,CAAA,SAIgB,uBAJS,CAAA;EAIlD,YAAA,EACD,UADqB;EACrB,gBAAA,CAAA,EAAA,OAAA;EAEiB,oBAAA,CAAA,EAAA,CAAA,KAAA,EAAA,UAAA,EAAA,GAAe,UAAf;;AAHyB,UAMzC,mBANyC,CAAA,UAAA,CAAA,CAAA;EAAuB,GAAA,EAAA,GAAA,GAOpE,UAPoE;EAMhE,GAAA,EAAA,CAAA,KAAA,EAEF,UAFqB,EAAA,GAAA,IAAA;EAuNvB,OAAA,EA0BZ,GAAA,GAAA,IAAA;;AAzBU,cADE,KACF,EAAA,CAAA,UAAA,CAAA,CAAA,OAAA,EAAA,oBAAA,CAAqB,UAArB,CAAA,EAAA,GACR,mBADQ,CACY,UADZ,CAAA"}
|