a tool for shared writing and social publishing

make things blocks easier to manuever in canvas

+49 -23
+11 -2
components/Blocks/Block.tsx
··· 68 68 // Block handles all block level events like 69 69 // mouse events, keyboard events and longPress, and setting AreYouSure state 70 70 // and shared styling like padding and flex for list layouting 71 - let { rep } = useReplicache(); 72 71 let mouseHandlers = useBlockMouseHandlers(props); 73 72 let handleDrop = useHandleDrop({ 74 73 parent: props.parent, ··· 76 75 nextPosition: props.nextPosition, 77 76 }); 78 77 let entity_set = useEntitySetContext(); 78 + 79 + let { isLongPress, longPressHandlers } = useLongPress(() => { 80 + if (isTextBlock[props.type]) return; 81 + if (isLongPress.current) { 82 + focusBlock( 83 + { type: props.type, value: props.entityID, parent: props.parent }, 84 + { type: "start" }, 85 + ); 86 + } 87 + }); 79 88 80 89 let selected = useUIState( 81 90 (s) => !!s.selectedBlocks.find((b) => b.value === props.entityID), ··· 107 116 108 117 return ( 109 118 <div 110 - {...(!props.preview ? { ...mouseHandlers } : {})} 119 + {...(!props.preview ? { ...mouseHandlers, ...longPressHandlers } : {})} 111 120 id={ 112 121 !props.preview ? elementId.block(props.entityID).container : undefined 113 122 }
-1
components/Blocks/ButtonBlock.tsx
··· 193 193 <Separator /> 194 194 <Input 195 195 type="text" 196 - autoFocus 197 196 className="w-full grow border-none outline-hidden bg-transparent" 198 197 placeholder="button text" 199 198 value={textValue}
+3 -1
components/Blocks/EmbedBlock.tsx
··· 111 111 <div 112 112 data-draggable 113 113 className={`resizeHandle 114 + 115 + 114 116 cursor-ns-resize shrink-0 z-10 w-6 h-[5px] 115 - absolute bottom-2 right-1/2 translate-x-1/2 translate-y-[2px] 117 + absolute bottom-[3px] right-1/2 translate-x-1/2 116 118 rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,inset_0_0_0_1px_white] 117 119 ${isCanvasBlock ? "hidden group-hover/canvas-block:block" : ""}`} 118 120 {...heightHandle.handlers}
+35 -19
components/Canvas.tsx
··· 26 26 PubLeafletPublicationRecord, 27 27 } from "lexicons/api"; 28 28 import { useHandleCanvasDrop } from "./Blocks/useHandleCanvasDrop"; 29 + import { useBlockMouseHandlers } from "./Blocks/useBlockMouseHandlers"; 29 30 30 31 export function Canvas(props: { 31 32 entityID: string; ··· 292 293 ); 293 294 let { dragDelta, handlers: dragHandlers } = useDrag({ 294 295 onDragEnd, 295 - delay: isMobile, 296 296 }); 297 297 298 298 let widthOnDragEnd = useCallback( ··· 389 389 }; 390 390 }, [props, type?.data.value]); 391 391 useBlockKeyboardHandlers(blockProps, areYouSure, setAreYouSure); 392 + let mouseHandlers = useBlockMouseHandlers(blockProps); 393 + 392 394 let isList = useEntity(props.entityID, "block/is-list"); 393 395 let isFocused = useUIState( 394 396 (s) => s.focusedEntity?.entityID === props.entityID, ··· 397 399 return ( 398 400 <div 399 401 ref={ref} 400 - {...(isMobile && permissions.write ? { ...dragHandlers } : {})} 402 + {...(!props.preview ? { ...longPressHandlers } : {})} 403 + // {...(isMobile && permissions.write ? { ...dragHandlers } : {})} 401 404 id={props.preview ? undefined : elementId.block(props.entityID).container} 402 - className={`absolute group/canvas-block will-change-transform rounded-lg flex items-stretch origin-center p-3 `} 405 + className={`canvasBlockWrapper absolute group/canvas-block will-change-transform rounded-lg flex items-stretch origin-center p-3 `} 403 406 style={{ 404 407 top: 0, 405 408 left: 0, ··· 409 412 }} 410 413 > 411 414 {/* the gripper show on hover, but longpress logic needs to be added for mobile*/} 412 - {!props.preview && permissions.write && <Gripper {...dragHandlers} />} 415 + {!props.preview && permissions.write && ( 416 + <Gripper isFocused={isFocused} {...dragHandlers} /> 417 + )} 418 + 419 + {/*mouseHandlers are being added here so they don't interfere with the dragHandlers in the div above*/} 413 420 <div 414 - className={`contents ${dragDelta || widthHandle.dragDelta || rotateHandle.dragDelta ? "pointer-events-none" : ""} `} 421 + className={` w-full ${dragDelta || widthHandle.dragDelta || rotateHandle.dragDelta ? "pointer-events-none" : ""} `} 422 + {...(!props.preview ? { ...mouseHandlers } : {})} 415 423 > 416 424 <BaseBlock 417 425 {...blockProps} ··· 429 437 <div 430 438 className={`resizeHandle 431 439 cursor-e-resize shrink-0 z-10 432 - hidden group-hover/canvas-block:block 433 - w-[5px] h-6 -ml-[3px] 434 - absolute top-1/2 right-3 -translate-y-1/2 translate-x-[2px] 435 - rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,inset_0_0_0_1px_white]`} 440 + group-hover/canvas-block:block 441 + sm:w-[5px] w-3 sm:h-6 h-8 442 + absolute top-1/2 sm:right-2 right-1 -translate-y-1/2 443 + rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,inset_0_0_0_1px_white] 444 + ${isFocused ? "block" : "hidden"} 445 + 446 + `} 436 447 {...widthHandle.handlers} 437 448 /> 438 449 )} ··· 441 452 <div 442 453 className={`rotateHandle 443 454 cursor-grab shrink-0 z-10 444 - hidden group-hover/canvas-block:block 445 - w-[8px] h-[8px] 446 - absolute bottom-0 -right-0 455 + group-hover/canvas-block:block 456 + sm:w-[8px] sm:h-[8px] w-4 h-4 457 + absolute sm:bottom-0 sm:right-0 -bottom-1 -right-1 447 458 -translate-y-1/2 -translate-x-1/2 448 - rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,inset_0_0_0_1px_white]`} 459 + rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,inset_0_0_0_1px_white] 460 + ${isFocused ? "block" : "hidden"} 461 + `} 449 462 {...rotateHandle.handlers} 450 463 /> 451 464 )} ··· 566 579 } 567 580 }; 568 581 569 - const Gripper = (props: { onMouseDown: (e: React.MouseEvent) => void }) => { 582 + const Gripper = (props: { 583 + onMouseDown: (e: React.MouseEvent) => void; 584 + isFocused: boolean; 585 + }) => { 570 586 return ( 571 587 <div 572 588 onMouseDown={props.onMouseDown} 573 589 onPointerDown={props.onMouseDown} 574 - className="w-[9px] shrink-0 py-1 mr-1 bg-bg-card cursor-grab touch-none" 590 + className="gripper w-[9px] shrink-0 py-1 mr-1 cursor-grab touch-none" 575 591 > 576 - <Media mobile={false} className="h-full grid grid-cols-1 grid-rows-1 "> 592 + <div className="h-full grid grid-cols-1 grid-rows-1 "> 577 593 {/* the gripper is two svg's stacked on top of each other. 578 594 One for the actual gripper, the other is an outline to endure the gripper stays visible on image backgrounds */} 579 595 <div 580 - className="h-full col-start-1 col-end-2 row-start-1 row-end-2 bg-bg-page hidden group-hover/canvas-block:block" 596 + className={`h-full col-start-1 col-end-2 row-start-1 row-end-2 bg-bg-page group-hover/canvas-block:block ${props.isFocused ? "block" : "hidden"}`} 581 597 style={{ maskImage: "var(--gripperSVG2)", maskRepeat: "repeat" }} 582 598 /> 583 599 <div 584 - className="h-full col-start-1 col-end-2 row-start-1 row-end-2 bg-tertiary hidden group-hover/canvas-block:block" 600 + className={`h-full col-start-1 col-end-2 row-start-1 row-end-2 bg-tertiary group-hover/canvas-block:block ${props.isFocused ? "block" : "hidden"}`} 585 601 style={{ maskImage: "var(--gripperSVG)", maskRepeat: "repeat" }} 586 602 /> 587 - </Media> 603 + </div> 588 604 </div> 589 605 ); 590 606 };