diff --git a/apps/roam/package.json b/apps/roam/package.json index e955ec506..987662b4a 100644 --- a/apps/roam/package.json +++ b/apps/roam/package.json @@ -53,6 +53,7 @@ "@use-gesture/react": "^10.2.27", "@vercel/blob": "^1.1.1", "classnames": "^2.3.2", + "@hello-pangea/dnd": "^18.0.1", "contrast-color": "^1.0.1", "core-js": "^3.45.0", "cytoscape": "^3.21.0", diff --git a/apps/roam/src/components/LeftSidebarView.tsx b/apps/roam/src/components/LeftSidebarView.tsx index 8e2628551..57cc0747d 100644 --- a/apps/roam/src/components/LeftSidebarView.tsx +++ b/apps/roam/src/components/LeftSidebarView.tsx @@ -7,6 +7,17 @@ import React, { useState, } from "react"; import ReactDOM from "react-dom"; +import { + DragDropContext, + Droppable, + Draggable, + DropResult, + DraggableProvided, + DraggableStateSnapshot, + DroppableProvided, + DragStart, + DraggableRubric, +} from "@hello-pangea/dnd"; import { Collapse, Icon, @@ -147,8 +158,10 @@ const SectionChildren = ({ const PersonalSectionItem = ({ section, + activeDragSourceId, }: { section: LeftSidebarPersonalSectionConfig; + activeDragSourceId: string | null; }) => { const titleRef = parseReference(section.text); const blockText = useMemo( @@ -160,7 +173,33 @@ const PersonalSectionItem = ({ const [isOpen, setIsOpen] = useState( !!section.settings?.folded.value || false, ); - const alias = section.settings?.alias?.value; + + const renderChild = ( + dragProvided: DraggableProvided, + child: { text: string; uid: string }, + ) => { + const ref = parseReference(child.text); + const label = truncate(ref.display, truncateAt); + const onClick = (e: React.MouseEvent) => { + return void openTarget(e, child.text); + }; + return ( +
+
+ {label} +
+
+ ); + }; const handleChevronClick = () => { if (!section.settings) return; @@ -187,7 +226,7 @@ const PersonalSectionItem = ({ } }} > - {(alias || blockText || titleRef.display).toUpperCase()} + {(blockText || titleRef.display).toUpperCase()} {(section.children?.length || 0) > 0 && ( - + { + const child = (section.children || [])[rubric.source.index]; + return renderChild(provided, child); + }} + > + {(provided: DroppableProvided) => ( +
+ {(section.children || []).map((child, index) => ( + + {(dragProvided: DraggableProvided) => { + return renderChild(dragProvided, child); + }} + + ))} + {provided.placeholder} +
+ )} +
); @@ -211,17 +279,122 @@ const PersonalSectionItem = ({ const PersonalSections = ({ config, + setConfig, }: { - config: LeftSidebarConfig["personal"]; + config: LeftSidebarConfig; + setConfig: Dispatch>; }) => { - const sections = config.sections || []; + const sections = config.personal.sections || []; + const [activeDragSourceId, setActiveDragSourceId] = useState( + null, + ); + if (!sections.length) return null; + + const handleDragStart = (start: DragStart) => { + if (start.type === "ITEMS") { + setActiveDragSourceId(start.source.droppableId); + } + }; + + const handleDragEnd = (result: DropResult) => { + setActiveDragSourceId(null); + const { source, destination, type } = result; + + if (!destination) return; + + if (type === "SECTIONS") { + if (destination.index === source.index) return; + + const newPersonalSections = Array.from(config.personal.sections); + const [removed] = newPersonalSections.splice(source.index, 1); + newPersonalSections.splice(destination.index, 0, removed); + + setConfig({ + ...config, + personal: { ...config.personal, sections: newPersonalSections }, + }); + const finalIndex = + destination.index > source.index + ? destination.index + 1 + : destination.index; + void window.roamAlphaAPI.moveBlock({ + location: { "parent-uid": config.personal.uid, order: finalIndex }, + block: { uid: removed.uid }, + }); + return; + } + + if (type === "ITEMS") { + if (source.droppableId !== destination.droppableId) { + return; + } + + if (destination.index === source.index) { + return; + } + + const newConfig = JSON.parse(JSON.stringify(config)) as LeftSidebarConfig; + const { personal } = newConfig; + + const listToReorder = personal.sections.find( + (s) => s.uid === source.droppableId, + ); + const parentUid = listToReorder?.childrenUid; + const listToReorderChildren = listToReorder?.children; + + if (!listToReorderChildren) return; + + const [removedItem] = listToReorderChildren.splice(source.index, 1); + listToReorderChildren.splice(destination.index, 0, removedItem); + + setConfig(newConfig); + const finalIndex = + destination.index > source.index + ? destination.index + 1 + : destination.index; + void window.roamAlphaAPI.moveBlock({ + location: { "parent-uid": parentUid || "", order: finalIndex }, + block: { uid: removedItem.uid }, + }); + } + }; return ( -
- {sections.map((s) => ( - - ))} -
+ + + {(provided: DroppableProvided) => ( +
+ {sections.map((section, index) => ( + + {(dragProvided: DraggableProvided) => ( +
+ +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
+
); }; @@ -312,7 +485,6 @@ const FavouritesPopover = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => { useEffect(() => { if (!isMenuOpen) return; - console.log("handleGlobalPointerDownCapture"); const opts = { capture: true } as AddEventListenerOptions; window.addEventListener( "mousedown", @@ -336,7 +508,7 @@ const FavouritesPopover = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => { opts, ); }; - }, [handleGlobalPointerDownCapture]); + }, [handleGlobalPointerDownCapture, isMenuOpen]); const renderSettingsDialog = (tabId: TabId) => { renderOverlay({ @@ -400,12 +572,13 @@ const FavouritesPopover = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => { }; const LeftSidebarView = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => { - const config = useConfig(); + const initialConfig = useConfig(); + const [config, setConfig] = useState(initialConfig); return ( <> - + ); }; diff --git a/apps/roam/src/components/results-view/Kanban.tsx b/apps/roam/src/components/results-view/Kanban.tsx index c78fe3ec2..49fba14ca 100644 --- a/apps/roam/src/components/results-view/Kanban.tsx +++ b/apps/roam/src/components/results-view/Kanban.tsx @@ -76,6 +76,7 @@ const KanbanCard = (card: { const cardView = card.viewsByColumn[displayKey]; const displayUid = card.result[`${displayKey}-uid`]; + // TODO - https://linear.app/discourse-graphs/issue/ENG-909/migrate-kanban-react-draggable-to-hello-pangeadnd return ( void; + dragProvided: DraggableProvided; }) => { return ( -
+
+
+ +
{page.text}
{pages.length > 0 ? ( -
- {pages.map((page) => ( - void removePage(page)} - /> - ))} -
+ + { + const page = pages[rubric.source.index]; + return ( + void removePage(page)} + dragProvided={provided} + /> + ); + }} + > + {(provided) => ( +
+ {pages.map((page, index) => ( + + {(dragProvided: DraggableProvided): ReactNode => ( + void removePage(page)} + dragProvided={dragProvided} + /> + )} + + ))} + {provided.placeholder} +
+ )} +
+
) : (
No pages added yet diff --git a/apps/roam/src/components/settings/LeftSidebarPersonalSettings.tsx b/apps/roam/src/components/settings/LeftSidebarPersonalSettings.tsx index e4806f1d7..d63dd2f1f 100644 --- a/apps/roam/src/components/settings/LeftSidebarPersonalSettings.tsx +++ b/apps/roam/src/components/settings/LeftSidebarPersonalSettings.tsx @@ -1,14 +1,12 @@ import discourseConfigRef from "~/utils/discourseConfigRef"; import React, { useCallback, useEffect, useMemo, useState } from "react"; -import FlagPanel from "roamjs-components/components/ConfigPanels/FlagPanel"; import AutocompleteInput from "roamjs-components/components/AutocompleteInput"; import getAllPageNames from "roamjs-components/queries/getAllPageNames"; -import { Button, Dialog, Collapse, Tooltip } from "@blueprintjs/core"; +import { Button, Dialog, Collapse, InputGroup, Icon } from "@blueprintjs/core"; import createBlock from "roamjs-components/writes/createBlock"; import deleteBlock from "roamjs-components/writes/deleteBlock"; import type { RoamBasicNode } from "roamjs-components/types"; import NumberPanel from "roamjs-components/components/ConfigPanels/NumberPanel"; -import TextPanel from "roamjs-components/components/ConfigPanels/TextPanel"; import { LeftSidebarPersonalSectionConfig, getLeftSidebarPersonalSectionConfig, @@ -21,6 +19,16 @@ import { render as renderToast } from "roamjs-components/components/Toast"; import refreshConfigTree from "~/utils/refreshConfigTree"; import { refreshAndNotify } from "~/components/LeftSidebarView"; import { memo, Dispatch, SetStateAction } from "react"; +import { + DragDropContext, + Droppable, + Draggable, + DropResult, + DraggableProvided, + DroppableProvided, + DraggableRubric, + DraggableStateSnapshot, +} from "@hello-pangea/dnd"; const SectionItem = memo( ({ @@ -28,16 +36,17 @@ const SectionItem = memo( setSettingsDialogSectionUid, pageNames, setSections, + dragHandleProps, }: { section: LeftSidebarPersonalSectionConfig; setSections: Dispatch>; setSettingsDialogSectionUid: (uid: string | null) => void; pageNames: string[]; + dragHandleProps: DraggableProvided["dragHandleProps"]; }) => { const ref = extractRef(section.text); const blockText = getTextByBlockUid(ref); const originalName = blockText || section.text; - const alias = section.settings?.alias?.value; const [childInput, setChildInput] = useState(""); const [childInputKey, setChildInputKey] = useState(0); @@ -75,11 +84,6 @@ const SectionItem = memo( order: 1, node: { text: "Truncate-result?", children: [{ text: "75" }] }, }); - const aliasUid = await createBlock({ - parentUid: settingsUid, - order: 2, - node: { text: "Alias" }, - }); const childrenUid = await createBlock({ parentUid: section.uid, @@ -96,7 +100,6 @@ const SectionItem = memo( uid: settingsUid, folded: { uid: foldedUid, value: false }, truncateResult: { uid: truncateSettingUid, value: 75 }, - alias: { uid: aliasUid, value: "" }, }, childrenUid, children: [], @@ -222,7 +225,8 @@ const SectionItem = memo( }, [childInput, section, addChildToSection]); const sectionWithoutSettingsAndChildren = - !section.settings && !section.children; + (!section.settings && section.children?.length === 0) || + !section.children; return (
+
+ +
+
{!sectionWithoutSettingsAndChildren && (
+ ); + }} + > + {(provided: DroppableProvided) => (
- {child.text} -
+ )} + + ))} + {provided.placeholder}
- ))} -
+ )} + )} {(!section.children || section.children.length === 0) && ( @@ -434,6 +501,77 @@ const LeftSidebarPersonalSectionsContent = ({ [personalSectionUid, sections], ); + const handleDragEnd = useCallback( + (result: DropResult) => { + const { source, destination, type } = result; + if (!destination) return; + + if (type === "SECTIONS") { + if (destination.index === source.index) return; + + const newSections = Array.from(sections); + const [removed] = newSections.splice(source.index, 1); + newSections.splice(destination.index, 0, removed); + setSections(newSections); + + const finalIndex = + destination.index > source.index + ? destination.index + 1 + : destination.index; + void window.roamAlphaAPI + .moveBlock({ + location: { "parent-uid": personalSectionUid!, order: finalIndex }, + block: { uid: removed.uid }, + }) + .then(() => { + refreshAndNotify(); + }); + return; + } + + if (type === "ITEMS") { + if (source.droppableId !== destination.droppableId) { + return; + } + if (destination.index === source.index) return; + + const sectionToReorder = sections.find( + (s) => s.uid === source.droppableId, + ); + if (!sectionToReorder || !sectionToReorder.children) return; + + const newChildren = Array.from(sectionToReorder.children); + const [removed] = newChildren.splice(source.index, 1); + newChildren.splice(destination.index, 0, removed); + + const newSections = sections.map((s) => { + if (s.uid === source.droppableId) { + return { ...s, children: newChildren }; + } + return s; + }); + setSections(newSections); + + const finalIndex = + destination.index > source.index + ? destination.index + 1 + : destination.index; + void window.roamAlphaAPI + .moveBlock({ + location: { + "parent-uid": sectionToReorder.childrenUid!, + order: finalIndex, + }, + block: { uid: removed.uid }, + }) + .then(() => { + refreshAndNotify(); + }); + } + }, + [sections, personalSectionUid, setSections], + ); + const handleNewSectionInputChange = useCallback((value: string) => { setNewSectionInput(value); }, []); @@ -464,13 +602,11 @@ const LeftSidebarPersonalSectionsContent = ({ } }} > - handleNewSectionInputChange(e.target.value)} + placeholder="Add section …" />