a tool for shared writing and social publishing

add height attribute and drag handle to embed block

+49 -6
+41 -6
components/Blocks/EmbedBlock.tsx
··· 1 1 import { useEntitySetContext } from "components/EntitySetProvider"; 2 2 import { generateKeyBetween } from "fractional-indexing"; 3 - import { useEffect, useState } from "react"; 3 + import { useCallback, useEffect, useState } from "react"; 4 4 import { useEntity, useReplicache } from "src/replicache"; 5 5 import { useUIState } from "src/useUIState"; 6 6 import { BlockProps } from "./Block"; ··· 13 13 import { elementId } from "src/utils/elementId"; 14 14 import { deleteBlock } from "./DeleteBlock"; 15 15 import { focusBlock } from "src/utils/focusBlock"; 16 + import { useDrag } from "src/hooks/useDrag"; 16 17 17 18 export const EmbedBlock = (props: BlockProps & { preview?: boolean }) => { 18 19 let { permissions } = useEntitySetContext(); 20 + let { rep } = useReplicache(); 19 21 let url = useEntity(props.entityID, "embed/url"); 20 22 let isCanvasBlock = props.pageType === "canvas"; 21 23 ··· 23 25 s.selectedBlocks.find((b) => b.value === props.entityID), 24 26 ); 25 27 28 + let height = useEntity(props.entityID, "embed/height")?.data.value || 360; 29 + 30 + let heightOnDragEnd = useCallback( 31 + (dragPosition: { x: number; y: number }) => { 32 + rep?.mutate.assertFact({ 33 + entity: props.entityID, 34 + attribute: "embed/height", 35 + data: { 36 + type: "number", 37 + value: height + dragPosition.y, 38 + }, 39 + }); 40 + }, 41 + [props, rep, height], 42 + ); 43 + 44 + let heightHandle = useDrag({ onDragEnd: heightOnDragEnd }); 45 + 26 46 useEffect(() => { 27 47 if (props.preview) return; 28 48 let input = document.getElementById(elementId.block(props.entityID).input); ··· 55 75 } 56 76 57 77 return ( 58 - <div className="w-full aspect-[4/3]"> 78 + <div 79 + className={`w-full ${heightHandle.dragDelta ? "pointer-events-none" : ""}`} 80 + > 59 81 {/* 60 82 the iframe! 61 - very simple, just a fixed height (could add as an option) 62 83 can also add 'allow' and 'referrerpolicy' attributes later if needed 63 84 */} 64 85 <iframe ··· 67 88 ${isSelected ? "block-border-selected " : "block-border"} 68 89 `} 69 90 width="100%" 70 - height="100%" 91 + height={height + (heightHandle.dragDelta?.y || 0)} 71 92 src={url?.data.value} 72 93 allow="fullscreen" 73 94 loading="lazy" 74 95 ></iframe> 75 - <div className="w-full overflow-x-hidden truncate text-xs italic text-accent-contrast"> 96 + {/* <div className="w-full overflow-x-hidden truncate text-xs italic text-accent-contrast"> 76 97 <a 77 98 href={url?.data.value} 78 99 target="_blank" ··· 80 101 > 81 102 {url?.data.value} 82 103 </a> 83 - </div> 104 + </div> */} 105 + 106 + {!props.preview && permissions.write && ( 107 + <> 108 + <div 109 + data-draggable 110 + className={`resizeHandle 111 + cursor-ns-resize shrink-0 z-10 w-6 h-[5px] 112 + absolute bottom-2 right-1/2 translate-x-1/2 translate-y-[2px] 113 + rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,_inset_0_0_0_1px_white] 114 + ${isCanvasBlock ? "hidden group-hover/canvas-block:block" : ""}`} 115 + {...heightHandle.handlers} 116 + /> 117 + </> 118 + )} 84 119 </div> 85 120 ); 86 121 };
+1
components/Blocks/useBlockMouseHandlers.ts
··· 16 16 let { rep } = useReplicache(); 17 17 let onMouseDown = useCallback( 18 18 (e: MouseEvent) => { 19 + if ((e.target as Element).getAttribute("data-draggable")) return; 19 20 if (isMobile) return; 20 21 if (!entity_set.permissions.write) return; 21 22 useSelectingMouse.setState({ start: props.value });
+1
components/SelectionManager.tsx
··· 491 491 if (isMobile) return; 492 492 if (!entity_set.permissions.write) return; 493 493 let mouseDownListener = (e: MouseEvent) => { 494 + if ((e.target as Element).getAttribute("data-draggable")) return; 494 495 setMouseDown(true); 495 496 let contentEditableParent = getContentEditableParent(e.target as Node); 496 497 if (contentEditableParent) {
+2
src/hooks/useDrag.ts
··· 76 76 window.addEventListener( 77 77 "touchmove", 78 78 (e) => { 79 + e.preventDefault(); 79 80 if (args.delay && touchStart.current) { 80 81 const deltaX = e.touches[0].clientX - touchStart.current.x; 81 82 const deltaY = e.touches[0].clientY - touchStart.current.y; ··· 102 103 window.addEventListener( 103 104 "pointermove", 104 105 (e: PointerEvent) => { 106 + e.preventDefault(); 105 107 currentDragDelta.current.x = e.clientX - dragStart.x; 106 108 currentDragDelta.current.y = e.clientY - dragStart.y; 107 109 setDragDelta({ ...currentDragDelta.current });
+4
src/replicache/attributes.ts
··· 117 117 type: "string", 118 118 cardinality: "one", 119 119 }, 120 + "embed/height": { 121 + type: "number", 122 + cardinality: "one", 123 + }, 120 124 } as const; 121 125 122 126 export const ThemeAttributes = {