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.
- package/.turbo/turbo-build.log +58 -54
- package/CHANGELOG.md +23 -0
- package/dist/{chunk-FFEEPZEG.js → chunk-AGF4HEDH.js} +61 -28
- package/dist/chunk-AGF4HEDH.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-YQNK5Y7B.js +4108 -0
- package/dist/inspector/chunk-YQNK5Y7B.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-KYV64IOC.js} +1057 -918
- package/dist/inspector/custom-element-KYV64IOC.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/tests/utils/transactions-changes.test.d.ts +2 -0
- package/dist/inspector/tests/utils/transactions-changes.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/utils/transactions-changes.d.ts +13 -13
- package/dist/inspector/utils/transactions-changes.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/react/index.js +4 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/react-core/index.js +2 -2
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-native/index.js +4 -1
- package/dist/react-native/index.js.map +1 -1
- package/dist/react-native-core/index.js +4 -1
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/react-native-core/provider.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/ContextManager.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/subscribe/SubscriptionScope.d.ts +3 -6
- package/dist/tools/subscribe/SubscriptionScope.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/tests/utils/transactions-changes.test.ts +102 -0
- package/src/inspector/ui/icons/add-icon.tsx +3 -3
- package/src/inspector/ui/modal.tsx +5 -2
- package/src/inspector/utils/history.ts +6 -6
- package/src/inspector/utils/transactions-changes.ts +37 -3
- package/src/inspector/viewer/breadcrumbs.tsx +5 -11
- package/src/inspector/viewer/header.tsx +67 -0
- package/src/inspector/viewer/history-view.tsx +13 -13
- package/src/inspector/viewer/page-stack.tsx +18 -26
- package/src/inspector/viewer/page.tsx +0 -1
- package/src/react/provider.tsx +6 -1
- package/src/react-core/hooks.ts +2 -2
- package/src/react-core/tests/useSuspenseCoState.test.tsx +47 -0
- package/src/react-native-core/provider.tsx +6 -1
- package/src/tools/coValues/account.ts +13 -2
- package/src/tools/implementation/ContextManager.ts +10 -0
- package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +8 -1
- package/src/tools/subscribe/SubscriptionScope.ts +61 -39
- 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,102 @@
|
|
|
1
|
+
import { assert, describe, expect, it } from "vitest";
|
|
2
|
+
import { setActiveAccount, setupJazzTestSync } from "jazz-tools/testing";
|
|
3
|
+
import { co, z } from "jazz-tools";
|
|
4
|
+
import * as TransactionsChanges from "../../utils/transactions-changes";
|
|
5
|
+
|
|
6
|
+
describe("transactions changes", async () => {
|
|
7
|
+
const account = await setupJazzTestSync();
|
|
8
|
+
setActiveAccount(account);
|
|
9
|
+
|
|
10
|
+
describe("ambiguous values in Group's transactions", () => {
|
|
11
|
+
it("isGroupExtension should return false for a CoMap", () => {
|
|
12
|
+
const value = co.map({ test: z.string() }).create({ test: "extend" })
|
|
13
|
+
.$jazz.raw;
|
|
14
|
+
|
|
15
|
+
const transactions = value.core.verifiedTransactions;
|
|
16
|
+
expect(
|
|
17
|
+
TransactionsChanges.isGroupExtension(
|
|
18
|
+
value,
|
|
19
|
+
transactions[0]?.changes?.[0],
|
|
20
|
+
),
|
|
21
|
+
).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("isGroupExtendRevocation should return false for a CoMap", () => {
|
|
25
|
+
const value = co.map({ test: z.string() }).create({ test: "revoked" })
|
|
26
|
+
.$jazz.raw;
|
|
27
|
+
|
|
28
|
+
const transactions = value.core.verifiedTransactions;
|
|
29
|
+
expect(
|
|
30
|
+
TransactionsChanges.isGroupExtendRevocation(
|
|
31
|
+
value,
|
|
32
|
+
transactions[0]?.changes?.[0],
|
|
33
|
+
),
|
|
34
|
+
).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("isGroupPromotion should return false for a CoMap", () => {
|
|
38
|
+
const value = co
|
|
39
|
+
.map({ parent_co_test: z.string() })
|
|
40
|
+
.create({ parent_co_test: "foo" }).$jazz.raw;
|
|
41
|
+
|
|
42
|
+
const transactions = value.core.verifiedTransactions;
|
|
43
|
+
expect(
|
|
44
|
+
TransactionsChanges.isGroupPromotion(
|
|
45
|
+
value,
|
|
46
|
+
transactions[0]?.changes?.[0],
|
|
47
|
+
),
|
|
48
|
+
).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("isUserPromotion should return false for a CoMap", () => {
|
|
52
|
+
const value = co.map({ everyone: z.string() }).create({ everyone: "foo" })
|
|
53
|
+
.$jazz.raw;
|
|
54
|
+
|
|
55
|
+
const transactions = value.core.verifiedTransactions;
|
|
56
|
+
expect(
|
|
57
|
+
TransactionsChanges.isUserPromotion(
|
|
58
|
+
value,
|
|
59
|
+
transactions[0]?.changes?.[0],
|
|
60
|
+
),
|
|
61
|
+
).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("isUserPromotion should return false for a CoMap", () => {
|
|
65
|
+
const value = co.map({ everyone: z.string() }).create({ everyone: "foo" })
|
|
66
|
+
.$jazz.raw;
|
|
67
|
+
|
|
68
|
+
const transactions = value.core.verifiedTransactions;
|
|
69
|
+
expect(
|
|
70
|
+
TransactionsChanges.isUserPromotion(
|
|
71
|
+
value,
|
|
72
|
+
transactions[0]?.changes?.[0],
|
|
73
|
+
),
|
|
74
|
+
).toBe(false);
|
|
75
|
+
|
|
76
|
+
const value2 = co.map({ co_z123: z.string() }).create({ co_z123: "foo" })
|
|
77
|
+
.$jazz.raw;
|
|
78
|
+
|
|
79
|
+
const transactions2 = value2.core.verifiedTransactions;
|
|
80
|
+
expect(
|
|
81
|
+
TransactionsChanges.isUserPromotion(
|
|
82
|
+
value2,
|
|
83
|
+
transactions2[0]?.changes?.[0],
|
|
84
|
+
),
|
|
85
|
+
).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("isKeyRevelation should return false for a CoMap", () => {
|
|
89
|
+
const value = co
|
|
90
|
+
.map({ "123_for_test": z.string() })
|
|
91
|
+
.create({ "123_for_test": "foo" }).$jazz.raw;
|
|
92
|
+
|
|
93
|
+
const transactions = value.core.verifiedTransactions;
|
|
94
|
+
expect(
|
|
95
|
+
TransactionsChanges.isKeyRevelation(
|
|
96
|
+
value,
|
|
97
|
+
transactions[0]?.changes?.[0],
|
|
98
|
+
),
|
|
99
|
+
).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -12,9 +12,9 @@ export function AddIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
|
12
12
|
>
|
|
13
13
|
<path
|
|
14
14
|
d="M4 12H20M12 4V20"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
strokeWidth="2"
|
|
16
|
+
strokeLinecap="round"
|
|
17
|
+
strokeLinejoin="round"
|
|
18
18
|
/>
|
|
19
19
|
</svg>
|
|
20
20
|
);
|
|
@@ -15,14 +15,15 @@ interface ModalProps {
|
|
|
15
15
|
onCancel?: () => void;
|
|
16
16
|
showButtons?: boolean;
|
|
17
17
|
className?: string;
|
|
18
|
+
wide?: boolean;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
const ModalContent = styled("dialog")
|
|
21
|
+
const ModalContent = styled("dialog")<{ wide?: boolean }>`
|
|
21
22
|
background-color: var(--j-background);
|
|
22
23
|
border-radius: var(--j-radius-lg);
|
|
23
24
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
|
24
25
|
border: 1px solid var(--j-border-color);
|
|
25
|
-
max-width: 32rem;
|
|
26
|
+
${(props) => (props.wide ? "max-width: 60vw;" : "max-width: 32rem;")}
|
|
26
27
|
margin-block: auto;
|
|
27
28
|
margin-inline: auto;
|
|
28
29
|
&::backdrop {
|
|
@@ -90,6 +91,7 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
|
|
90
91
|
onCancel,
|
|
91
92
|
showButtons = true,
|
|
92
93
|
className,
|
|
94
|
+
wide = false,
|
|
93
95
|
},
|
|
94
96
|
ref,
|
|
95
97
|
) => {
|
|
@@ -123,6 +125,7 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
|
|
123
125
|
role="dialog"
|
|
124
126
|
aria-labelledby="modal-heading"
|
|
125
127
|
onClose={onClose}
|
|
128
|
+
wide={wide}
|
|
126
129
|
>
|
|
127
130
|
<ModalHeader>
|
|
128
131
|
<Heading id="modal-heading">{heading}</Heading>
|
|
@@ -62,10 +62,10 @@ export function getTransactionChanges(
|
|
|
62
62
|
const firstChange = tx.changes[0]!;
|
|
63
63
|
|
|
64
64
|
if (
|
|
65
|
-
TransactionChanges.isItemAppend(firstChange) &&
|
|
65
|
+
TransactionChanges.isItemAppend(coValue, firstChange) &&
|
|
66
66
|
tx.changes.every(
|
|
67
67
|
(c) =>
|
|
68
|
-
TransactionChanges.isItemAppend(c) &&
|
|
68
|
+
TransactionChanges.isItemAppend(coValue, c) &&
|
|
69
69
|
areSameOpIds(c.after, firstChange.after),
|
|
70
70
|
)
|
|
71
71
|
) {
|
|
@@ -84,10 +84,10 @@ export function getTransactionChanges(
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
if (
|
|
87
|
-
TransactionChanges.isItemPrepend(firstChange) &&
|
|
87
|
+
TransactionChanges.isItemPrepend(coValue, firstChange) &&
|
|
88
88
|
tx.changes.every(
|
|
89
89
|
(c) =>
|
|
90
|
-
TransactionChanges.isItemPrepend(c) &&
|
|
90
|
+
TransactionChanges.isItemPrepend(coValue, c) &&
|
|
91
91
|
areSameOpIds(c.before, firstChange.before),
|
|
92
92
|
)
|
|
93
93
|
) {
|
|
@@ -106,8 +106,8 @@ export function getTransactionChanges(
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
if (
|
|
109
|
-
TransactionChanges.isItemDeletion(firstChange) &&
|
|
110
|
-
tx.changes.every((c) => TransactionChanges.isItemDeletion(c))
|
|
109
|
+
TransactionChanges.isItemDeletion(coValue, firstChange) &&
|
|
110
|
+
tx.changes.every((c) => TransactionChanges.isItemDeletion(coValue, c))
|
|
111
111
|
) {
|
|
112
112
|
const coValueBeforeDeletions = coValue.atTime(tx.madeAt - 1);
|
|
113
113
|
|
|
@@ -14,85 +14,119 @@ import type {
|
|
|
14
14
|
import { isCoId } from "../viewer/types";
|
|
15
15
|
|
|
16
16
|
export const isGroupExtension = (
|
|
17
|
+
coValue: RawCoValue,
|
|
17
18
|
change: any,
|
|
18
19
|
): change is Extract<
|
|
19
20
|
MapOpPayload<`child_${string}`, "extend">,
|
|
20
21
|
{ op: "set" }
|
|
21
22
|
> => {
|
|
23
|
+
if (coValue.core.isGroup() === false) return false;
|
|
22
24
|
return change?.op === "set" && change?.value === "extend";
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export const isGroupExtendRevocation = (
|
|
28
|
+
coValue: RawCoValue,
|
|
26
29
|
change: any,
|
|
27
30
|
): change is Extract<
|
|
28
31
|
MapOpPayload<`child_${string}`, "revoked">,
|
|
29
32
|
{ op: "set" }
|
|
30
33
|
> => {
|
|
34
|
+
if (coValue.core.isGroup() === false) return false;
|
|
31
35
|
return change?.op === "set" && change?.value === "revoked";
|
|
32
36
|
};
|
|
33
37
|
|
|
34
38
|
export const isGroupPromotion = (
|
|
39
|
+
coValue: RawCoValue,
|
|
35
40
|
change: any,
|
|
36
41
|
): change is Extract<
|
|
37
42
|
MapOpPayload<`parent_co_${string}`, AccountRole>,
|
|
38
43
|
{ op: "set" }
|
|
39
44
|
> => {
|
|
45
|
+
if (coValue.core.isGroup() === false) return false;
|
|
40
46
|
return change?.op === "set" && change?.key.startsWith("parent_co_");
|
|
41
47
|
};
|
|
42
48
|
|
|
43
49
|
export const isUserPromotion = (
|
|
50
|
+
coValue: RawCoValue,
|
|
44
51
|
change: any,
|
|
45
52
|
): change is Extract<MapOpPayload<CoID<RawCoValue>, Role>, { op: "set" }> => {
|
|
53
|
+
if (coValue.core.isGroup() === false) return false;
|
|
46
54
|
return (
|
|
47
55
|
change?.op === "set" && (isCoId(change?.key) || change?.key === "everyone")
|
|
48
56
|
);
|
|
49
57
|
};
|
|
50
58
|
|
|
51
59
|
export const isKeyRevelation = (
|
|
60
|
+
coValue: RawCoValue,
|
|
52
61
|
change: any,
|
|
53
62
|
): change is Extract<
|
|
54
63
|
MapOpPayload<`${string}_for_${string}`, string>,
|
|
55
64
|
{ op: "set" }
|
|
56
65
|
> => {
|
|
66
|
+
if (
|
|
67
|
+
coValue.core.isGroup() === false &&
|
|
68
|
+
coValue.headerMeta?.type !== "account"
|
|
69
|
+
)
|
|
70
|
+
return false;
|
|
57
71
|
return change?.op === "set" && change?.key.includes("_for_");
|
|
58
72
|
};
|
|
59
73
|
|
|
60
74
|
export const isPropertySet = (
|
|
75
|
+
coValue: RawCoValue,
|
|
61
76
|
change: any,
|
|
62
77
|
): change is Extract<MapOpPayload<string, any>, { op: "set" }> => {
|
|
63
78
|
return change?.op === "set" && "key" in change && "value" in change;
|
|
64
79
|
};
|
|
65
80
|
export const isPropertyDeletion = (
|
|
81
|
+
coValue: RawCoValue,
|
|
66
82
|
change: any,
|
|
67
83
|
): change is Extract<MapOpPayload<string, any>, { op: "del" }> => {
|
|
68
84
|
return change?.op === "del" && "key" in change;
|
|
69
85
|
};
|
|
70
86
|
|
|
71
87
|
export const isItemAppend = (
|
|
88
|
+
coValue: RawCoValue,
|
|
72
89
|
change: any,
|
|
73
90
|
): change is Extract<ListOpPayload<any>, { op: "app" }> => {
|
|
91
|
+
if (coValue.type !== "colist" && coValue.type !== "coplaintext") return false;
|
|
74
92
|
return change?.op === "app" && "after" in change && "value" in change;
|
|
75
93
|
};
|
|
76
94
|
export const isItemPrepend = (
|
|
95
|
+
coValue: RawCoValue,
|
|
77
96
|
change: any,
|
|
78
97
|
): change is Extract<ListOpPayload<any>, { op: "pre" }> => {
|
|
98
|
+
if (coValue.type !== "colist" && coValue.type !== "coplaintext") return false;
|
|
79
99
|
return change?.op === "pre" && "before" in change && "value" in change;
|
|
80
100
|
};
|
|
81
101
|
|
|
82
102
|
export const isItemDeletion = (
|
|
103
|
+
coValue: RawCoValue,
|
|
83
104
|
change: any,
|
|
84
105
|
): change is Extract<ListOpPayload<any>, { op: "del" }> => {
|
|
106
|
+
if (coValue.type !== "colist" && coValue.type !== "coplaintext") return false;
|
|
85
107
|
return change?.op === "del" && "insertion" in change;
|
|
86
108
|
};
|
|
87
109
|
|
|
88
|
-
export const isStreamStart = (
|
|
110
|
+
export const isStreamStart = (
|
|
111
|
+
coValue: RawCoValue,
|
|
112
|
+
change: any,
|
|
113
|
+
): change is BinaryStreamStart => {
|
|
114
|
+
if (coValue.type !== "coStream") return false;
|
|
89
115
|
return change?.type === "start" && "mimeType" in change;
|
|
90
116
|
};
|
|
91
117
|
|
|
92
|
-
export const isStreamChunk = (
|
|
118
|
+
export const isStreamChunk = (
|
|
119
|
+
coValue: RawCoValue,
|
|
120
|
+
change: any,
|
|
121
|
+
): change is BinaryStreamChunk => {
|
|
122
|
+
if (coValue.type !== "coStream") return false;
|
|
93
123
|
return change?.type === "chunk" && "chunk" in change;
|
|
94
124
|
};
|
|
95
125
|
|
|
96
|
-
export const isStreamEnd = (
|
|
126
|
+
export const isStreamEnd = (
|
|
127
|
+
coValue: RawCoValue,
|
|
128
|
+
change: any,
|
|
129
|
+
): change is BinaryStreamEnd => {
|
|
130
|
+
if (coValue.type !== "coStream") return false;
|
|
97
131
|
return change?.type === "end";
|
|
98
132
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { styled } from "goober";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { Button } from "../ui/button.js";
|
|
4
|
-
import {
|
|
4
|
+
import { useRouter } from "../router/context.js";
|
|
5
5
|
|
|
6
6
|
const BreadcrumbsContainer = styled("div")`
|
|
7
7
|
position: relative;
|
|
@@ -15,21 +15,15 @@ const Separator = styled("span")`
|
|
|
15
15
|
padding: 0 0.125rem;
|
|
16
16
|
`;
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
path
|
|
20
|
-
onBreadcrumbClick: (index: number) => void;
|
|
21
|
-
}
|
|
18
|
+
export const Breadcrumbs: React.FC<{}> = () => {
|
|
19
|
+
const { path, goToIndex } = useRouter();
|
|
22
20
|
|
|
23
|
-
export const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
|
|
24
|
-
path,
|
|
25
|
-
onBreadcrumbClick,
|
|
26
|
-
}) => {
|
|
27
21
|
return (
|
|
28
22
|
<BreadcrumbsContainer>
|
|
29
23
|
<Button
|
|
30
24
|
variant="link"
|
|
31
25
|
style={{ padding: "0 0.25rem" }}
|
|
32
|
-
onClick={() =>
|
|
26
|
+
onClick={() => goToIndex(-1)}
|
|
33
27
|
>
|
|
34
28
|
Home
|
|
35
29
|
</Button>
|
|
@@ -40,7 +34,7 @@ export const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
|
|
|
40
34
|
<Button
|
|
41
35
|
variant="link"
|
|
42
36
|
style={{ padding: "0 0.25rem" }}
|
|
43
|
-
onClick={() =>
|
|
37
|
+
onClick={() => goToIndex(index)}
|
|
44
38
|
>
|
|
45
39
|
{index === 0 ? page.name || "Root" : page.name}
|
|
46
40
|
</Button>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { CoID, RawCoValue } from "cojson";
|
|
2
|
+
import { styled } from "goober";
|
|
3
|
+
import React, { type PropsWithChildren, useState } from "react";
|
|
4
|
+
import { Button } from "../ui/button.js";
|
|
5
|
+
import { Input } from "../ui/input.js";
|
|
6
|
+
import { Breadcrumbs } from "./breadcrumbs.js";
|
|
7
|
+
import { DeleteLocalData } from "./delete-local-data.js";
|
|
8
|
+
import { useRouter } from "../router/context.js";
|
|
9
|
+
|
|
10
|
+
export function Header({
|
|
11
|
+
showDeleteLocalData = false,
|
|
12
|
+
showClose = false,
|
|
13
|
+
onClose,
|
|
14
|
+
children,
|
|
15
|
+
}: PropsWithChildren<{
|
|
16
|
+
showDeleteLocalData?: boolean;
|
|
17
|
+
showClose?: boolean;
|
|
18
|
+
onClose?: () => void;
|
|
19
|
+
}>) {
|
|
20
|
+
const [coValueId, setCoValueId] = useState<CoID<RawCoValue> | "">("");
|
|
21
|
+
const { path, setPage } = useRouter();
|
|
22
|
+
|
|
23
|
+
const handleCoValueIdSubmit = (e: React.FormEvent) => {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
if (coValueId) {
|
|
26
|
+
setPage(coValueId);
|
|
27
|
+
}
|
|
28
|
+
setCoValueId("");
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<HeaderContainer>
|
|
33
|
+
<Breadcrumbs />
|
|
34
|
+
{path.length !== 0 && (
|
|
35
|
+
<Form onSubmit={handleCoValueIdSubmit}>
|
|
36
|
+
<Input
|
|
37
|
+
label="CoValue ID"
|
|
38
|
+
style={{ fontFamily: "monospace" }}
|
|
39
|
+
hideLabel
|
|
40
|
+
placeholder="co_z1234567890abcdef123456789"
|
|
41
|
+
value={coValueId}
|
|
42
|
+
onChange={(e) => setCoValueId(e.target.value as CoID<RawCoValue>)}
|
|
43
|
+
/>
|
|
44
|
+
</Form>
|
|
45
|
+
)}
|
|
46
|
+
{children}
|
|
47
|
+
{showDeleteLocalData && <DeleteLocalData />}
|
|
48
|
+
{showClose && (
|
|
49
|
+
<Button variant="plain" type="button" onClick={onClose}>
|
|
50
|
+
Close
|
|
51
|
+
</Button>
|
|
52
|
+
)}
|
|
53
|
+
</HeaderContainer>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const HeaderContainer = styled("div")`
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: 1rem;
|
|
61
|
+
padding: 0 0.75rem;
|
|
62
|
+
margin: 0.75rem 0;
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
const Form = styled("form")`
|
|
66
|
+
width: 24rem;
|
|
67
|
+
`;
|
|
@@ -128,7 +128,7 @@ function mapTransactionToAction(
|
|
|
128
128
|
coValue: RawCoValue,
|
|
129
129
|
): string {
|
|
130
130
|
// Group changes
|
|
131
|
-
if (TransactionChanges.isUserPromotion(change)) {
|
|
131
|
+
if (TransactionChanges.isUserPromotion(coValue, change)) {
|
|
132
132
|
if (change.value === "revoked") {
|
|
133
133
|
return `${change.key} has been revoked`;
|
|
134
134
|
}
|
|
@@ -136,28 +136,28 @@ function mapTransactionToAction(
|
|
|
136
136
|
return `${change.key} has been promoted to ${change.value}`;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
if (TransactionChanges.isGroupExtension(change)) {
|
|
139
|
+
if (TransactionChanges.isGroupExtension(coValue, change)) {
|
|
140
140
|
const child = change.key.slice(6);
|
|
141
141
|
return `Group became a member of ${child}`;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
if (TransactionChanges.isGroupExtendRevocation(change)) {
|
|
144
|
+
if (TransactionChanges.isGroupExtendRevocation(coValue, change)) {
|
|
145
145
|
const child = change.key.slice(6);
|
|
146
146
|
return `Group's membership of ${child} has been revoked.`;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
if (TransactionChanges.isGroupPromotion(change)) {
|
|
149
|
+
if (TransactionChanges.isGroupPromotion(coValue, change)) {
|
|
150
150
|
const parent = change.key.slice(7);
|
|
151
151
|
return `Group ${parent} has been promoted to ${change.value}`;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
if (TransactionChanges.isKeyRevelation(change)) {
|
|
154
|
+
if (TransactionChanges.isKeyRevelation(coValue, change)) {
|
|
155
155
|
const [key, target] = change.key.split("_for_");
|
|
156
156
|
return `Key "${key}" has been revealed to "${target}"`;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
// coList changes
|
|
160
|
-
if (TransactionChanges.isItemAppend(change)) {
|
|
160
|
+
if (TransactionChanges.isItemAppend(coValue, change)) {
|
|
161
161
|
if (change.after === "start") {
|
|
162
162
|
return `"${change.value}" has been appended`;
|
|
163
163
|
}
|
|
@@ -171,7 +171,7 @@ function mapTransactionToAction(
|
|
|
171
171
|
return `"${change.value}" has been inserted after "${(after as any).value}"`;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
if (TransactionChanges.isItemPrepend(change)) {
|
|
174
|
+
if (TransactionChanges.isItemPrepend(coValue, change)) {
|
|
175
175
|
if (change.before === "end") {
|
|
176
176
|
return `"${change.value}" has been prepended`;
|
|
177
177
|
}
|
|
@@ -185,7 +185,7 @@ function mapTransactionToAction(
|
|
|
185
185
|
return `"${change.value}" has been inserted before "${(before as any).value}"`;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
if (TransactionChanges.isItemDeletion(change)) {
|
|
188
|
+
if (TransactionChanges.isItemDeletion(coValue, change)) {
|
|
189
189
|
const insertion = findListChange(change.insertion, coValue);
|
|
190
190
|
if (insertion === undefined) {
|
|
191
191
|
return `An undefined item has been deleted`;
|
|
@@ -195,24 +195,24 @@ function mapTransactionToAction(
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
// coStream changes
|
|
198
|
-
if (TransactionChanges.isStreamStart(change)) {
|
|
198
|
+
if (TransactionChanges.isStreamStart(coValue, change)) {
|
|
199
199
|
return `Stream started with mime type "${change.mimeType}" and file name "${change.fileName}"`;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
if (TransactionChanges.isStreamChunk(change)) {
|
|
202
|
+
if (TransactionChanges.isStreamChunk(coValue, change)) {
|
|
203
203
|
return `Stream chunk added`;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
if (TransactionChanges.isStreamEnd(change)) {
|
|
206
|
+
if (TransactionChanges.isStreamEnd(coValue, change)) {
|
|
207
207
|
return `Stream ended`;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
// coMap changes
|
|
211
|
-
if (TransactionChanges.isPropertySet(change)) {
|
|
211
|
+
if (TransactionChanges.isPropertySet(coValue, change)) {
|
|
212
212
|
return `Property "${change.key}" has been set to ${JSON.stringify(change.value)}`;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
if (TransactionChanges.isPropertyDeletion(change)) {
|
|
215
|
+
if (TransactionChanges.isPropertyDeletion(coValue, change)) {
|
|
216
216
|
return `Property "${change.key}" has been deleted`;
|
|
217
217
|
}
|
|
218
218
|
|
|
@@ -2,23 +2,11 @@ import { CoID, LocalNode, RawCoValue } from "cojson";
|
|
|
2
2
|
import { styled } from "goober";
|
|
3
3
|
import { Page } from "./page.js";
|
|
4
4
|
import { ErrorBoundary } from "../ui/error-boundary.js";
|
|
5
|
+
import { useRouter } from "../router/context.js";
|
|
6
|
+
import { useNode } from "../contexts/node.js";
|
|
7
|
+
import { HomePage } from "../pages/home.js";
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
interface PageInfo {
|
|
8
|
-
coId: CoID<RawCoValue>;
|
|
9
|
-
name?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Props for the PageStack component
|
|
13
|
-
interface PageStackProps {
|
|
14
|
-
path: PageInfo[];
|
|
15
|
-
node?: LocalNode | null;
|
|
16
|
-
goBack: () => void;
|
|
17
|
-
addPages: (pages: PageInfo[]) => void;
|
|
18
|
-
children?: React.ReactNode;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const PageStackContainer = styled("div")`
|
|
9
|
+
const PageStackContainer = styled("article")`
|
|
22
10
|
position: relative;
|
|
23
11
|
padding: 0 0.75rem;
|
|
24
12
|
overflow-y: auto;
|
|
@@ -27,25 +15,29 @@ const PageStackContainer = styled("div")`
|
|
|
27
15
|
font-size: 16px;
|
|
28
16
|
`;
|
|
29
17
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
18
|
+
type PageStackProps = {
|
|
19
|
+
homePage?: React.ReactNode;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function PageStack({ homePage }: PageStackProps) {
|
|
23
|
+
const { path, addPages, goBack } = useRouter();
|
|
24
|
+
const { localNode } = useNode();
|
|
25
|
+
|
|
37
26
|
const page = path[path.length - 1];
|
|
38
27
|
const index = path.length - 1;
|
|
39
28
|
|
|
29
|
+
if (path.length <= 0) {
|
|
30
|
+
return <PageStackContainer>{homePage ?? <HomePage />}</PageStackContainer>;
|
|
31
|
+
}
|
|
32
|
+
|
|
40
33
|
return (
|
|
41
34
|
<>
|
|
42
35
|
<PageStackContainer>
|
|
43
|
-
{
|
|
44
|
-
{node && page && (
|
|
36
|
+
{localNode && page && (
|
|
45
37
|
<ErrorBoundary title="An error occurred while rendering this CoValue">
|
|
46
38
|
<Page
|
|
47
39
|
coId={page.coId}
|
|
48
|
-
node={
|
|
40
|
+
node={localNode}
|
|
49
41
|
name={page.name || page.coId}
|
|
50
42
|
onHeaderClick={goBack}
|
|
51
43
|
onNavigate={addPages}
|
package/src/react/provider.tsx
CHANGED
|
@@ -58,6 +58,9 @@ export function JazzReactProvider<
|
|
|
58
58
|
);
|
|
59
59
|
const logoutReplacementActiveRef = useRef(false);
|
|
60
60
|
logoutReplacementActiveRef.current = Boolean(logOutReplacement);
|
|
61
|
+
const onAnonymousAccountDiscardedEnabled = Boolean(
|
|
62
|
+
onAnonymousAccountDiscarded,
|
|
63
|
+
);
|
|
61
64
|
|
|
62
65
|
const value = React.useSyncExternalStore<
|
|
63
66
|
JazzContextType<InstanceOfSchema<S>> | undefined
|
|
@@ -74,7 +77,9 @@ export function JazzReactProvider<
|
|
|
74
77
|
logOutReplacement: logoutReplacementActiveRef.current
|
|
75
78
|
? logOutReplacementRefCallback
|
|
76
79
|
: undefined,
|
|
77
|
-
onAnonymousAccountDiscarded:
|
|
80
|
+
onAnonymousAccountDiscarded: onAnonymousAccountDiscardedEnabled
|
|
81
|
+
? onAnonymousAccountDiscardedRefCallback
|
|
82
|
+
: undefined,
|
|
78
83
|
} satisfies JazzContextManagerProps<S>;
|
|
79
84
|
|
|
80
85
|
if (contextManager.propsChanged(props)) {
|
package/src/react-core/hooks.ts
CHANGED
|
@@ -489,7 +489,7 @@ export function useSuspenseCoState<
|
|
|
489
489
|
throw new Error("Subscription not found");
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
-
use(subscription.
|
|
492
|
+
use(subscription.getCachedPromise());
|
|
493
493
|
|
|
494
494
|
const getCurrentValue = () => {
|
|
495
495
|
const value = subscription.getCurrentValue();
|
|
@@ -824,7 +824,7 @@ export function useSuspenseAccount<
|
|
|
824
824
|
);
|
|
825
825
|
}
|
|
826
826
|
|
|
827
|
-
use(subscription.
|
|
827
|
+
use(subscription.getCachedPromise());
|
|
828
828
|
|
|
829
829
|
const getCurrentValue = () => {
|
|
830
830
|
const value = subscription.getCurrentValue();
|
|
@@ -370,6 +370,53 @@ describe("useSuspenseCoState", () => {
|
|
|
370
370
|
});
|
|
371
371
|
});
|
|
372
372
|
|
|
373
|
+
it("should throw error when CoValue becomes unauthorized", async () => {
|
|
374
|
+
const TestMap = co.map({
|
|
375
|
+
value: z.string(),
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const group = Group.create();
|
|
379
|
+
group.addMember("everyone", "reader");
|
|
380
|
+
|
|
381
|
+
// Create CoValue owned by another account without sharing
|
|
382
|
+
const map = TestMap.create(
|
|
383
|
+
{
|
|
384
|
+
value: "123",
|
|
385
|
+
},
|
|
386
|
+
group,
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
await createJazzTestAccount({
|
|
390
|
+
isCurrentActiveAccount: true,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const TestComponent = () => {
|
|
394
|
+
const value = useSuspenseCoState(TestMap, map.$jazz.id);
|
|
395
|
+
return <div>{value.value}</div>;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const { container } = await act(async () => {
|
|
399
|
+
return render(
|
|
400
|
+
<ErrorBoundary fallback={<div>Error!</div>}>
|
|
401
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
402
|
+
<TestComponent />
|
|
403
|
+
</Suspense>
|
|
404
|
+
</ErrorBoundary>,
|
|
405
|
+
);
|
|
406
|
+
});
|
|
407
|
+
await waitFor(() => {
|
|
408
|
+
expect(container.textContent).toContain("123");
|
|
409
|
+
expect(container.textContent).not.toContain("Loading...");
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
group.removeMember("everyone");
|
|
413
|
+
|
|
414
|
+
// Wait for error to be thrown (unauthorized access)
|
|
415
|
+
await waitFor(() => {
|
|
416
|
+
expect(container.textContent).toContain("Error!");
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
373
420
|
it("should update value when CoValue changes", async () => {
|
|
374
421
|
const TestMap = co.map({
|
|
375
422
|
value: z.string(),
|