a tool for shared writing and social publishing
1import { Block } from "components/Blocks/Block";
2import { useMemo } from "react";
3import { ReadTransaction } from "replicache";
4import { useSubscribe } from "src/replicache/useSubscribe";
5import { Fact, useReplicache } from "src/replicache";
6import { scanIndex, scanIndexLocal } from "src/replicache/utils";
7
8export const useBlocks = (entityID: string | null) => {
9 let rep = useReplicache();
10 let initialValue = useMemo(
11 () =>
12 entityID === null
13 ? []
14 : getBlocksWithTypeLocal(rep.initialFacts, entityID),
15 [rep.initialFacts, entityID],
16 );
17 let repData = useSubscribe(
18 rep?.rep,
19 async (tx) => (entityID === null ? [] : getBlocksWithType(tx, entityID)),
20 { dependencies: [entityID] },
21 );
22 let data = repData || initialValue;
23 return data.flatMap((f) => (!f ? [] : [f]));
24};
25
26export const useCanvasBlocksWithType = (entityID: string | null) => {
27 let rep = useReplicache();
28 let initialValue = useMemo(() => {
29 if (!entityID) return [];
30 let scan = scanIndexLocal(rep.initialFacts);
31 let blocks = scan.eav(entityID, "canvas/block");
32 return blocks
33 .map((b) => {
34 let type = scan.eav(b.data.value, "block/type");
35 if (!type[0]) return null;
36 return {
37 ...b.data,
38 type: type[0]?.data.value || "text",
39 };
40 })
41 .filter((f) => f !== null);
42 }, [rep.initialFacts, entityID]);
43 let repData = useSubscribe(
44 rep?.rep,
45 async (tx) => {
46 if (!entityID) return [];
47 let scan = scanIndex(tx);
48 let blocks = await scan.eav(entityID, "canvas/block");
49 return Promise.all(
50 blocks.map(async (b) => {
51 let type = await scan.eav(b.data.value, "block/type");
52 return {
53 ...b.data,
54 type: type[0].data.value,
55 };
56 }),
57 );
58 },
59 { dependencies: [entityID] },
60 );
61 let data = repData || initialValue;
62 return data
63 .flatMap((f) => (!f ? [] : [f]))
64 .sort((a, b) => {
65 if (a.position.y === b.position.y) {
66 return a.position.x - b.position.x;
67 }
68 return a.position.y - b.position.y;
69 });
70};
71
72export const getBlocksWithType = async (
73 tx: ReadTransaction,
74 entityID: string,
75) => {
76 let initialized = await tx.get("initialized");
77 if (!initialized) return null;
78 let scan = scanIndex(tx);
79 let blocks = await scan.eav(entityID, "card/block");
80
81 return (
82 await Promise.all(
83 blocks
84 .sort((a, b) => {
85 if (a.data.position === b.data.position) return a.id > b.id ? 1 : -1;
86 return a.data.position > b.data.position ? 1 : -1;
87 })
88 .map(async (b) => {
89 let type = (await scan.eav(b.data.value, "block/type"))[0];
90 let isList = await scan.eav(b.data.value, "block/is-list");
91 if (!type) return null;
92 if (isList[0]?.data.value) {
93 const getChildren = async (
94 root: Fact<"card/block">,
95 parent: string,
96 depth: number,
97 path: { depth: number; entity: string }[],
98 ): Promise<Block[]> => {
99 let children = (
100 await scan.eav(root.data.value, "card/block")
101 ).sort((a, b) => (a.data.position > b.data.position ? 1 : -1));
102 let type = (await scan.eav(root.data.value, "block/type"))[0];
103 let checklist = await scan.eav(
104 root.data.value,
105 "block/check-list",
106 );
107 if (!type) return [];
108 let newPath = [...path, { entity: root.data.value, depth }];
109 let childBlocks = await Promise.all(
110 children.map((c) =>
111 getChildren(c, root.data.value, depth + 1, newPath),
112 ),
113 );
114 return [
115 {
116 ...root.data,
117 factID: root.id,
118 type: type.data.value,
119 parent: b.entity,
120 listData: {
121 depth: depth,
122 parent,
123 path: newPath,
124 checklist: !!checklist[0],
125 },
126 },
127 ...childBlocks.flat(),
128 ];
129 };
130 return getChildren(b, b.entity, 1, []);
131 }
132 return [
133 {
134 ...b.data,
135 factID: b.id,
136 type: type.data.value,
137 parent: b.entity,
138 },
139 ] as Block[];
140 }),
141 )
142 )
143 .flat()
144 .filter((f) => f !== null);
145};
146
147export const getBlocksWithTypeLocal = (
148 initialFacts: Fact<any>[],
149 entityID: string,
150) => {
151 let scan = scanIndexLocal(initialFacts);
152 let blocks = scan.eav(entityID, "card/block");
153 return blocks
154 .sort((a, b) => {
155 if (a.data.position === b.data.position) return a.id > b.id ? 1 : -1;
156 return a.data.position > b.data.position ? 1 : -1;
157 })
158 .map((b) => {
159 let type = scan.eav(b.data.value, "block/type")[0];
160 let isList = scan.eav(b.data.value, "block/is-list");
161 if (!type) return null;
162 if (isList[0]?.data.value) {
163 const getChildren = (
164 root: Fact<"card/block">,
165 parent: string,
166 depth: number,
167 path: { depth: number; entity: string }[],
168 ): Block[] => {
169 let children = scan
170 .eav(root.data.value, "card/block")
171 .sort((a, b) => (a.data.position > b.data.position ? 1 : -1));
172 let type = scan.eav(root.data.value, "block/type")[0];
173 if (!type) return [];
174 let newPath = [...path, { entity: root.data.value, depth }];
175 let childBlocks = children.map((c) =>
176 getChildren(c, root.data.value, depth + 1, newPath),
177 );
178 return [
179 {
180 ...root.data,
181 factID: root.id,
182 type: type.data.value,
183 parent: b.entity,
184 listData: { depth: depth, parent, path: newPath },
185 },
186 ...childBlocks.flat(),
187 ];
188 };
189 return getChildren(b, b.entity, 1, []);
190 }
191 return [
192 {
193 ...b.data,
194 factID: b.id,
195 type: type.data.value,
196 parent: b.entity,
197 },
198 ] as Block[];
199 })
200 .flat()
201 .filter((f) => f !== null);
202};