jazz-tools 0.19.10 → 0.19.11
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/.turbo/turbo-build.log +53 -49
- package/CHANGELOG.md +11 -0
- package/dist/{chunk-FFEEPZEG.js → chunk-HX5S6W5E.js} +6 -2
- package/dist/chunk-HX5S6W5E.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/inspector/account-switcher.d.ts +4 -0
- package/dist/inspector/account-switcher.d.ts.map +1 -0
- package/dist/inspector/chunk-C6BJPHBQ.js +4096 -0
- package/dist/inspector/chunk-C6BJPHBQ.js.map +1 -0
- package/dist/inspector/contexts/node.d.ts +19 -0
- package/dist/inspector/contexts/node.d.ts.map +1 -0
- package/dist/inspector/{custom-element-P76EIWEV.js → custom-element-GJVBPZES.js} +1011 -884
- package/dist/inspector/custom-element-GJVBPZES.js.map +1 -0
- package/dist/inspector/{viewer/new-app.d.ts → in-app.d.ts} +3 -3
- package/dist/inspector/in-app.d.ts.map +1 -0
- package/dist/inspector/index.d.ts +0 -11
- package/dist/inspector/index.d.ts.map +1 -1
- package/dist/inspector/index.js +56 -3910
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/pages/home.d.ts +2 -0
- package/dist/inspector/pages/home.d.ts.map +1 -0
- package/dist/inspector/register-custom-element.js +1 -1
- package/dist/inspector/router/context.d.ts +12 -0
- package/dist/inspector/router/context.d.ts.map +1 -0
- package/dist/inspector/router/hash-router.d.ts +7 -0
- package/dist/inspector/router/hash-router.d.ts.map +1 -0
- package/dist/inspector/router/in-memory-router.d.ts +7 -0
- package/dist/inspector/router/in-memory-router.d.ts.map +1 -0
- package/dist/inspector/router/index.d.ts +5 -0
- package/dist/inspector/router/index.d.ts.map +1 -0
- package/dist/inspector/standalone.d.ts +6 -0
- package/dist/inspector/standalone.d.ts.map +1 -0
- package/dist/inspector/standalone.js +420 -0
- package/dist/inspector/standalone.js.map +1 -0
- package/dist/inspector/tests/router/hash-router.test.d.ts +2 -0
- package/dist/inspector/tests/router/hash-router.test.d.ts.map +1 -0
- package/dist/inspector/tests/router/in-memory-router.test.d.ts +2 -0
- package/dist/inspector/tests/router/in-memory-router.test.d.ts.map +1 -0
- package/dist/inspector/ui/modal.d.ts +1 -0
- package/dist/inspector/ui/modal.d.ts.map +1 -1
- package/dist/inspector/viewer/breadcrumbs.d.ts +1 -7
- package/dist/inspector/viewer/breadcrumbs.d.ts.map +1 -1
- package/dist/inspector/viewer/header.d.ts +7 -0
- package/dist/inspector/viewer/header.d.ts.map +1 -0
- package/dist/inspector/viewer/page-stack.d.ts +4 -13
- package/dist/inspector/viewer/page-stack.d.ts.map +1 -1
- package/dist/inspector/viewer/page.d.ts.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/coValues/account.d.ts +7 -1
- package/dist/tools/coValues/account.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts +8 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
- package/dist/tools/testing.d.ts.map +1 -1
- package/package.json +9 -4
- package/src/inspector/account-switcher.tsx +440 -0
- package/src/inspector/contexts/node.tsx +129 -0
- package/src/inspector/custom-element.tsx +2 -2
- package/src/inspector/in-app.tsx +61 -0
- package/src/inspector/index.tsx +2 -22
- package/src/inspector/pages/home.tsx +77 -0
- package/src/inspector/router/context.ts +21 -0
- package/src/inspector/router/hash-router.tsx +128 -0
- package/src/inspector/{viewer/use-page-path.ts → router/in-memory-router.tsx} +31 -29
- package/src/inspector/router/index.ts +4 -0
- package/src/inspector/standalone.tsx +60 -0
- package/src/inspector/tests/router/hash-router.test.tsx +847 -0
- package/src/inspector/tests/router/in-memory-router.test.tsx +724 -0
- package/src/inspector/ui/modal.tsx +5 -2
- package/src/inspector/viewer/breadcrumbs.tsx +5 -11
- package/src/inspector/viewer/header.tsx +67 -0
- package/src/inspector/viewer/page-stack.tsx +18 -26
- package/src/inspector/viewer/page.tsx +0 -1
- package/src/tools/coValues/account.ts +13 -2
- package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +8 -1
- package/src/tools/tests/account.test.ts +11 -4
- package/src/tools/tests/schema.resolved.test.ts +3 -3
- package/tsup.config.ts +1 -0
- package/dist/chunk-FFEEPZEG.js.map +0 -1
- package/dist/inspector/custom-element-P76EIWEV.js.map +0 -1
- package/dist/inspector/viewer/new-app.d.ts.map +0 -1
- package/dist/inspector/viewer/use-page-path.d.ts +0 -10
- package/dist/inspector/viewer/use-page-path.d.ts.map +0 -1
- package/src/inspector/viewer/new-app.tsx +0 -156
|
@@ -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 {
|
|
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
|
-
<
|
|
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
|
+
`;
|
package/src/inspector/index.tsx
CHANGED
|
@@ -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
|
-
<
|
|
23
|
+
<InspectorInApp
|
|
44
24
|
position={position}
|
|
45
25
|
localNode={localNode}
|
|
46
26
|
accountId={me?.$jazz.raw.id}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { styled } from "goober";
|
|
2
|
+
import { useNode } from "../contexts/node";
|
|
3
|
+
import { Heading } from "../ui/heading";
|
|
4
|
+
import { Button, Input } from "../ui";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { CoID, RawCoValue } from "cojson";
|
|
7
|
+
import { useRouter } from "../router";
|
|
8
|
+
|
|
9
|
+
export function HomePage() {
|
|
10
|
+
const { localNode, accountID } = useNode();
|
|
11
|
+
const { path, setPage } = useRouter();
|
|
12
|
+
const [coValueId, setCoValueId] = useState<CoID<RawCoValue> | "">("");
|
|
13
|
+
|
|
14
|
+
if (!localNode || !accountID) {
|
|
15
|
+
return <div>Loading...</div>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const handleCoValueIdSubmit = (e: React.FormEvent) => {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
if (coValueId) {
|
|
21
|
+
setPage(coValueId);
|
|
22
|
+
}
|
|
23
|
+
setCoValueId("");
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<CenteredForm
|
|
29
|
+
onSubmit={handleCoValueIdSubmit}
|
|
30
|
+
aria-hidden={path.length !== 0}
|
|
31
|
+
>
|
|
32
|
+
<Heading>Jazz CoValue Inspector</Heading>
|
|
33
|
+
|
|
34
|
+
<Input
|
|
35
|
+
label="CoValue ID"
|
|
36
|
+
className="font-mono"
|
|
37
|
+
hideLabel
|
|
38
|
+
placeholder="co_z1234567890abcdef123456789"
|
|
39
|
+
value={coValueId}
|
|
40
|
+
onChange={(e) => setCoValueId(e.target.value as CoID<RawCoValue>)}
|
|
41
|
+
/>
|
|
42
|
+
|
|
43
|
+
<Button type="submit" variant="primary">
|
|
44
|
+
Inspect CoValue
|
|
45
|
+
</Button>
|
|
46
|
+
|
|
47
|
+
<OrText>or</OrText>
|
|
48
|
+
|
|
49
|
+
<Button
|
|
50
|
+
variant="secondary"
|
|
51
|
+
onClick={() => {
|
|
52
|
+
setPage(accountID);
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
Inspect my account
|
|
56
|
+
</Button>
|
|
57
|
+
</CenteredForm>
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const CenteredForm = styled("form")`
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
position: relative;
|
|
66
|
+
top: -1.5rem;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
gap: 0.5rem;
|
|
69
|
+
height: 100%;
|
|
70
|
+
width: 100%;
|
|
71
|
+
max-width: 24rem;
|
|
72
|
+
margin: 0 auto;
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
const OrText = styled("p")`
|
|
76
|
+
text-align: center;
|
|
77
|
+
`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CoID, RawCoValue } from "cojson";
|
|
2
|
+
import { PageInfo } from "../viewer/types.js";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
|
|
5
|
+
export interface Router {
|
|
6
|
+
path: PageInfo[];
|
|
7
|
+
setPage: (coId: CoID<RawCoValue>) => void;
|
|
8
|
+
addPages: (newPages: PageInfo[]) => void;
|
|
9
|
+
goToIndex: (index: number) => void;
|
|
10
|
+
goBack: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const RouterContext = createContext<Router | null>(null);
|
|
14
|
+
|
|
15
|
+
export function useRouter(): Router {
|
|
16
|
+
const context = useContext(RouterContext);
|
|
17
|
+
if (!context) {
|
|
18
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
19
|
+
}
|
|
20
|
+
return context;
|
|
21
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
ReactNode,
|
|
3
|
+
useState,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { Router, RouterContext } from "./context.js";
|
|
9
|
+
import { PageInfo } from "../viewer/types.js";
|
|
10
|
+
import { CoID, RawCoValue } from "cojson";
|
|
11
|
+
|
|
12
|
+
export function HashRouterProvider({
|
|
13
|
+
children,
|
|
14
|
+
defaultPath,
|
|
15
|
+
}: {
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
defaultPath?: PageInfo[];
|
|
18
|
+
}) {
|
|
19
|
+
const [path, setPath] = useState<PageInfo[]>(() => {
|
|
20
|
+
if (typeof window === "undefined") return defaultPath || [];
|
|
21
|
+
const hash = window.location.hash.slice(2); // Remove '#/'
|
|
22
|
+
|
|
23
|
+
const defaultEncoded = encodePathToHash(defaultPath || []);
|
|
24
|
+
|
|
25
|
+
if (defaultPath) {
|
|
26
|
+
window.history.pushState({}, "", `#/${defaultEncoded}`);
|
|
27
|
+
return defaultPath;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (hash) {
|
|
31
|
+
const path = decodePathFromHash(hash);
|
|
32
|
+
return path;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
window.history.pushState({}, "", `#/${encodePathToHash([])}`);
|
|
36
|
+
return [];
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const updatePath = useCallback((newPath: PageInfo[]) => {
|
|
40
|
+
setPath(newPath);
|
|
41
|
+
if (typeof window !== "undefined") {
|
|
42
|
+
const hash = encodePathToHash(newPath);
|
|
43
|
+
window.history.pushState({}, "", `#/${hash}`);
|
|
44
|
+
}
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (typeof window === "undefined") return;
|
|
49
|
+
|
|
50
|
+
const handleHashChange = () => {
|
|
51
|
+
const hash = window.location.hash.slice(2);
|
|
52
|
+
const currentPath = encodePathToHash(path);
|
|
53
|
+
|
|
54
|
+
if (hash === currentPath) return;
|
|
55
|
+
|
|
56
|
+
if (hash) {
|
|
57
|
+
try {
|
|
58
|
+
const newPath = decodePathFromHash(hash);
|
|
59
|
+
setPath(newPath);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error("Failed to parse hash:", e);
|
|
62
|
+
}
|
|
63
|
+
} else if (defaultPath) {
|
|
64
|
+
setPath(defaultPath);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
69
|
+
return () => window.removeEventListener("hashchange", handleHashChange);
|
|
70
|
+
}, [path, defaultPath]);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (defaultPath) {
|
|
74
|
+
updatePath(defaultPath);
|
|
75
|
+
}
|
|
76
|
+
}, [defaultPath]);
|
|
77
|
+
|
|
78
|
+
const router: Router = useMemo(() => {
|
|
79
|
+
const addPages = (newPages: PageInfo[]) => {
|
|
80
|
+
updatePath([...path, ...newPages]);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const goToIndex = (index: number) => {
|
|
84
|
+
updatePath(path.slice(0, index + 1));
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const setPage = (coId: CoID<RawCoValue>) => {
|
|
88
|
+
updatePath([{ coId, name: "Root" }]);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const goBack = () => {
|
|
92
|
+
updatePath(path.slice(0, path.length - 1));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
path,
|
|
97
|
+
addPages,
|
|
98
|
+
goToIndex,
|
|
99
|
+
setPage,
|
|
100
|
+
goBack,
|
|
101
|
+
};
|
|
102
|
+
}, [path, updatePath]);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<RouterContext.Provider value={router}>{children}</RouterContext.Provider>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function encodePathToHash(path: PageInfo[]): string {
|
|
110
|
+
return path
|
|
111
|
+
.map((page) => {
|
|
112
|
+
if (page.name && page.name !== "Root") {
|
|
113
|
+
return `${page.coId}:${encodeURIComponent(page.name)}`;
|
|
114
|
+
}
|
|
115
|
+
return page.coId;
|
|
116
|
+
})
|
|
117
|
+
.join("/");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function decodePathFromHash(hash: string): PageInfo[] {
|
|
121
|
+
return hash.split("/").map((segment) => {
|
|
122
|
+
const [coId, encodedName] = segment.split(":");
|
|
123
|
+
return {
|
|
124
|
+
coId,
|
|
125
|
+
name: encodedName ? decodeURIComponent(encodedName) : undefined,
|
|
126
|
+
} as PageInfo;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import type { CoID, RawCoValue } from "cojson";
|
|
3
|
+
import { Router, RouterContext } from "./context.js";
|
|
4
|
+
import { PageInfo } from "../viewer/types.js";
|
|
4
5
|
|
|
5
6
|
const STORAGE_KEY = "jazz-inspector-paths";
|
|
6
7
|
|
|
7
|
-
export function
|
|
8
|
+
export function InMemoryRouterProvider({
|
|
9
|
+
children,
|
|
10
|
+
defaultPath,
|
|
11
|
+
}: {
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
defaultPath?: PageInfo[];
|
|
14
|
+
}) {
|
|
8
15
|
const [path, setPath] = useState<PageInfo[]>(() => {
|
|
9
16
|
if (typeof window === "undefined") return [];
|
|
10
17
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
@@ -29,38 +36,33 @@ export function usePagePath(defaultPath?: PageInfo[]) {
|
|
|
29
36
|
}
|
|
30
37
|
}, [defaultPath, path, updatePath]);
|
|
31
38
|
|
|
32
|
-
const
|
|
33
|
-
(newPages: PageInfo[]) => {
|
|
39
|
+
const router: Router = useMemo(() => {
|
|
40
|
+
const addPages = (newPages: PageInfo[]) => {
|
|
34
41
|
updatePath([...path, ...newPages]);
|
|
35
|
-
}
|
|
36
|
-
[path, updatePath],
|
|
37
|
-
);
|
|
42
|
+
};
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
(index: number) => {
|
|
44
|
+
const goToIndex = (index: number) => {
|
|
41
45
|
updatePath(path.slice(0, index + 1));
|
|
42
|
-
}
|
|
43
|
-
[path, updatePath],
|
|
44
|
-
);
|
|
46
|
+
};
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
(coId: CoID<RawCoValue>) => {
|
|
48
|
+
const setPage = (coId: CoID<RawCoValue>) => {
|
|
48
49
|
updatePath([{ coId, name: "Root" }]);
|
|
49
|
-
}
|
|
50
|
-
[updatePath],
|
|
51
|
-
);
|
|
50
|
+
};
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
if (path.length > 1) {
|
|
52
|
+
const goBack = () => {
|
|
55
53
|
updatePath(path.slice(0, path.length - 1));
|
|
56
|
-
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
path,
|
|
58
|
+
addPages,
|
|
59
|
+
goToIndex,
|
|
60
|
+
setPage,
|
|
61
|
+
goBack,
|
|
62
|
+
};
|
|
57
63
|
}, [path, updatePath]);
|
|
58
64
|
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
addPages,
|
|
63
|
-
goToIndex,
|
|
64
|
-
goBack,
|
|
65
|
-
};
|
|
65
|
+
return (
|
|
66
|
+
<RouterContext.Provider value={router}>{children}</RouterContext.Provider>
|
|
67
|
+
);
|
|
66
68
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { HashRouterProvider } from "./router";
|
|
3
|
+
import { setup, styled } from "goober";
|
|
4
|
+
import { NodeContext, NodeProvider } from "./contexts/node";
|
|
5
|
+
import { Header } from "./viewer/header";
|
|
6
|
+
import { GlobalStyles } from "./ui/global-styles";
|
|
7
|
+
import { PageStack } from "./viewer/page-stack";
|
|
8
|
+
import { AccountSwitcher } from "./account-switcher";
|
|
9
|
+
|
|
10
|
+
type InspectorAppProps = {
|
|
11
|
+
defaultSyncServer?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
setup(React.createElement);
|
|
15
|
+
|
|
16
|
+
export default function InspectorStandalone(props: InspectorAppProps) {
|
|
17
|
+
return (
|
|
18
|
+
<HashRouterProvider>
|
|
19
|
+
<NodeProvider>
|
|
20
|
+
<InspectorContainer as={GlobalStyles}>
|
|
21
|
+
<Header>
|
|
22
|
+
<AccountSwitcher defaultSyncServer={props.defaultSyncServer} />
|
|
23
|
+
</Header>
|
|
24
|
+
<NodeContext.Consumer>
|
|
25
|
+
{({ accountID }) =>
|
|
26
|
+
accountID ? (
|
|
27
|
+
<PageStack />
|
|
28
|
+
) : (
|
|
29
|
+
<CenteredMessage>
|
|
30
|
+
Select an account to connect to the inspector.
|
|
31
|
+
</CenteredMessage>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
</NodeContext.Consumer>
|
|
35
|
+
</InspectorContainer>
|
|
36
|
+
</NodeProvider>
|
|
37
|
+
</HashRouterProvider>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const InspectorContainer = styled("div")`
|
|
42
|
+
height: 100vh;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
color: #44403c; /* text-stone-700 */
|
|
47
|
+
background-color: #fff; /* bg-white */
|
|
48
|
+
|
|
49
|
+
@media (prefers-color-scheme: dark) {
|
|
50
|
+
color: #d6d3d1; /* text-stone-300 */
|
|
51
|
+
background-color: #0c0a09; /* bg-stone-950 */
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
const CenteredMessage = styled("p")`
|
|
56
|
+
text-align: center;
|
|
57
|
+
margin: 0;
|
|
58
|
+
padding: 1rem;
|
|
59
|
+
color: var(--j-text-color);
|
|
60
|
+
`;
|