jazz-tools 0.19.10 → 0.19.12

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.
Files changed (112) hide show
  1. package/.turbo/turbo-build.log +58 -54
  2. package/CHANGELOG.md +23 -0
  3. package/dist/{chunk-FFEEPZEG.js → chunk-AGF4HEDH.js} +61 -28
  4. package/dist/chunk-AGF4HEDH.js.map +1 -0
  5. package/dist/index.js +1 -1
  6. package/dist/inspector/account-switcher.d.ts +4 -0
  7. package/dist/inspector/account-switcher.d.ts.map +1 -0
  8. package/dist/inspector/chunk-YQNK5Y7B.js +4108 -0
  9. package/dist/inspector/chunk-YQNK5Y7B.js.map +1 -0
  10. package/dist/inspector/contexts/node.d.ts +19 -0
  11. package/dist/inspector/contexts/node.d.ts.map +1 -0
  12. package/dist/inspector/{custom-element-P76EIWEV.js → custom-element-KYV64IOC.js} +1057 -918
  13. package/dist/inspector/custom-element-KYV64IOC.js.map +1 -0
  14. package/dist/inspector/{viewer/new-app.d.ts → in-app.d.ts} +3 -3
  15. package/dist/inspector/in-app.d.ts.map +1 -0
  16. package/dist/inspector/index.d.ts +0 -11
  17. package/dist/inspector/index.d.ts.map +1 -1
  18. package/dist/inspector/index.js +56 -3910
  19. package/dist/inspector/index.js.map +1 -1
  20. package/dist/inspector/pages/home.d.ts +2 -0
  21. package/dist/inspector/pages/home.d.ts.map +1 -0
  22. package/dist/inspector/register-custom-element.js +1 -1
  23. package/dist/inspector/router/context.d.ts +12 -0
  24. package/dist/inspector/router/context.d.ts.map +1 -0
  25. package/dist/inspector/router/hash-router.d.ts +7 -0
  26. package/dist/inspector/router/hash-router.d.ts.map +1 -0
  27. package/dist/inspector/router/in-memory-router.d.ts +7 -0
  28. package/dist/inspector/router/in-memory-router.d.ts.map +1 -0
  29. package/dist/inspector/router/index.d.ts +5 -0
  30. package/dist/inspector/router/index.d.ts.map +1 -0
  31. package/dist/inspector/standalone.d.ts +6 -0
  32. package/dist/inspector/standalone.d.ts.map +1 -0
  33. package/dist/inspector/standalone.js +420 -0
  34. package/dist/inspector/standalone.js.map +1 -0
  35. package/dist/inspector/tests/router/hash-router.test.d.ts +2 -0
  36. package/dist/inspector/tests/router/hash-router.test.d.ts.map +1 -0
  37. package/dist/inspector/tests/router/in-memory-router.test.d.ts +2 -0
  38. package/dist/inspector/tests/router/in-memory-router.test.d.ts.map +1 -0
  39. package/dist/inspector/tests/utils/transactions-changes.test.d.ts +2 -0
  40. package/dist/inspector/tests/utils/transactions-changes.test.d.ts.map +1 -0
  41. package/dist/inspector/ui/modal.d.ts +1 -0
  42. package/dist/inspector/ui/modal.d.ts.map +1 -1
  43. package/dist/inspector/utils/transactions-changes.d.ts +13 -13
  44. package/dist/inspector/utils/transactions-changes.d.ts.map +1 -1
  45. package/dist/inspector/viewer/breadcrumbs.d.ts +1 -7
  46. package/dist/inspector/viewer/breadcrumbs.d.ts.map +1 -1
  47. package/dist/inspector/viewer/header.d.ts +7 -0
  48. package/dist/inspector/viewer/header.d.ts.map +1 -0
  49. package/dist/inspector/viewer/page-stack.d.ts +4 -13
  50. package/dist/inspector/viewer/page-stack.d.ts.map +1 -1
  51. package/dist/inspector/viewer/page.d.ts.map +1 -1
  52. package/dist/react/index.js +4 -1
  53. package/dist/react/index.js.map +1 -1
  54. package/dist/react/provider.d.ts.map +1 -1
  55. package/dist/react-core/index.js +2 -2
  56. package/dist/react-core/index.js.map +1 -1
  57. package/dist/react-native/index.js +4 -1
  58. package/dist/react-native/index.js.map +1 -1
  59. package/dist/react-native-core/index.js +4 -1
  60. package/dist/react-native-core/index.js.map +1 -1
  61. package/dist/react-native-core/provider.d.ts.map +1 -1
  62. package/dist/testing.js +1 -1
  63. package/dist/tools/coValues/account.d.ts +7 -1
  64. package/dist/tools/coValues/account.d.ts.map +1 -1
  65. package/dist/tools/implementation/ContextManager.d.ts.map +1 -1
  66. package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts +8 -1
  67. package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
  68. package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
  69. package/dist/tools/subscribe/SubscriptionScope.d.ts +3 -6
  70. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  71. package/dist/tools/testing.d.ts.map +1 -1
  72. package/package.json +9 -4
  73. package/src/inspector/account-switcher.tsx +440 -0
  74. package/src/inspector/contexts/node.tsx +129 -0
  75. package/src/inspector/custom-element.tsx +2 -2
  76. package/src/inspector/in-app.tsx +61 -0
  77. package/src/inspector/index.tsx +2 -22
  78. package/src/inspector/pages/home.tsx +77 -0
  79. package/src/inspector/router/context.ts +21 -0
  80. package/src/inspector/router/hash-router.tsx +128 -0
  81. package/src/inspector/{viewer/use-page-path.ts → router/in-memory-router.tsx} +31 -29
  82. package/src/inspector/router/index.ts +4 -0
  83. package/src/inspector/standalone.tsx +60 -0
  84. package/src/inspector/tests/router/hash-router.test.tsx +847 -0
  85. package/src/inspector/tests/router/in-memory-router.test.tsx +724 -0
  86. package/src/inspector/tests/utils/transactions-changes.test.ts +102 -0
  87. package/src/inspector/ui/icons/add-icon.tsx +3 -3
  88. package/src/inspector/ui/modal.tsx +5 -2
  89. package/src/inspector/utils/history.ts +6 -6
  90. package/src/inspector/utils/transactions-changes.ts +37 -3
  91. package/src/inspector/viewer/breadcrumbs.tsx +5 -11
  92. package/src/inspector/viewer/header.tsx +67 -0
  93. package/src/inspector/viewer/history-view.tsx +13 -13
  94. package/src/inspector/viewer/page-stack.tsx +18 -26
  95. package/src/inspector/viewer/page.tsx +0 -1
  96. package/src/react/provider.tsx +6 -1
  97. package/src/react-core/hooks.ts +2 -2
  98. package/src/react-core/tests/useSuspenseCoState.test.tsx +47 -0
  99. package/src/react-native-core/provider.tsx +6 -1
  100. package/src/tools/coValues/account.ts +13 -2
  101. package/src/tools/implementation/ContextManager.ts +10 -0
  102. package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +8 -1
  103. package/src/tools/subscribe/SubscriptionScope.ts +61 -39
  104. package/src/tools/tests/account.test.ts +11 -4
  105. package/src/tools/tests/schema.resolved.test.ts +3 -3
  106. package/tsup.config.ts +1 -0
  107. package/dist/chunk-FFEEPZEG.js.map +0 -1
  108. package/dist/inspector/custom-element-P76EIWEV.js.map +0 -1
  109. package/dist/inspector/viewer/new-app.d.ts.map +0 -1
  110. package/dist/inspector/viewer/use-page-path.d.ts +0 -10
  111. package/dist/inspector/viewer/use-page-path.d.ts.map +0 -1
  112. package/src/inspector/viewer/new-app.tsx +0 -156
@@ -0,0 +1,440 @@
1
+ import type { AgentSecret, RawAccountID } from "cojson";
2
+ import React, { useCallback, useEffect, useState } from "react";
3
+ import { styled } from "goober";
4
+ import { useNode } from "./contexts/node";
5
+ import { Button, Icon, Input, Modal } from "./ui";
6
+ import { AccountOrGroupText } from "./viewer/account-or-group-text";
7
+
8
+ interface Account {
9
+ id: RawAccountID;
10
+ secret: AgentSecret;
11
+ syncServer?: string;
12
+ }
13
+
14
+ interface JazzLoggedInSecret {
15
+ accountID: string;
16
+ accountSecret: string;
17
+ secretSeed?: number[];
18
+ provider?: string;
19
+ }
20
+
21
+ export function AccountSwitcher({
22
+ defaultSyncServer,
23
+ }: {
24
+ defaultSyncServer?: string;
25
+ }) {
26
+ const {
27
+ accountID: currentAccountId,
28
+ localNode,
29
+ createLocalNode,
30
+ reset,
31
+ } = useNode();
32
+ const [accounts, setAccounts] = useState<Account[]>(() => {
33
+ const storedAccounts = localStorage.getItem("inspectorAccounts");
34
+ return storedAccounts ? JSON.parse(storedAccounts) : [];
35
+ });
36
+ const [isModalOpen, setIsModalOpen] = useState(false);
37
+ const [selectedAccountId, setSelectedAccountId] =
38
+ useState<RawAccountID | null>(() => {
39
+ const lastSelectedAccountId = localStorage.getItem(
40
+ "lastSelectedAccountId",
41
+ );
42
+ return lastSelectedAccountId
43
+ ? (lastSelectedAccountId as RawAccountID)
44
+ : currentAccountId;
45
+ });
46
+ const [newAccountId, setNewAccountId] = useState("");
47
+ const [newAccountSecret, setNewAccountSecret] = useState("");
48
+ const [newAccountSyncServer, setNewAccountSyncServer] = useState(
49
+ "wss://cloud.jazz.tools",
50
+ );
51
+ const [addAccountError, setAddAccountError] = useState<string | null>(null);
52
+
53
+ const addAccount = (
54
+ id: RawAccountID,
55
+ secret: AgentSecret,
56
+ syncServer: string,
57
+ ) => {
58
+ const newAccount: Account = {
59
+ id,
60
+ secret,
61
+ syncServer,
62
+ };
63
+ const accountExists = accounts.some((account) => account.id === id);
64
+ if (!accountExists) {
65
+ const updatedAccounts = [...accounts, newAccount];
66
+ setAccounts(updatedAccounts);
67
+ localStorage.setItem(
68
+ "inspectorAccounts",
69
+ JSON.stringify(updatedAccounts),
70
+ );
71
+ setSelectedAccountId(id);
72
+ } else {
73
+ setSelectedAccountId(id);
74
+ }
75
+
76
+ setNewAccountId("");
77
+ setNewAccountSecret("");
78
+ setNewAccountSyncServer(syncServer);
79
+ setAddAccountError(null);
80
+ };
81
+
82
+ const deleteAccount = (accountId: RawAccountID) => {
83
+ const updatedAccounts = accounts.filter(
84
+ (account) => account.id !== accountId,
85
+ );
86
+ setAccounts(updatedAccounts);
87
+ localStorage.setItem("inspectorAccounts", JSON.stringify(updatedAccounts));
88
+ if (updatedAccounts.length > 0) {
89
+ setCurrentAccount(updatedAccounts[0]!.id);
90
+ } else {
91
+ setSelectedAccountId(null);
92
+ localStorage.removeItem("lastSelectedAccountId");
93
+ reset();
94
+ }
95
+ };
96
+
97
+ const deleteCurrentAccount = () => {
98
+ if (currentAccountId) {
99
+ deleteAccount(currentAccountId);
100
+ }
101
+ };
102
+
103
+ const setCurrentAccount = useCallback(
104
+ async (accountId: RawAccountID | null) => {
105
+ if (accountId === null) {
106
+ localStorage.removeItem("lastSelectedAccountId");
107
+ reset();
108
+ setSelectedAccountId(null);
109
+
110
+ return;
111
+ }
112
+
113
+ const account = accounts.find((a) => a.id === accountId);
114
+ if (!account) {
115
+ throw new Error(`Account ${accountId} not found in accounts list`);
116
+ }
117
+ const syncServer =
118
+ account.syncServer || defaultSyncServer || "wss://cloud.jazz.tools";
119
+
120
+ await createLocalNode(accountId, account.secret, syncServer);
121
+
122
+ setSelectedAccountId(accountId);
123
+ localStorage.setItem("lastSelectedAccountId", accountId);
124
+ },
125
+ [createLocalNode, accounts, defaultSyncServer],
126
+ );
127
+
128
+ const handleModalCancel = () => {
129
+ setSelectedAccountId(currentAccountId);
130
+ setNewAccountId("");
131
+ setNewAccountSecret("");
132
+ setNewAccountSyncServer("wss://cloud.jazz.tools/");
133
+ setAddAccountError(null);
134
+ setIsModalOpen(false);
135
+ };
136
+
137
+ const handleNewAccountIdChange = (
138
+ e: React.ChangeEvent<HTMLInputElement>,
139
+ ): void => {
140
+ const value = e.target.value;
141
+ setNewAccountId(value);
142
+
143
+ if (value.trim().startsWith("{") && value.trim().endsWith("}")) {
144
+ try {
145
+ const parsed: JazzLoggedInSecret = JSON.parse(value);
146
+ if (parsed.accountID && parsed.accountSecret) {
147
+ setNewAccountId(parsed.accountID);
148
+ setNewAccountSecret(parsed.accountSecret);
149
+ }
150
+ } catch (error) {
151
+ console.log("Failed to parse JSON:", error);
152
+ }
153
+ }
154
+ };
155
+
156
+ const handleAddAccountSubmit = async (e: React.FormEvent) => {
157
+ e.preventDefault();
158
+ if (!newAccountId || !newAccountSecret) {
159
+ setAddAccountError("Account ID and secret are required");
160
+ return;
161
+ }
162
+ try {
163
+ // first: try to use the credentials
164
+ // if successful, add the account to the list
165
+ addAccount(
166
+ newAccountId as RawAccountID,
167
+ newAccountSecret as AgentSecret,
168
+ newAccountSyncServer,
169
+ );
170
+
171
+ // await setCurrentAccount(newAccountId as RawAccountID);
172
+ setSelectedAccountId(newAccountId as RawAccountID);
173
+
174
+ setIsModalOpen(false);
175
+ } catch (error) {
176
+ setAddAccountError(
177
+ error instanceof Error ? error.message : "Failed to add account",
178
+ );
179
+ deleteAccount(newAccountId as RawAccountID);
180
+ }
181
+ };
182
+
183
+ useEffect(() => {
184
+ if (selectedAccountId) {
185
+ setCurrentAccount(selectedAccountId);
186
+ }
187
+ }, [selectedAccountId]);
188
+
189
+ return (
190
+ <>
191
+ <AccountSwitcherContainer>
192
+ <Button
193
+ variant="secondary"
194
+ onClick={() => {
195
+ setSelectedAccountId(currentAccountId);
196
+ setIsModalOpen(true);
197
+ }}
198
+ className="max-w-96"
199
+ >
200
+ {currentAccountId ? (
201
+ localNode ? (
202
+ <AccountOrGroupText
203
+ coId={currentAccountId}
204
+ showId
205
+ node={localNode}
206
+ />
207
+ ) : (
208
+ currentAccountId
209
+ )
210
+ ) : (
211
+ "Select account"
212
+ )}
213
+ </Button>
214
+ {currentAccountId && (
215
+ <Button
216
+ variant="secondary"
217
+ onClick={deleteCurrentAccount}
218
+ className="rounded-md p-2 ml-1"
219
+ aria-label="Remove account"
220
+ >
221
+ <Icon name="delete" className="text-gray-500" />
222
+ </Button>
223
+ )}
224
+ </AccountSwitcherContainer>
225
+
226
+ <Modal
227
+ isOpen={isModalOpen}
228
+ onClose={handleModalCancel}
229
+ heading="Select Account"
230
+ showButtons={false}
231
+ wide={true}
232
+ >
233
+ <ModalContentGrid>
234
+ <div>
235
+ <AccountSelectionFieldset>
236
+ <legend>Accounts</legend>
237
+ {accounts.length === 0 ? (
238
+ <p style={{ color: "var(--j-text-color)", margin: "0.5rem 0" }}>
239
+ No accounts available. Add one below.
240
+ </p>
241
+ ) : (
242
+ accounts.map((account) => (
243
+ <RadioOption key={account.id}>
244
+ <input
245
+ type="radio"
246
+ id={`account-${account.id}`}
247
+ name="account-selection"
248
+ value={account.id}
249
+ checked={selectedAccountId === account.id}
250
+ onChange={(e) =>
251
+ setSelectedAccountId(e.target.value as RawAccountID)
252
+ }
253
+ />
254
+ <label htmlFor={`account-${account.id}`}>
255
+ <AccountLabelContent>
256
+ {localNode ? (
257
+ <AccountOrGroupText
258
+ coId={account.id}
259
+ showId
260
+ node={localNode}
261
+ />
262
+ ) : (
263
+ account.id
264
+ )}
265
+ <SyncServerText>
266
+ {account.syncServer ?? "cloud.jazz.tools"}
267
+ </SyncServerText>
268
+ </AccountLabelContent>
269
+ </label>
270
+ </RadioOption>
271
+ ))
272
+ )}
273
+ </AccountSelectionFieldset>
274
+ </div>
275
+
276
+ <ModalAddAccountForm onSubmit={handleAddAccountSubmit}>
277
+ <FormHeading>Add an account to inspect</FormHeading>
278
+ <FormDescription>
279
+ Use the <CodeInline>jazz-logged-in-secret</CodeInline> local
280
+ storage key from within your Jazz app for your account
281
+ credentials. You can paste the full JSON object or enter the ID
282
+ and secret separately.
283
+ </FormDescription>
284
+ {addAccountError && <ErrorText>{addAccountError}</ErrorText>}
285
+ <Input
286
+ label="Account ID"
287
+ value={newAccountId}
288
+ required
289
+ placeholder="co_z1234567890abcdef123456789 or paste full JSON"
290
+ onChange={handleNewAccountIdChange}
291
+ />
292
+ <Input
293
+ label="Account secret"
294
+ type="password"
295
+ required
296
+ value={newAccountSecret}
297
+ onChange={(e) => setNewAccountSecret(e.target.value)}
298
+ placeholder="sealerSecret_ziz7NA12340abcdef123789..."
299
+ />
300
+ <Input
301
+ label="Sync server"
302
+ required
303
+ value={newAccountSyncServer}
304
+ onChange={(e) => setNewAccountSyncServer(e.target.value)}
305
+ placeholder="wss://cloud.jazz.tools/"
306
+ />
307
+ <Button className="mt-3" type="submit">
308
+ Add account
309
+ </Button>
310
+ </ModalAddAccountForm>
311
+ </ModalContentGrid>
312
+ </Modal>
313
+ </>
314
+ );
315
+ }
316
+
317
+ const AccountSwitcherContainer = styled("div")`
318
+ position: relative;
319
+ display: flex;
320
+ align-items: stretch;
321
+ gap: 0.25rem;
322
+ `;
323
+
324
+ const ModalContentGrid = styled("div")`
325
+ display: flex;
326
+ flex-direction: column;
327
+ gap: 1.5rem;
328
+
329
+ @media (min-width: 768px) {
330
+ display: grid;
331
+ grid-template-columns: 1fr 1fr;
332
+ gap: 1.5rem;
333
+ }
334
+ `;
335
+
336
+ const AccountSelectionFieldset = styled("fieldset")`
337
+ border: 1px solid var(--j-border-color);
338
+ border-radius: var(--j-radius-md);
339
+ padding: 1rem;
340
+ margin: 0;
341
+ display: flex;
342
+ flex-direction: column;
343
+ gap: 0.75rem;
344
+
345
+ legend {
346
+ padding: 0 0.5rem;
347
+ font-weight: 500;
348
+ color: var(--j-text-color);
349
+ }
350
+ `;
351
+
352
+ const ConfirmButtonContainer = styled("div")`
353
+ margin-top: 0.5rem;
354
+ display: flex;
355
+ justify-content: flex-end;
356
+ `;
357
+
358
+ const RadioOption = styled("div")`
359
+ display: flex;
360
+ align-items: flex-start;
361
+ gap: 0.5rem;
362
+
363
+ input[type="radio"] {
364
+ margin: 0;
365
+ cursor: pointer;
366
+ margin-top: 0.25rem;
367
+ }
368
+
369
+ label {
370
+ cursor: pointer;
371
+ color: var(--j-text-color);
372
+ flex: 1;
373
+ }
374
+ `;
375
+
376
+ const AccountLabelContent = styled("div")`
377
+ display: flex;
378
+ flex-direction: column;
379
+ gap: 0.25rem;
380
+ `;
381
+
382
+ const SyncServerText = styled("span")`
383
+ font-style: italic;
384
+ font-size: 0.875rem;
385
+ color: var(--j-text-color);
386
+ opacity: 0.7;
387
+ `;
388
+
389
+ const ModalAddAccountForm = styled("form")`
390
+ display: flex;
391
+ flex-direction: column;
392
+ gap: 1rem;
393
+
394
+ @media (max-width: 767px) {
395
+ padding-top: 1rem;
396
+ border-top: 1px solid var(--j-border-color);
397
+ }
398
+ `;
399
+
400
+ const ErrorText = styled("p")`
401
+ color: #b91c1c;
402
+ font-size: 0.875rem;
403
+ margin: 0;
404
+ padding: 0.5rem;
405
+ background-color: #fee2e2;
406
+ border-radius: var(--j-radius-sm);
407
+ border: 1px solid #f87171;
408
+
409
+ @media (prefers-color-scheme: dark) {
410
+ color: #fca5a5;
411
+ background-color: #7f1d1d;
412
+ border-color: #991b1b;
413
+ }
414
+ `;
415
+
416
+ const FormHeading = styled("h2")`
417
+ font-size: 1.5rem;
418
+ font-weight: 500;
419
+ color: #111827;
420
+
421
+ @media (prefers-color-scheme: dark) {
422
+ color: #fff;
423
+ }
424
+ `;
425
+
426
+ const FormDescription = styled("p")`
427
+ margin-bottom: 1.25rem;
428
+ font-size: 0.875rem;
429
+ color: var(--j-text-color);
430
+ `;
431
+
432
+ const CodeInline = styled("code")`
433
+ white-space: nowrap;
434
+ color: #0c0a09;
435
+ font-weight: 600;
436
+
437
+ @media (prefers-color-scheme: dark) {
438
+ color: #fff;
439
+ }
440
+ `;
@@ -0,0 +1,129 @@
1
+ import {
2
+ createContext,
3
+ type PropsWithChildren,
4
+ useContext,
5
+ useEffect,
6
+ useState,
7
+ } from "react";
8
+ import { CoID, LocalNode, RawAccount } from "cojson";
9
+ import { WasmCrypto } from "cojson/crypto/WasmCrypto";
10
+ import type { PureJSCrypto } from "cojson/dist/crypto/PureJSCrypto";
11
+ import { createWebSocketPeer } from "cojson-transport-ws";
12
+
13
+ type NodeContextType = {
14
+ accountID: CoID<RawAccount> | null;
15
+ localNode: LocalNode | null;
16
+ server: string;
17
+ createLocalNode: (
18
+ accountID: CoID<RawAccount>,
19
+ clientSecret: string,
20
+ server: string,
21
+ ) => Promise<void>;
22
+ reset: () => void;
23
+ };
24
+
25
+ export const NodeContext = createContext<NodeContextType>({
26
+ accountID: null,
27
+ localNode: null,
28
+ server: "wss://cloud.jazz.tools/",
29
+ createLocalNode: async () => {
30
+ throw new Error("createLocalNode not implemented");
31
+ },
32
+ reset: () => {
33
+ throw new Error("reset not implemented");
34
+ },
35
+ });
36
+
37
+ type NodeProviderProps = PropsWithChildren<{
38
+ localNode?: LocalNode | null;
39
+ accountID?: CoID<RawAccount> | null;
40
+ server?: string;
41
+ }>;
42
+
43
+ let crypto: WasmCrypto | PureJSCrypto | null = null;
44
+
45
+ async function getCrypto() {
46
+ if (crypto) return crypto;
47
+ crypto = await WasmCrypto.create();
48
+ return crypto;
49
+ }
50
+
51
+ export function NodeProvider(props: NodeProviderProps) {
52
+ const [accountID, setAccountID] = useState<CoID<RawAccount> | null>(
53
+ props?.accountID ?? null,
54
+ );
55
+ const [localNode, setLocalNode] = useState<LocalNode | null>(
56
+ props?.localNode ?? null,
57
+ );
58
+ const [server, setServer] = useState<string>(
59
+ props?.server ?? "wss://cloud.jazz.tools/",
60
+ );
61
+
62
+ useEffect(() => {
63
+ if (props.localNode !== undefined) setLocalNode(props.localNode);
64
+
65
+ if (props.accountID !== undefined) setAccountID(props.accountID);
66
+
67
+ if (props.server !== undefined) setServer(props.server);
68
+ }, [props.localNode, props.accountID, props.server]);
69
+
70
+ async function createLocalNode(
71
+ accountID: CoID<RawAccount>,
72
+ clientSecret: string,
73
+ server: string,
74
+ ) {
75
+ if (localNode) {
76
+ localNode.gracefulShutdown();
77
+ }
78
+
79
+ setLocalNode(null);
80
+
81
+ const wsPeer = createWebSocketPeer({
82
+ id: "cloud",
83
+ websocket: new WebSocket(server),
84
+ role: "server",
85
+ });
86
+
87
+ const crypto = await getCrypto();
88
+
89
+ const node = await LocalNode.withLoadedAccount({
90
+ accountID: accountID,
91
+ accountSecret: clientSecret as any,
92
+ sessionID: crypto.newRandomSessionID(accountID),
93
+ peers: [wsPeer],
94
+ crypto,
95
+ migration: async () => {
96
+ console.log("Not running any migration in inspector");
97
+ },
98
+ });
99
+
100
+ setLocalNode(node);
101
+ setAccountID(accountID);
102
+ setServer(server);
103
+ }
104
+
105
+ function reset() {
106
+ if (localNode) {
107
+ localNode.gracefulShutdown();
108
+ }
109
+ setLocalNode(null);
110
+ setAccountID(null);
111
+ setServer("wss://cloud.jazz.tools/");
112
+ }
113
+
114
+ return (
115
+ <NodeContext.Provider
116
+ value={{ accountID, localNode, server, createLocalNode, reset }}
117
+ >
118
+ {props.children}
119
+ </NodeContext.Provider>
120
+ );
121
+ }
122
+
123
+ export function useNode() {
124
+ const context = useContext(NodeContext);
125
+ if (!context) {
126
+ throw new Error("useNode must be used within a NodeProvider");
127
+ }
128
+ return context;
129
+ }
@@ -2,7 +2,7 @@ import React from "react";
2
2
  import { setup } from "goober";
3
3
  import { Account } from "jazz-tools";
4
4
  import { createRoot } from "react-dom/client";
5
- import { JazzInspectorInternal } from "./viewer/new-app.js";
5
+ import { InspectorInApp } from "./in-app.js";
6
6
 
7
7
  setup(React.createElement);
8
8
 
@@ -56,7 +56,7 @@ export class JazzInspectorElement extends HTMLElement {
56
56
  }
57
57
 
58
58
  this.root?.render(
59
- <JazzInspectorInternal
59
+ <InspectorInApp
60
60
  localNode={this.account.$jazz.localNode}
61
61
  accountId={this.account.$jazz.raw.id}
62
62
  />,
@@ -0,0 +1,61 @@
1
+ import { CoID, LocalNode, RawAccount } from "cojson";
2
+ import { styled } from "goober";
3
+ import { PageStack } from "./viewer/page-stack.js";
4
+ import { GlobalStyles } from "./ui/global-styles.js";
5
+ import { InspectorButton, type Position } from "./viewer/inspector-button.js";
6
+ import { useOpenInspector } from "./viewer/use-open-inspector.js";
7
+ import { NodeProvider } from "./contexts/node.js";
8
+ import { InMemoryRouterProvider } from "./router/in-memory-router.js";
9
+ import { Header } from "./viewer/header.js";
10
+
11
+ export function InspectorInApp({
12
+ position = "right",
13
+ localNode,
14
+ accountId,
15
+ }: {
16
+ position?: Position;
17
+ localNode?: LocalNode;
18
+ accountId?: CoID<RawAccount>;
19
+ }) {
20
+ const [open, setOpen] = useOpenInspector();
21
+
22
+ if (!open) {
23
+ return (
24
+ <InspectorButton position={position} onClick={() => setOpen(true)} />
25
+ );
26
+ }
27
+
28
+ return (
29
+ <NodeProvider localNode={localNode ?? null} accountID={accountId ?? null}>
30
+ <InMemoryRouterProvider>
31
+ <InspectorContainer as={GlobalStyles} style={{ zIndex: 999 }}>
32
+ <Header
33
+ showDeleteLocalData={true}
34
+ showClose={true}
35
+ onClose={() => setOpen(false)}
36
+ />
37
+
38
+ <PageStack />
39
+ </InspectorContainer>
40
+ </InMemoryRouterProvider>
41
+ </NodeProvider>
42
+ );
43
+ }
44
+
45
+ const InspectorContainer = styled("div")`
46
+ position: fixed;
47
+ height: 50vh;
48
+ max-height: 800px;
49
+ display: flex;
50
+ flex-direction: column;
51
+ bottom: 0;
52
+ left: 0;
53
+ width: 100%;
54
+ background-color: white;
55
+ border-top: 1px solid var(--j-border-color);
56
+ color: var(--j-text-color);
57
+
58
+ @media (prefers-color-scheme: dark) {
59
+ background-color: var(--j-background);
60
+ }
61
+ `;
@@ -1,28 +1,8 @@
1
1
  import React, { useEffect, useState } from "react";
2
-
3
- export { JazzInspectorInternal } from "./viewer/new-app.js";
4
- export { PageStack } from "./viewer/page-stack.js";
5
- export { Breadcrumbs } from "./viewer/breadcrumbs.js";
6
- export { AccountOrGroupText } from "./viewer/account-or-group-text.js";
7
-
8
- export { Button } from "./ui/button.js";
9
- export { Input } from "./ui/input.js";
10
- export { Select } from "./ui/select.js";
11
- export { Icon } from "./ui/icon.js";
12
- export { GlobalStyles } from "./ui/global-styles.js";
13
-
14
- export {
15
- resolveCoValue,
16
- useResolvedCoValue,
17
- } from "./viewer/use-resolve-covalue.js";
18
-
19
- export type { PageInfo } from "./viewer/types.js";
20
-
21
2
  import { setup } from "goober";
22
3
  import { useJazzContext } from "jazz-tools/react-core";
23
4
  import { Account } from "jazz-tools";
24
-
25
- import { JazzInspectorInternal } from "./viewer/new-app.js";
5
+ import { InspectorInApp } from "./in-app.js";
26
6
  import { Position } from "./viewer/inspector-button.js";
27
7
 
28
8
  export function JazzInspector({ position = "right" }: { position?: Position }) {
@@ -40,7 +20,7 @@ export function JazzInspector({ position = "right" }: { position?: Position }) {
40
20
  }
41
21
 
42
22
  return (
43
- <JazzInspectorInternal
23
+ <InspectorInApp
44
24
  position={position}
45
25
  localNode={localNode}
46
26
  accountId={me?.$jazz.raw.id}