jazz-tools 0.19.1 → 0.19.3
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 +63 -63
- package/CHANGELOG.md +21 -0
- package/dist/{chunk-NCNM6UDZ.js → chunk-JPWM4CS2.js} +4 -2
- package/dist/{chunk-NCNM6UDZ.js.map → chunk-JPWM4CS2.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/inspector/{custom-element-QESCMFY7.js → custom-element-3JAYHXWQ.js} +1134 -464
- package/dist/inspector/custom-element-3JAYHXWQ.js.map +1 -0
- package/dist/inspector/index.js +1104 -434
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/register-custom-element.js +1 -1
- package/dist/inspector/tests/utils/history.test.d.ts +2 -0
- package/dist/inspector/tests/utils/history.test.d.ts.map +1 -0
- package/dist/inspector/tests/viewer/co-value-editor.test.d.ts +2 -0
- package/dist/inspector/tests/viewer/co-value-editor.test.d.ts.map +1 -0
- package/dist/inspector/tests/viewer/comap-view.test.d.ts +2 -0
- package/dist/inspector/tests/viewer/comap-view.test.d.ts.map +1 -0
- package/dist/inspector/ui/icon.d.ts +6 -0
- package/dist/inspector/ui/icon.d.ts.map +1 -1
- package/dist/inspector/ui/icons/add-icon.d.ts +2 -0
- package/dist/inspector/ui/icons/add-icon.d.ts.map +1 -0
- package/dist/inspector/ui/icons/edit-icon.d.ts +2 -0
- package/dist/inspector/ui/icons/edit-icon.d.ts.map +1 -0
- package/dist/inspector/ui/icons/history.d.ts +2 -0
- package/dist/inspector/ui/icons/history.d.ts.map +1 -0
- package/dist/inspector/utils/history.d.ts +3 -0
- package/dist/inspector/utils/history.d.ts.map +1 -0
- package/dist/inspector/utils/permissions.d.ts +3 -0
- package/dist/inspector/utils/permissions.d.ts.map +1 -0
- package/dist/inspector/utils/transactions-changes.d.ts +38 -0
- package/dist/inspector/utils/transactions-changes.d.ts.map +1 -0
- package/dist/inspector/viewer/co-map-view.d.ts +9 -0
- package/dist/inspector/viewer/co-map-view.d.ts.map +1 -0
- package/dist/inspector/viewer/co-value-editor.d.ts +10 -0
- package/dist/inspector/viewer/co-value-editor.d.ts.map +1 -0
- package/dist/inspector/viewer/grid-view.d.ts +3 -2
- package/dist/inspector/viewer/grid-view.d.ts.map +1 -1
- package/dist/inspector/viewer/history-view.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/CoFieldInit.d.ts +2 -1
- package/dist/tools/coValues/CoFieldInit.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.d.ts +3 -2
- package/dist/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/unionUtils.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/inspector/tests/utils/history.test.ts +401 -0
- package/src/inspector/tests/viewer/co-value-editor.test.tsx +903 -0
- package/src/inspector/tests/viewer/comap-view.test.tsx +889 -0
- package/src/inspector/ui/icon.tsx +6 -0
- package/src/inspector/ui/icons/add-icon.tsx +21 -0
- package/src/inspector/ui/icons/edit-icon.tsx +17 -0
- package/src/inspector/ui/icons/history.tsx +28 -0
- package/src/inspector/ui/modal.tsx +3 -3
- package/src/inspector/utils/history.ts +49 -0
- package/src/inspector/utils/permissions.ts +10 -0
- package/src/inspector/utils/transactions-changes.ts +98 -0
- package/src/inspector/viewer/co-map-view.tsx +324 -0
- package/src/inspector/viewer/co-value-editor.tsx +164 -0
- package/src/inspector/viewer/grid-view.tsx +140 -10
- package/src/inspector/viewer/history-view.tsx +19 -119
- package/src/inspector/viewer/page.tsx +13 -0
- package/src/react-core/tests/usePassPhraseAuth.test.ts +1 -1
- package/src/tools/coValues/CoFieldInit.ts +6 -3
- package/src/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.ts +12 -7
- package/src/tools/implementation/zodSchema/unionUtils.ts +3 -4
- package/src/tools/tests/coVector.test.ts +43 -0
- package/dist/inspector/custom-element-QESCMFY7.js.map +0 -1
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { CautionIcon } from "./icons/caution.js";
|
|
2
2
|
import { ChevronDownIcon } from "./icons/chevron-down-icon.js";
|
|
3
3
|
import { DeleteIcon } from "./icons/delete-icon.js";
|
|
4
|
+
import { EditIcon } from "./icons/edit-icon.js";
|
|
4
5
|
import { LinkIcon } from "./icons/link-icon.js";
|
|
6
|
+
import { HistoryIcon } from "./icons/history.js";
|
|
7
|
+
import { AddIcon } from "./icons/add-icon.js";
|
|
5
8
|
|
|
6
9
|
const icons = {
|
|
7
10
|
caution: CautionIcon,
|
|
8
11
|
chevronDown: ChevronDownIcon,
|
|
9
12
|
delete: DeleteIcon,
|
|
13
|
+
edit: EditIcon,
|
|
10
14
|
link: LinkIcon,
|
|
15
|
+
history: HistoryIcon,
|
|
16
|
+
add: AddIcon,
|
|
11
17
|
};
|
|
12
18
|
|
|
13
19
|
// copied from tailwind line height https://tailwindcss.com/docs/font-size
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function AddIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
{...props}
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
width="24"
|
|
7
|
+
height="24"
|
|
8
|
+
viewBox="0 0 24 24"
|
|
9
|
+
fill="none"
|
|
10
|
+
stroke="currentColor"
|
|
11
|
+
strokeLinejoin="round"
|
|
12
|
+
>
|
|
13
|
+
<path
|
|
14
|
+
d="M4 12H20M12 4V20"
|
|
15
|
+
stroke-width="2"
|
|
16
|
+
stroke-linecap="round"
|
|
17
|
+
stroke-linejoin="round"
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function EditIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
{...props}
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
width="24"
|
|
7
|
+
height="24"
|
|
8
|
+
viewBox="0 0 24 24"
|
|
9
|
+
fill="none"
|
|
10
|
+
stroke="currentColor"
|
|
11
|
+
strokeLinejoin="round"
|
|
12
|
+
className="lucide lucide-edit lucide-pencil"
|
|
13
|
+
>
|
|
14
|
+
<path d="M13.2942 7.95881C13.5533 7.63559 13.5013 7.16358 13.178 6.90453C12.8548 6.64549 12.3828 6.6975 12.1238 7.02072L13.2942 7.95881ZM6.811 14.8488L7.37903 15.3385C7.38489 15.3317 7.39062 15.3248 7.39623 15.3178L6.811 14.8488ZM6.64 15.2668L5.89146 15.2179L5.8908 15.2321L6.64 15.2668ZM6.5 18.2898L5.7508 18.2551C5.74908 18.2923 5.75013 18.3296 5.75396 18.3667L6.5 18.2898ZM7.287 18.9768L7.31152 19.7264C7.36154 19.7247 7.41126 19.7181 7.45996 19.7065L7.287 18.9768ZM10.287 18.2658L10.46 18.9956L10.4716 18.9927L10.287 18.2658ZM10.672 18.0218L11.2506 18.4991L11.2571 18.491L10.672 18.0218ZM17.2971 10.959C17.5562 10.6358 17.5043 10.1638 17.1812 9.90466C16.8581 9.64552 16.386 9.69742 16.1269 10.0206L17.2971 10.959ZM12.1269 7.02052C11.8678 7.34365 11.9196 7.81568 12.2428 8.07484C12.5659 8.33399 13.0379 8.28213 13.2971 7.95901L12.1269 7.02052ZM14.3 5.50976L14.8851 5.97901C14.8949 5.96672 14.9044 5.95412 14.9135 5.94123L14.3 5.50976ZM15.929 5.18976L16.4088 4.61332C16.3849 4.59344 16.3598 4.57507 16.3337 4.5583L15.929 5.18976ZM18.166 7.05176L18.6968 6.52192C18.6805 6.50561 18.6635 6.49007 18.6458 6.47532L18.166 7.05176ZM18.5029 7.87264L19.2529 7.87676V7.87676L18.5029 7.87264ZM18.157 8.68976L17.632 8.15412C17.6108 8.17496 17.5908 8.19704 17.5721 8.22025L18.157 8.68976ZM16.1271 10.0203C15.8678 10.3433 15.9195 10.8153 16.2425 11.0746C16.5655 11.3339 17.0376 11.2823 17.2969 10.9593L16.1271 10.0203ZM13.4537 7.37862C13.3923 6.96898 13.0105 6.68666 12.6009 6.74805C12.1912 6.80943 11.9089 7.19127 11.9703 7.60091L13.4537 7.37862ZM16.813 11.2329C17.2234 11.1772 17.5109 10.7992 17.4552 10.3888C17.3994 9.97834 17.0215 9.69082 16.611 9.74659L16.813 11.2329ZM12.1238 7.02072L6.22577 14.3797L7.39623 15.3178L13.2942 7.95881L12.1238 7.02072ZM6.24297 14.359C6.03561 14.5995 5.91226 14.9011 5.89159 15.218L7.38841 15.3156C7.38786 15.324 7.38457 15.3321 7.37903 15.3385L6.24297 14.359ZM5.8908 15.2321L5.7508 18.2551L7.2492 18.3245L7.3892 15.3015L5.8908 15.2321ZM5.75396 18.3667C5.83563 19.1586 6.51588 19.7524 7.31152 19.7264L7.26248 18.2272C7.25928 18.2273 7.25771 18.2268 7.25669 18.2264C7.25526 18.2259 7.25337 18.2249 7.25144 18.2232C7.2495 18.2215 7.24825 18.2198 7.24754 18.2185C7.24703 18.2175 7.24637 18.216 7.24604 18.2128L5.75396 18.3667ZM7.45996 19.7065L10.46 18.9955L10.114 17.536L7.11404 18.247L7.45996 19.7065ZM10.4716 18.9927C10.7771 18.9151 11.05 18.7422 11.2506 18.499L10.0934 17.5445C10.0958 17.5417 10.0989 17.5397 10.1024 17.5388L10.4716 18.9927ZM11.2571 18.491L17.2971 10.959L16.1269 10.0206L10.0869 17.5526L11.2571 18.491ZM13.2971 7.95901L14.8851 5.97901L13.7149 5.04052L12.1269 7.02052L13.2971 7.95901ZM14.9135 5.94123C15.0521 5.74411 15.3214 5.6912 15.5243 5.82123L16.3337 4.5583C15.4544 3.99484 14.2873 4.2241 13.6865 5.0783L14.9135 5.94123ZM15.4492 5.7662L17.6862 7.6282L18.6458 6.47532L16.4088 4.61332L15.4492 5.7662ZM17.6352 7.58161C17.7111 7.6577 17.7535 7.761 17.7529 7.86852L19.2529 7.87676C19.2557 7.36905 19.0555 6.88127 18.6968 6.52192L17.6352 7.58161ZM17.7529 7.86852C17.7524 7.97604 17.7088 8.07886 17.632 8.15412L18.682 9.22541C19.0446 8.87002 19.2501 8.38447 19.2529 7.87676L17.7529 7.86852ZM17.5721 8.22025L16.1271 10.0203L17.2969 10.9593L18.7419 9.15928L17.5721 8.22025ZM11.9703 7.60091C12.3196 9.93221 14.4771 11.5503 16.813 11.2329L16.611 9.74659C15.0881 9.95352 13.6815 8.89855 13.4537 7.37862L11.9703 7.60091Z" />
|
|
15
|
+
</svg>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function HistoryIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
{...props}
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
width="24"
|
|
7
|
+
height="24"
|
|
8
|
+
viewBox="0 0 24 24"
|
|
9
|
+
fill="none"
|
|
10
|
+
stroke="currentColor"
|
|
11
|
+
strokeLinejoin="round"
|
|
12
|
+
className="lucide lucide-trash-icon lucide-trash"
|
|
13
|
+
>
|
|
14
|
+
<path
|
|
15
|
+
d="M5.52786 16.7023C6.6602 18.2608 8.3169 19.3584 10.1936 19.7934C12.0703 20.2284 14.0409 19.9716 15.7434 19.0701C17.446 18.1687 18.766 16.6832 19.4611 14.8865C20.1562 13.0898 20.1796 11.1027 19.527 9.29011C18.8745 7.47756 17.5898 5.96135 15.909 5.02005C14.2282 4.07875 12.2641 3.77558 10.3777 4.16623C8.49129 4.55689 6.80919 5.61514 5.64045 7.14656C4.47171 8.67797 3.89482 10.5797 4.01579 12.5023M4.01579 12.5023L2.51579 11.0023M4.01579 12.5023L5.51579 11.0023"
|
|
16
|
+
strokeWidth="2"
|
|
17
|
+
strokeLinecap="round"
|
|
18
|
+
strokeLinejoin="round"
|
|
19
|
+
/>
|
|
20
|
+
<path
|
|
21
|
+
d="M12 8V12L15 15"
|
|
22
|
+
strokeWidth="2"
|
|
23
|
+
strokeLinecap="round"
|
|
24
|
+
strokeLinejoin="round"
|
|
25
|
+
/>
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -28,7 +28,7 @@ const ModalContent = styled("dialog")`
|
|
|
28
28
|
&::backdrop {
|
|
29
29
|
background-color: rgba(0, 0, 0, 0.7);
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
`;
|
|
33
33
|
|
|
34
34
|
const ModalHeader = styled("div")`
|
|
@@ -65,11 +65,11 @@ const CloseButton = styled("button")`
|
|
|
65
65
|
justify-content: center;
|
|
66
66
|
min-width: 2rem;
|
|
67
67
|
min-height: 2rem;
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
&:hover {
|
|
70
70
|
background-color: var(--j-foreground);
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
&:focus-visible {
|
|
74
74
|
outline: 2px solid var(--j-border-focus);
|
|
75
75
|
outline-offset: 2px;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { JsonObject, JsonValue, RawCoMap, Role } from "cojson";
|
|
2
|
+
import type { MapOpPayload } from "cojson/dist/coValues/coMap.js";
|
|
3
|
+
|
|
4
|
+
export function restoreCoMapToTimestamp(
|
|
5
|
+
coValue: RawCoMap,
|
|
6
|
+
timestamp: number,
|
|
7
|
+
removeUnknownProperties: boolean,
|
|
8
|
+
): void {
|
|
9
|
+
const myRole = coValue.group.myRole();
|
|
10
|
+
|
|
11
|
+
if (
|
|
12
|
+
myRole === undefined ||
|
|
13
|
+
!(["admin", "manager", "writer", "writerOnly"] as Role[]).includes(myRole)
|
|
14
|
+
) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const newCoValue = coValue.atTime(timestamp).toJSON() as JsonObject | null;
|
|
19
|
+
const oldCoValue = coValue.toJSON() as JsonObject;
|
|
20
|
+
|
|
21
|
+
if (newCoValue === null) return;
|
|
22
|
+
|
|
23
|
+
let changes: MapOpPayload<string, JsonValue | undefined>[] = [];
|
|
24
|
+
|
|
25
|
+
if (removeUnknownProperties) {
|
|
26
|
+
for (const key in oldCoValue) {
|
|
27
|
+
if (!(key in newCoValue)) {
|
|
28
|
+
changes.push({
|
|
29
|
+
op: "del",
|
|
30
|
+
key,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const key in newCoValue) {
|
|
37
|
+
if (newCoValue[key] !== oldCoValue[key]) {
|
|
38
|
+
changes.push({
|
|
39
|
+
op: "set",
|
|
40
|
+
key,
|
|
41
|
+
value: newCoValue[key],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (changes.length > 0) {
|
|
47
|
+
coValue.core.makeTransaction(changes, "private");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AccountRole,
|
|
3
|
+
BinaryStreamStart,
|
|
4
|
+
CoID,
|
|
5
|
+
RawCoValue,
|
|
6
|
+
Role,
|
|
7
|
+
} from "cojson";
|
|
8
|
+
import type { ListOpPayload } from "cojson/dist/coValues/coList.js";
|
|
9
|
+
import type { MapOpPayload } from "cojson/dist/coValues/coMap.js";
|
|
10
|
+
import type {
|
|
11
|
+
BinaryStreamChunk,
|
|
12
|
+
BinaryStreamEnd,
|
|
13
|
+
} from "cojson/dist/coValues/coStream.js";
|
|
14
|
+
import { isCoId } from "../viewer/types";
|
|
15
|
+
|
|
16
|
+
export const isGroupExtension = (
|
|
17
|
+
change: any,
|
|
18
|
+
): change is Extract<
|
|
19
|
+
MapOpPayload<`child_${string}`, "extend">,
|
|
20
|
+
{ op: "set" }
|
|
21
|
+
> => {
|
|
22
|
+
return change?.op === "set" && change?.value === "extend";
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const isGroupExtendRevocation = (
|
|
26
|
+
change: any,
|
|
27
|
+
): change is Extract<
|
|
28
|
+
MapOpPayload<`child_${string}`, "revoked">,
|
|
29
|
+
{ op: "set" }
|
|
30
|
+
> => {
|
|
31
|
+
return change?.op === "set" && change?.value === "revoked";
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const isGroupPromotion = (
|
|
35
|
+
change: any,
|
|
36
|
+
): change is Extract<
|
|
37
|
+
MapOpPayload<`parent_co_${string}`, AccountRole>,
|
|
38
|
+
{ op: "set" }
|
|
39
|
+
> => {
|
|
40
|
+
return change?.op === "set" && change?.key.startsWith("parent_co_");
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const isUserPromotion = (
|
|
44
|
+
change: any,
|
|
45
|
+
): change is Extract<MapOpPayload<CoID<RawCoValue>, Role>, { op: "set" }> => {
|
|
46
|
+
return (
|
|
47
|
+
change?.op === "set" && (isCoId(change?.key) || change?.key === "everyone")
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const isKeyRevelation = (
|
|
52
|
+
change: any,
|
|
53
|
+
): change is Extract<
|
|
54
|
+
MapOpPayload<`${string}_for_${string}`, string>,
|
|
55
|
+
{ op: "set" }
|
|
56
|
+
> => {
|
|
57
|
+
return change?.op === "set" && change?.key.includes("_for_");
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const isPropertySet = (
|
|
61
|
+
change: any,
|
|
62
|
+
): change is Extract<MapOpPayload<string, any>, { op: "set" }> => {
|
|
63
|
+
return change?.op === "set" && "key" in change && "value" in change;
|
|
64
|
+
};
|
|
65
|
+
export const isPropertyDeletion = (
|
|
66
|
+
change: any,
|
|
67
|
+
): change is Extract<MapOpPayload<string, any>, { op: "del" }> => {
|
|
68
|
+
return change?.op === "del" && "key" in change;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const isItemAppend = (
|
|
72
|
+
change: any,
|
|
73
|
+
): change is Extract<ListOpPayload<any>, { op: "app" }> => {
|
|
74
|
+
return change?.op === "app" && "after" in change && "value" in change;
|
|
75
|
+
};
|
|
76
|
+
export const isItemPrepend = (
|
|
77
|
+
change: any,
|
|
78
|
+
): change is Extract<ListOpPayload<any>, { op: "pre" }> => {
|
|
79
|
+
return change?.op === "pre" && "before" in change && "value" in change;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const isItemDeletion = (
|
|
83
|
+
change: any,
|
|
84
|
+
): change is Extract<ListOpPayload<any>, { op: "del" }> => {
|
|
85
|
+
return change?.op === "del" && "insertion" in change;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const isStreamStart = (change: any): change is BinaryStreamStart => {
|
|
89
|
+
return change?.type === "start" && "mimeType" in change;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const isStreamChunk = (change: any): change is BinaryStreamChunk => {
|
|
93
|
+
return change?.type === "chunk" && "chunk" in change;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const isStreamEnd = (change: any): change is BinaryStreamEnd => {
|
|
97
|
+
return change?.type === "end";
|
|
98
|
+
};
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { JsonObject, LocalNode, RawCoMap } from "cojson";
|
|
2
|
+
import { PageInfo } from "./types";
|
|
3
|
+
import { GridView } from "./grid-view.js";
|
|
4
|
+
import { useState, useMemo } from "react";
|
|
5
|
+
import { Button, Icon, Input, Modal } from "../ui";
|
|
6
|
+
import { styled } from "goober";
|
|
7
|
+
import { restoreCoMapToTimestamp } from "../utils/history";
|
|
8
|
+
import { CoValueEditor } from "./co-value-editor.js";
|
|
9
|
+
import { isWriter } from "../utils/permissions";
|
|
10
|
+
|
|
11
|
+
export function CoMapView({
|
|
12
|
+
coValue,
|
|
13
|
+
data,
|
|
14
|
+
node,
|
|
15
|
+
onNavigate,
|
|
16
|
+
}: {
|
|
17
|
+
coValue: RawCoMap;
|
|
18
|
+
data: JsonObject;
|
|
19
|
+
node: LocalNode;
|
|
20
|
+
onNavigate: (pages: PageInfo[]) => void;
|
|
21
|
+
}) {
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<GridView
|
|
25
|
+
data={data}
|
|
26
|
+
onNavigate={onNavigate}
|
|
27
|
+
node={node}
|
|
28
|
+
coValue={coValue}
|
|
29
|
+
/>
|
|
30
|
+
<div>
|
|
31
|
+
<AddPropertyModal
|
|
32
|
+
disabled={!isWriter(coValue.group.myRole())}
|
|
33
|
+
coValue={coValue}
|
|
34
|
+
node={node}
|
|
35
|
+
/>{" "}
|
|
36
|
+
<RestoreSnapshotModal coValue={coValue} />
|
|
37
|
+
</div>
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function AddPropertyModal({
|
|
43
|
+
coValue,
|
|
44
|
+
node,
|
|
45
|
+
disabled,
|
|
46
|
+
}: {
|
|
47
|
+
coValue: RawCoMap;
|
|
48
|
+
node: LocalNode;
|
|
49
|
+
disabled: boolean;
|
|
50
|
+
}) {
|
|
51
|
+
const [isAddPropertyModalOpen, setIsAddPropertyModalOpen] = useState(false);
|
|
52
|
+
const [propertyName, setPropertyName] = useState("");
|
|
53
|
+
|
|
54
|
+
const openAddPropertyModal = () => {
|
|
55
|
+
setIsAddPropertyModalOpen(true);
|
|
56
|
+
setPropertyName("");
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleCancel = () => {
|
|
60
|
+
setIsAddPropertyModalOpen(false);
|
|
61
|
+
setPropertyName("");
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
<Button
|
|
67
|
+
title="Add Property"
|
|
68
|
+
variant="secondary"
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
onClick={openAddPropertyModal}
|
|
71
|
+
>
|
|
72
|
+
<Icon name="add" />
|
|
73
|
+
</Button>
|
|
74
|
+
|
|
75
|
+
<Modal
|
|
76
|
+
isOpen={isAddPropertyModalOpen}
|
|
77
|
+
onClose={handleCancel}
|
|
78
|
+
heading="Add Property"
|
|
79
|
+
showButtons={false}
|
|
80
|
+
>
|
|
81
|
+
<Input
|
|
82
|
+
label="Property Name"
|
|
83
|
+
value={propertyName}
|
|
84
|
+
onChange={(e) => setPropertyName(e.target.value)}
|
|
85
|
+
placeholder="Enter property name"
|
|
86
|
+
/>
|
|
87
|
+
{propertyName && (
|
|
88
|
+
<EditorContainer>
|
|
89
|
+
<CoValueEditor
|
|
90
|
+
node={node}
|
|
91
|
+
property={propertyName}
|
|
92
|
+
value={undefined}
|
|
93
|
+
coValue={coValue}
|
|
94
|
+
onCancel={handleCancel}
|
|
95
|
+
/>
|
|
96
|
+
</EditorContainer>
|
|
97
|
+
)}
|
|
98
|
+
</Modal>
|
|
99
|
+
</>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function RestoreSnapshotModal({ coValue }: { coValue: RawCoMap }) {
|
|
104
|
+
const [isRestoreModalOpen, setIsRestoreModalOpen] = useState(false);
|
|
105
|
+
const [selectedIndex, setSelectedIndex] = useState<number>(-1);
|
|
106
|
+
const [removeUnknownProperties, setRemoveUnknownProperties] = useState(false);
|
|
107
|
+
|
|
108
|
+
const timestamps = useMemo(
|
|
109
|
+
() => coValue.core.verifiedTransactions.map((tx) => tx.madeAt),
|
|
110
|
+
[coValue.core.verifiedTransactions.length],
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const coMapAtSelectedIndex = useMemo(() => {
|
|
114
|
+
if (selectedIndex === -1) return null;
|
|
115
|
+
return coValue.atTime(timestamps[selectedIndex]!).toJSON() as JsonObject;
|
|
116
|
+
}, [coValue, timestamps, selectedIndex]);
|
|
117
|
+
|
|
118
|
+
const openRestoreModal = () => {
|
|
119
|
+
setIsRestoreModalOpen(true);
|
|
120
|
+
setSelectedIndex(timestamps.length - 1);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const handleRestore = () => {
|
|
124
|
+
if (timestamps.length < 2) return;
|
|
125
|
+
if (timestamps.length === 0) return;
|
|
126
|
+
|
|
127
|
+
const selectedTimestamp = timestamps[selectedIndex];
|
|
128
|
+
if (selectedTimestamp === undefined) return;
|
|
129
|
+
|
|
130
|
+
restoreCoMapToTimestamp(
|
|
131
|
+
coValue,
|
|
132
|
+
selectedTimestamp,
|
|
133
|
+
removeUnknownProperties,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
setIsRestoreModalOpen(false);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const handleClose = () => {
|
|
140
|
+
setIsRestoreModalOpen(false);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const canRestore = isWriter(coValue.group.myRole());
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<>
|
|
147
|
+
<Button title="Timeline" variant="secondary" onClick={openRestoreModal}>
|
|
148
|
+
<Icon name="history" />
|
|
149
|
+
</Button>
|
|
150
|
+
|
|
151
|
+
<Modal
|
|
152
|
+
isOpen={isRestoreModalOpen}
|
|
153
|
+
onClose={handleClose}
|
|
154
|
+
heading="Timeline"
|
|
155
|
+
confirmText="Restore"
|
|
156
|
+
cancelText="Cancel"
|
|
157
|
+
onConfirm={handleRestore}
|
|
158
|
+
onCancel={handleClose}
|
|
159
|
+
showButtons={timestamps.length > 1 && canRestore}
|
|
160
|
+
>
|
|
161
|
+
{timestamps.length > 1 && (
|
|
162
|
+
<>
|
|
163
|
+
<RangeContainer>
|
|
164
|
+
<RangeLabel>Select Timestamp</RangeLabel>
|
|
165
|
+
<RangeInput
|
|
166
|
+
type="range"
|
|
167
|
+
min={0}
|
|
168
|
+
max={Math.max(0, timestamps.length - 1)}
|
|
169
|
+
value={selectedIndex}
|
|
170
|
+
onChange={(e) => setSelectedIndex(Number(e.target.value))}
|
|
171
|
+
disabled={timestamps.length === 0}
|
|
172
|
+
/>
|
|
173
|
+
<TimestampDisplay>
|
|
174
|
+
{timestamps[selectedIndex] !== undefined
|
|
175
|
+
? new Date(timestamps[selectedIndex]!).toISOString()
|
|
176
|
+
: "No timestamps available"}
|
|
177
|
+
</TimestampDisplay>
|
|
178
|
+
</RangeContainer>
|
|
179
|
+
|
|
180
|
+
{canRestore && (
|
|
181
|
+
<CheckboxContainer>
|
|
182
|
+
<CheckboxInput
|
|
183
|
+
type="checkbox"
|
|
184
|
+
id="remove-unknown-properties"
|
|
185
|
+
checked={removeUnknownProperties}
|
|
186
|
+
onChange={(e) => setRemoveUnknownProperties(e.target.checked)}
|
|
187
|
+
/>
|
|
188
|
+
<CheckboxLabel htmlFor="remove-unknown-properties">
|
|
189
|
+
Remove unknown properties (properties that don't exist in the
|
|
190
|
+
selected snapshot)
|
|
191
|
+
</CheckboxLabel>
|
|
192
|
+
</CheckboxContainer>
|
|
193
|
+
)}
|
|
194
|
+
</>
|
|
195
|
+
)}
|
|
196
|
+
|
|
197
|
+
{timestamps.length > 0 && timestamps[selectedIndex] !== undefined && (
|
|
198
|
+
<PreviewSection>
|
|
199
|
+
<PreviewLabel>State at that time:</PreviewLabel>
|
|
200
|
+
<PreviewPre>
|
|
201
|
+
{JSON.stringify(coMapAtSelectedIndex, null, 2)}
|
|
202
|
+
</PreviewPre>
|
|
203
|
+
</PreviewSection>
|
|
204
|
+
)}
|
|
205
|
+
|
|
206
|
+
{timestamps.length < 2 && (
|
|
207
|
+
<div style={{ color: "var(--j-text-color)" }}>
|
|
208
|
+
At least 2 timestamps are required to restore a snapshot.
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
</Modal>
|
|
212
|
+
</>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const PreviewSection = styled("div")`
|
|
217
|
+
margin-top: 1.5rem;
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
const PreviewLabel = styled("div")`
|
|
221
|
+
font-weight: 500;
|
|
222
|
+
margin-bottom: 0.5rem;
|
|
223
|
+
color: var(--j-text-color-strong);
|
|
224
|
+
`;
|
|
225
|
+
|
|
226
|
+
const PreviewPre = styled("pre")`
|
|
227
|
+
background-color: var(--j-foreground);
|
|
228
|
+
border: 1px solid var(--j-border-color);
|
|
229
|
+
border-radius: var(--j-radius-md);
|
|
230
|
+
padding: 1rem;
|
|
231
|
+
overflow-x: auto;
|
|
232
|
+
font-size: 0.875rem;
|
|
233
|
+
max-height: 400px;
|
|
234
|
+
overflow-y: auto;
|
|
235
|
+
color: var(--j-text-color);
|
|
236
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
237
|
+
`;
|
|
238
|
+
|
|
239
|
+
const RangeContainer = styled("div")`
|
|
240
|
+
display: flex;
|
|
241
|
+
flex-direction: column;
|
|
242
|
+
gap: 0.75rem;
|
|
243
|
+
`;
|
|
244
|
+
|
|
245
|
+
const RangeLabel = styled("label")`
|
|
246
|
+
font-weight: 500;
|
|
247
|
+
color: var(--j-text-color-strong);
|
|
248
|
+
font-size: 0.875rem;
|
|
249
|
+
`;
|
|
250
|
+
|
|
251
|
+
const RangeInput = styled("input")`
|
|
252
|
+
width: 100%;
|
|
253
|
+
height: 0.5rem;
|
|
254
|
+
border-radius: var(--j-radius-sm);
|
|
255
|
+
outline: none;
|
|
256
|
+
-webkit-appearance: none;
|
|
257
|
+
appearance: none;
|
|
258
|
+
background: var(--j-foreground);
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
|
|
261
|
+
&::-webkit-slider-thumb {
|
|
262
|
+
-webkit-appearance: none;
|
|
263
|
+
appearance: none;
|
|
264
|
+
width: 1.25rem;
|
|
265
|
+
height: 1.25rem;
|
|
266
|
+
border-radius: 50%;
|
|
267
|
+
background: var(--j-primary-color);
|
|
268
|
+
cursor: pointer;
|
|
269
|
+
border: 2px solid var(--j-background);
|
|
270
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
&::-moz-range-thumb {
|
|
274
|
+
width: 1.25rem;
|
|
275
|
+
height: 1.25rem;
|
|
276
|
+
border-radius: 50%;
|
|
277
|
+
background: var(--j-primary-color);
|
|
278
|
+
cursor: pointer;
|
|
279
|
+
border: 2px solid var(--j-background);
|
|
280
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
&:disabled {
|
|
284
|
+
opacity: 0.5;
|
|
285
|
+
cursor: not-allowed;
|
|
286
|
+
}
|
|
287
|
+
`;
|
|
288
|
+
|
|
289
|
+
const TimestampDisplay = styled("div")`
|
|
290
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
291
|
+
font-size: 0.875rem;
|
|
292
|
+
color: var(--j-text-color);
|
|
293
|
+
padding: 0.5rem;
|
|
294
|
+
background-color: var(--j-foreground);
|
|
295
|
+
border: 1px solid var(--j-border-color);
|
|
296
|
+
border-radius: var(--j-radius-md);
|
|
297
|
+
text-align: center;
|
|
298
|
+
`;
|
|
299
|
+
|
|
300
|
+
const CheckboxContainer = styled("div")`
|
|
301
|
+
display: flex;
|
|
302
|
+
align-items: flex-start;
|
|
303
|
+
gap: 0.5rem;
|
|
304
|
+
margin-top: 1rem;
|
|
305
|
+
`;
|
|
306
|
+
|
|
307
|
+
const CheckboxInput = styled("input")`
|
|
308
|
+
width: 1rem;
|
|
309
|
+
height: 1rem;
|
|
310
|
+
margin-top: 0.125rem;
|
|
311
|
+
cursor: pointer;
|
|
312
|
+
accent-color: var(--j-primary-color);
|
|
313
|
+
`;
|
|
314
|
+
|
|
315
|
+
const CheckboxLabel = styled("label")`
|
|
316
|
+
font-size: 0.875rem;
|
|
317
|
+
color: var(--j-text-color);
|
|
318
|
+
cursor: pointer;
|
|
319
|
+
line-height: 1.25rem;
|
|
320
|
+
`;
|
|
321
|
+
|
|
322
|
+
const EditorContainer = styled("div")`
|
|
323
|
+
margin-top: 1rem;
|
|
324
|
+
`;
|