a tool for shared writing and social publishing
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

added PollOptions as facts

celine ff3f4c5a 20e72693

+104 -30
+56 -30
components/Blocks/PollBlock.tsx
··· 7 7 import { Separator } from "components/Layout"; 8 8 import { useEntitySetContext } from "components/EntitySetProvider"; 9 9 import { theme } from "tailwind.config"; 10 + import { useEntity, useReplicache } from "src/replicache"; 11 + import { v7 } from "uuid"; 10 12 11 13 export const PollBlock = (props: BlockProps) => { 14 + let { rep } = useReplicache(); 12 15 let isSelected = useUIState((s) => 13 16 s.selectedBlocks.find((b) => b.value === props.entityID), 14 17 ); ··· 18 21 !permissions.write ? "voting" : "editing", 19 22 ); 20 23 24 + let dataPollOptions = useEntity(props.entityID, "poll/options"); 25 + 21 26 let [pollOptions, setPollOptions] = useState< 22 27 { value: string; votes: number }[] 23 28 >([ 24 29 { value: "hello", votes: 2 }, 25 30 { value: "hi", votes: 4 }, 26 31 ]); 32 + let [localPollOptionNames, setLocalPollOptionNames] = useState<{ 33 + [k: string]: string; 34 + }>({}); 27 35 28 36 let totalVotes = pollOptions.reduce((sum, option) => sum + option.votes, 0); 29 37 ··· 60 68 </div> 61 69 )} 62 70 63 - {pollOptions.map((option, index) => ( 71 + {dataPollOptions.map((option, index) => ( 64 72 <PollOption 65 - key={index} 73 + localNameState={localPollOptionNames[option.data.value]} 74 + setLocalNameState={setLocalPollOptionNames} 75 + entityID={option.data.value} 76 + key={option.data.value} 66 77 state={pollState} 67 78 setState={setPollState} 68 - optionName={option.value} 69 - setOptionName={(newValue) => { 70 - setPollOptions((oldOptions) => { 71 - let newOptions = [...oldOptions]; 72 - newOptions[index] = { 73 - value: newValue, 74 - votes: oldOptions[index].votes, 75 - }; 76 - return newOptions; 77 - }); 78 - }} 79 - votes={option.votes} 79 + votes={0} 80 80 setVotes={(newVotes) => { 81 81 setPollOptions((oldOptions) => { 82 82 let newOptions = [...oldOptions]; ··· 100 100 ))} 101 101 {!permissions.write ? null : pollState === "editing" ? ( 102 102 <> 103 - <AddPollOptionButton 104 - addPollOption={() => { 105 - setPollOptions([...pollOptions, { value: "", votes: 0 }]); 106 - }} 107 - /> 103 + <AddPollOptionButton entityID={props.entityID} /> 108 104 <hr className="border-border" /> 109 105 <ButtonPrimary 110 106 className="place-self-end" 111 107 onMouseDown={() => { 112 108 setPollState("voting"); 109 + rep?.mutate.assertFact( 110 + Object.entries(localPollOptionNames).map(([entity, name]) => ({ 111 + entity, 112 + attribute: "poll-option/name", 113 + data: { type: "string", value: name }, 114 + })), 115 + ); 113 116 // TODO: Currently, the options are updated onChange in thier inputs in PollOption. 114 117 // However, they should instead be updated when this save button is clicked! 115 118 }} ··· 129 132 }; 130 133 131 134 const PollOption = (props: { 135 + entityID: string; 136 + localNameState: string | undefined; 137 + setLocalNameState: ( 138 + s: (s: { [k: string]: string }) => { [k: string]: string }, 139 + ) => void; 132 140 state: "editing" | "voting" | "results"; 133 141 setState: (state: "editing" | "voting" | "results") => void; 134 - optionName: string; 135 - setOptionName: (optionName: string) => void; 136 142 votes: number; 137 143 setVotes: (votes: number) => void; 138 144 totalVotes: number; 139 145 winner: boolean; 140 146 removeOption: () => void; 141 147 }) => { 142 - let [inputValue, setInputValue] = useState(props.optionName); 148 + let { rep } = useReplicache(); 149 + let optionName = useEntity(props.entityID, "poll-option/name")?.data.value; 150 + useEffect(() => { 151 + props.setLocalNameState((s) => ({ 152 + ...s, 153 + [props.entityID]: optionName || "", 154 + })); 155 + }, [optionName, props.setLocalNameState, props.entityID]); 143 156 return props.state === "editing" ? ( 144 157 <div className="flex gap-2 items-center"> 145 158 <Input ··· 147 160 className="pollOptionInput w-full input-with-border" 148 161 placeholder="Option here..." 149 162 disabled={props.votes > 0} 150 - value={inputValue} 163 + value={ 164 + props.localNameState === undefined ? optionName : props.localNameState 165 + } 151 166 onChange={(e) => { 152 - setInputValue(e.target.value); 153 - props.setOptionName(e.target.value); 167 + props.setLocalNameState((s) => ({ 168 + ...s, 169 + [props.entityID]: e.target.value, 170 + })); 154 171 }} 155 172 onKeyDown={(e) => { 156 173 if (e.key === "Backspace" && !e.currentTarget.value) { ··· 170 187 <CloseTiny /> 171 188 </button> 172 189 </div> 173 - ) : props.optionName === "" ? null : props.state === "voting" ? ( 190 + ) : optionName === "" ? null : props.state === "voting" ? ( 174 191 <div className="flex gap-2 items-center"> 175 192 <ButtonSecondary 176 193 className={`pollOption grow max-w-full`} ··· 179 196 props.setVotes(props.votes + 1); 180 197 }} 181 198 > 182 - {props.optionName} 199 + {optionName} 183 200 </ButtonSecondary> 184 201 </div> 185 202 ) : ( ··· 193 210 }} 194 211 className={`pollResultContent text-accent-contrast relative flex gap-2 justify-between z-10`} 195 212 > 196 - <div className="grow max-w-full truncate">{props.optionName}</div> 213 + <div className="grow max-w-full truncate">{optionName}</div> 197 214 <div>{props.votes}</div> 198 215 </div> 199 216 <div ··· 216 233 ); 217 234 }; 218 235 219 - const AddPollOptionButton = (props: { addPollOption: () => void }) => { 236 + const AddPollOptionButton = (props: { entityID: string }) => { 237 + let { rep } = useReplicache(); 238 + let permission_set = useEntitySetContext(); 239 + let options = useEntity(props.entityID, "poll/options"); 220 240 return ( 221 241 <button 222 242 className="pollAddOption w-fit flex gap-2 items-center justify-start text-sm text-accent-contrast" 223 243 onClick={() => { 224 - props.addPollOption(); 244 + rep?.mutate.addPollOption({ 245 + pollEntity: props.entityID, 246 + pollOptionEntity: v7(), 247 + pollOptionName: "", 248 + permission_set: permission_set.set, 249 + factID: v7(), 250 + }); 225 251 }} 226 252 > 227 253 Add an Option
+12
src/replicache/attributes.ts
··· 141 141 }, 142 142 } as const; 143 143 144 + const PollBlockAttributes = { 145 + "poll/options": { 146 + type: "ordered-reference", 147 + cardinality: "many", 148 + }, 149 + "poll-option/name": { 150 + type: "string", 151 + cardinality: "one", 152 + }, 153 + } as const; 154 + 144 155 export const ThemeAttributes = { 145 156 "theme/page-leaflet-watermark": { 146 157 type: "boolean", ··· 210 221 ...EmbedBlockAttributes, 211 222 ...ButtonBlockAttributes, 212 223 ...ImageBlockAttributes, 224 + ...PollBlockAttributes, 213 225 }; 214 226 type Attribute = typeof Attributes; 215 227 export type Data<A extends keyof typeof Attributes> = {
+36
src/replicache/mutations.ts
··· 556 556 } 557 557 }; 558 558 559 + const addPollOption: Mutation<{ 560 + pollEntity: string; 561 + pollOptionEntity: string; 562 + pollOptionName: string; 563 + permission_set: string; 564 + factID: string; 565 + }> = async (args, ctx) => { 566 + await ctx.createEntity({ 567 + entityID: args.pollOptionEntity, 568 + permission_set: args.permission_set, 569 + }); 570 + 571 + await ctx.assertFact({ 572 + entity: args.pollOptionEntity, 573 + attribute: "poll-option/name", 574 + data: { type: "string", value: args.pollOptionName }, 575 + }); 576 + 577 + let children = await ctx.scanIndex.eav(args.pollEntity, "poll/options"); 578 + let lastChild = children.toSorted((a, b) => 579 + a.data.position > b.data.position ? 1 : -1, 580 + )[children.length - 1]; 581 + 582 + await ctx.assertFact({ 583 + entity: args.pollEntity, 584 + id: args.factID, 585 + attribute: "poll/options", 586 + data: { 587 + type: "ordered-reference", 588 + value: args.pollOptionEntity, 589 + position: generateKeyBetween(lastChild?.data.position || null, null), 590 + }, 591 + }); 592 + }; 593 + 559 594 export const mutations = { 560 595 retractAttribute, 561 596 addBlock, ··· 575 610 toggleTodoState, 576 611 createDraft, 577 612 createEntity, 613 + addPollOption, 578 614 };