diff options
| author | MohamedBassem <me@mbassem.com> | 2024-05-18 12:14:55 +0100 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-05-18 12:14:55 +0100 |
| commit | e5fd9eeca0d34658c9eed4f1a7d6ec25d4918488 (patch) | |
| tree | 8e30298660158aa6f9bdf4fffadf6367c0b4d2ee /apps | |
| parent | 1fee129c337069fc41d1c46141beaaec94033af8 (diff) | |
| download | karakeep-e5fd9eeca0d34658c9eed4f1a7d6ec25d4918488.tar.zst | |
fix(web): Simplify the logic for tag drag and dropping
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/web/components/dashboard/tags/AllTagsView.tsx | 72 | ||||
| -rw-r--r-- | apps/web/components/dashboard/tags/TagPill.tsx | 22 | ||||
| -rw-r--r-- | apps/web/lib/drag-and-drop.ts | 101 |
3 files changed, 68 insertions, 127 deletions
diff --git a/apps/web/components/dashboard/tags/AllTagsView.tsx b/apps/web/components/dashboard/tags/AllTagsView.tsx index 017a1e40..ac139e23 100644 --- a/apps/web/components/dashboard/tags/AllTagsView.tsx +++ b/apps/web/components/dashboard/tags/AllTagsView.tsx @@ -15,6 +15,7 @@ import { Toggle } from "@/components/ui/toggle"; import { toast } from "@/components/ui/use-toast"; import { useDragAndDrop } from "@/lib/drag-and-drop"; import { api } from "@/lib/trpc"; +import { ArrowDownAZ, Combine } from "lucide-react"; import Draggable from "react-draggable"; import type { ZGetTagResponse } from "@hoarder/shared/types/tags"; @@ -75,27 +76,26 @@ export default function AllTagsView({ }: { initialData: ZGetTagResponse[]; }) { - const [draggingEnabled, toggleDraggingEnabled] = React.useState(false); - const [sortByName, toggleSortByName] = React.useState(false); - - const { dragState, handleDrag, handleDragStart, handleDragEnd } = - useDragAndDrop( - "data-id", - "data-id", - (dragSourceId: string, dragTargetId: string) => { - mergeTag({ - fromTagIds: [dragSourceId], - intoTagId: dragTargetId, - }); - }, - ); + const [draggingEnabled, setDraggingEnabled] = React.useState(false); + const [sortByName, setSortByName] = React.useState(false); + + const { handleDragStart, handleDragEnd } = useDragAndDrop( + "data-id", + "data-id", + (dragSourceId: string, dragTargetId: string) => { + mergeTag({ + fromTagIds: [dragSourceId], + intoTagId: dragTargetId, + }); + }, + ); - function handleSortByNameChange(): void { - toggleSortByName(!sortByName); + function toggleSortByName(): void { + setSortByName(!sortByName); } - function handleDraggableChange(): void { - toggleDraggingEnabled(!draggingEnabled); + function toggleDraggingEnabled(): void { + setDraggingEnabled(!draggingEnabled); } const { mutate: mergeTag } = useMergeTag({ @@ -131,6 +131,7 @@ export default function AllTagsView({ const { data } = api.tags.list.useQuery(undefined, { initialData: { tags: initialData }, }); + // Sort tags by usage desc const allTags = data.tags.sort(sortByName ? byNameSorter : byUsageSorter); @@ -148,28 +149,15 @@ export default function AllTagsView({ key={t.id} axis="both" onStart={handleDragStart} - onDrag={handleDrag} onStop={handleDragEnd} disabled={!draggingEnabled} defaultClassNameDragging={ "position-relative z-10 pointer-events-none" } - position={ - !dragState.dragSourceId - ? { - x: dragState.initialX ?? 0, - y: dragState.initialY ?? 0, - } - : undefined - } + position={{ x: 0, y: 0 }} > - <div className="group relative flex cursor-grab" data-id={t.id}> - <TagPill - id={t.id} - name={t.name} - count={t.count} - isDraggable={draggingEnabled} - /> + <div className="cursor-grab" data-id={t.id}> + <TagPill id={t.id} name={t.name} count={t.count} /> </div> </Draggable> ))} @@ -182,22 +170,24 @@ export default function AllTagsView({ }; return ( <> - <div className="float-right"> + <div className="flex justify-end gap-x-2"> <Toggle variant="outline" - aria-label="Toggle bold" pressed={draggingEnabled} - onPressedChange={handleDraggableChange} + onPressedChange={toggleDraggingEnabled} > - Allow Merging via Drag&Drop + <Combine className="mr-2 size-4" /> + Drag & Drop Merging + <InfoTooltip size={15} className="my-auto ml-2" variant="explain"> + <p>Drag and drop tags on each other to merge them</p> + </InfoTooltip> </Toggle> <Toggle variant="outline" - aria-label="Toggle bold" pressed={sortByName} - onPressedChange={handleSortByNameChange} + onPressedChange={toggleSortByName} > - Sort by Name + <ArrowDownAZ className="mr-2 size-4" /> Sort by Name </Toggle> </div> <span className="flex items-center gap-2"> diff --git a/apps/web/components/dashboard/tags/TagPill.tsx b/apps/web/components/dashboard/tags/TagPill.tsx index 7236dc39..f1c99d70 100644 --- a/apps/web/components/dashboard/tags/TagPill.tsx +++ b/apps/web/components/dashboard/tags/TagPill.tsx @@ -5,32 +5,24 @@ import { X } from "lucide-react"; import DeleteTagConfirmationDialog from "./DeleteTagConfirmationDialog"; -const PILL_STYLE = - "flex gap-2 rounded-md border border-border bg-background px-2 py-1 text-foreground hover:bg-foreground hover:text-background"; - export function TagPill({ id, name, count, - isDraggable, }: { id: string; name: string; count: number; - isDraggable: boolean; }) { - // When the element is draggable, do not generate a link. Links can be dragged into e.g. the tab-bar and therefore dragging the TagPill does not work properly - if (isDraggable) { - return ( - <div className={PILL_STYLE} data-id={id}> - {name} <Separator orientation="vertical" /> {count} - </div> - ); - } - return ( <div className="group relative flex"> - <Link className={PILL_STYLE} href={`/dashboard/tags/${id}`}> + <Link + className={ + "flex gap-2 rounded-md border border-border bg-background px-2 py-1 text-foreground hover:bg-foreground hover:text-background" + } + href={`/dashboard/tags/${id}`} + data-id={id} + > {name} <Separator orientation="vertical" /> {count} </Link> diff --git a/apps/web/lib/drag-and-drop.ts b/apps/web/lib/drag-and-drop.ts index 03a9e7c3..e005a6d0 100644 --- a/apps/web/lib/drag-and-drop.ts +++ b/apps/web/lib/drag-and-drop.ts @@ -1,69 +1,30 @@ +import React from "react"; import { DraggableData, DraggableEvent } from "react-draggable"; -export interface DragState { - // The id of the element that is being dragged - dragSourceId: string | null; - // The id of the element that is currently being hovered over - dragTargetId: string | null; - // The position of the elements being dragged such that on drag over, we can revert the position. - initialX: number; - initialY: number; -} - -export interface DragAndDropFunctions { - handleDragStart: (e: DraggableEvent, data: DraggableData) => void; - handleDrag: (e: DraggableEvent) => void; - handleDragEnd: () => void; - dragState: DragState; -} - export function useDragAndDrop( dragSourceIdAttribute: string, dragTargetIdAttribute: string, - callback: (dragSourceId: string, dragTargetId: string) => void, -): DragAndDropFunctions { - const initialState: DragState = { - dragSourceId: null, - dragTargetId: null, - initialX: 0, - initialY: 0, - }; - - let currentState: DragState = initialState; - - function handleDragStart(e: DraggableEvent, data: DraggableData): void { - const { node } = data; - const id = node.getAttribute(dragSourceIdAttribute); - - currentState = { - ...initialState, - dragSourceId: id, - initialX: data.x, - initialY: data.y, - }; - } - - function handleDrag(e: DraggableEvent): void { - const { dragTargetId } = currentState; - const { target } = e; - - // Important according to the sample I found - e.preventDefault(); - - if (target) { - const id = (target as HTMLElement).getAttribute(dragTargetIdAttribute); - - if (id !== dragTargetId) { - currentState.dragTargetId = id; - } - } - } - - function handleDragEnd(): void { - const { dragSourceId, dragTargetId } = currentState; - - if (dragSourceId && dragTargetId && dragSourceId !== dragTargetId) { - /* + onDragOver: (dragSourceId: string, dragTargetId: string) => void, +) { + const [dragSourceId, setDragSourceId] = React.useState<string | null>(null); + + const handleDragStart = React.useCallback( + (_e: DraggableEvent, { node }: DraggableData) => { + const id = node.getAttribute(dragSourceIdAttribute); + setDragSourceId(id); + }, + [], + ); + + const handleDragEnd = React.useCallback( + (e: DraggableEvent) => { + const { target } = e; + const dragTargetId = (target as HTMLElement).getAttribute( + dragTargetIdAttribute, + ); + + if (dragSourceId && dragTargetId && dragSourceId !== dragTargetId) { + /* As Draggable tries to setState when the component is unmounted, it is needed to push onCombine to the event loop queue. @@ -71,19 +32,17 @@ export function useDragAndDrop( Draggable so it would fix the issue until they fix it on their end. */ - setTimeout(() => { - console.log(dragSourceId, dragTargetId); - callback(dragSourceId, dragTargetId); - }, 0); - } - - currentState = initialState; - } + setTimeout(() => { + onDragOver(dragSourceId, dragTargetId); + }, 0); + } + setDragSourceId(null); + }, + [dragSourceId, onDragOver], + ); return { - dragState: currentState, handleDragStart, - handleDrag, handleDragEnd, }; } |
