Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,014 changes: 796 additions & 218 deletions examples/nextjs-comments-canvas/package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions examples/nextjs-comments-canvas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
},
"dependencies": {
"@dnd-kit/core": "^6.1.0",
"@liveblocks/client": "^3.14.0",
"@liveblocks/node": "^3.14.0",
"@liveblocks/react": "^3.14.0",
"@liveblocks/react-ui": "^3.14.0",
"@liveblocks/client": "^3.15.0-components1",
"@liveblocks/node": "^3.15.0-components1",
"@liveblocks/react": "^3.15.0-components1",
"@liveblocks/react-ui": "^3.15.0-components1",
"next": "^16.1.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down

This file was deleted.

128 changes: 31 additions & 97 deletions examples/nextjs-comments-canvas/src/components/CommentsCanvas.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import { Thread } from "@liveblocks/react-ui";
import { ThreadData } from "@liveblocks/core";
import { ThreadData } from "@liveblocks/client";
import { useThreads, useEditThreadMetadata } from "@liveblocks/react/suspense";
import { useUser } from "@liveblocks/react";
import {
DataRef,
DndContext,
DragEndEvent,
PointerSensor,
TouchSensor,
useDraggable,
useSensor,
useSensors,
} from "@dnd-kit/core";
import { useCallback, useMemo, useRef, useState } from "react";
import styles from "./CommentsCanvas.module.css";
import { Toolbar } from "./Toolbar";
import { useMaxZIndex, useNearEdge } from "../hooks";
import { useCallback } from "react";
import { PlaceThreadButton } from "./PlaceThreadButton";
import { DraggableThread } from "./DraggableThread";

export function CommentsCanvas() {
const { threads } = useThreads();
Expand All @@ -36,103 +32,41 @@ export function CommentsCanvas() {
);

// On drag end, update thread metadata with new coords
const handleDragEnd = useCallback(({ active, delta }: DragEndEvent) => {
const thread = (active.data as DataRef<{ thread: ThreadData }>).current
?.thread;

if (!thread) {
return;
}

editThreadMetadata({
threadId: thread.id,
metadata: {
x: thread.metadata.x + delta.x,
y: thread.metadata.y + delta.y,
},
});
}, []);

return (
<div className={`${styles.wrapper} lb-root`}>
<div className={styles.threads}>
<DndContext onDragEnd={handleDragEnd} sensors={sensors}>
{threads.map((thread) => (
<DraggableThread key={thread.id} thread={thread} />
))}
</DndContext>
</div>
<Toolbar />
</div>
const handleDragEnd = useCallback(
({ active, delta }: DragEndEvent) => {
const thread = (active.data as DataRef<{ thread: ThreadData }>).current
?.thread;
if (!thread) {
return;
}
editThreadMetadata({
threadId: thread.id,
metadata: {
x: thread.metadata.x + delta.x,
y: thread.metadata.y + delta.y,
},
});
},
[editThreadMetadata]
);
}

// A draggable thread
function DraggableThread({ thread }: { thread: ThreadData }) {
// Open threads that have just been created
const startOpen = useMemo(() => {
return Number(new Date()) - Number(new Date(thread.createdAt)) <= 100;
}, [thread]);
const [open, setOpen] = useState(startOpen);

// Enable drag
const { attributes, listeners, setNodeRef, transform, node } = useDraggable({
id: thread.id,
data: { thread }, // Pass thread to DndContext drag end event
});

// If currently dragging, add drag values to current metadata
const x = transform ? transform.x + thread.metadata.x : thread.metadata.x;
const y = transform ? transform.y + thread.metadata.y : thread.metadata.y;

// Get the creator of the thread
const { user: creator } = useUser(thread.comments[0].userId);

// Used to set z-index higher than other threads when pointer down
const editThreadMetadata = useEditThreadMetadata();
const maxZIndex = useMaxZIndex();

// Used to flip thread near edge of screen
const { nearRightEdge, nearBottomEdge } = useNearEdge(node);

return (
<div
ref={setNodeRef}
className={styles.draggableThread}
onPointerDown={() =>
editThreadMetadata({
threadId: thread.id,
metadata: { zIndex: maxZIndex + 1 },
})
}
style={{
transform: `translate3d(${x}px, ${y}px, 0)`,
zIndex: thread.metadata?.zIndex || 0,
position: "relative",
width: "100vw",
height: "100vh",
overflow: "hidden",
}}
>
<div {...listeners} {...attributes}>
<div className={styles.avatar} onClick={() => setOpen(!open)}>
{creator ? (
<img
src={creator.avatar}
alt={creator.name}
width="28px"
height="28px"
draggable={false}
/>
) : (
<div />
)}
</div>
<div style={{ isolation: "isolate" }}>
<DndContext onDragEnd={handleDragEnd} sensors={sensors}>
{threads.map((thread) => (
<DraggableThread key={thread.id} thread={thread} />
))}
</DndContext>
</div>
{open ? (
<Thread
thread={thread}
className="thread"
data-flip-vertical={nearBottomEdge || undefined}
data-flip-horizontal={nearRightEdge || undefined}
/>
) : null}
<PlaceThreadButton />
</div>
);
}
62 changes: 62 additions & 0 deletions examples/nextjs-comments-canvas/src/components/DraggableThread.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useMemo } from "react";
import { useEditThreadMetadata } from "@liveblocks/react/suspense";
import { FloatingThread, CommentPin } from "@liveblocks/react-ui";
import { ThreadData } from "@liveblocks/client";
import { useDraggable } from "@dnd-kit/core";
import { useMaxZIndex } from "../hooks";

// A draggable thread
export function DraggableThread({ thread }: { thread: ThreadData }) {
// Open threads that have just been created
const defaultOpen = useMemo(() => {
return Number(new Date()) - Number(new Date(thread.createdAt)) <= 100;
}, [thread]);

// Enable drag
const { isDragging, attributes, listeners, setNodeRef, transform } =
useDraggable({
id: thread.id,
data: { thread }, // Pass thread to DndContext drag end event
});

// If currently dragging, add drag values to current metadata
const x = transform ? transform.x + thread.metadata.x : thread.metadata.x;
const y = transform ? transform.y + thread.metadata.y : thread.metadata.y;

// Used to set z-index higher than other threads when dragging
const editThreadMetadata = useEditThreadMetadata();
const maxZIndex = useMaxZIndex();

return (
<FloatingThread
thread={thread}
defaultOpen={defaultOpen}
side="right"
style={{ pointerEvents: isDragging ? "none" : "auto" }}
>
<div
ref={setNodeRef}
onPointerDown={() =>
editThreadMetadata({
threadId: thread.id,
metadata: { zIndex: maxZIndex + 1 },
})
}
style={{
position: "absolute",
top: 0,
left: 0,
transform: `translate3d(${x}px, ${y}px, 0)`,
zIndex: thread.metadata?.zIndex || 0,
}}
>
<CommentPin
userId={thread.comments[0]?.userId}
corner="top-left"
{...listeners}
{...attributes}
/>
</div>
</FloatingThread>
);
}
Loading
Loading