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};