From 581224b8c505771e3c3afa404c826681a5f5a73a Mon Sep 17 00:00:00 2001 From: Benjamin Preiss Date: Wed, 19 Mar 2025 17:40:31 +0100 Subject: [PATCH 01/10] WIP: Add chat view and view switching capabilities This commit introduces a new chat view option in the social media app, alongside the existing thread view. It includes: - New ViewProvider context to manage view state (chat/thread) - Enhanced Reply and CanvasPreview components to support chat-message variant - Added "chat" as a new sort criteria option in the dropdown - Auto-switching between views based on sort criteria selection - UI improvements for the chat view with appropriate styling The changes allow users to toggle between thread and chat display modes for better conversation flow. Note: This is work in progress and may need further UI refinements and testing. --- .../social-media-app/frontend/src/Home.tsx | 5 +- .../frontend/src/canvas/CanvasAndReplies.tsx | 6 +-- .../frontend/src/canvas/Preview.tsx | 26 +++++++++-- .../frontend/src/canvas/Replies.tsx | 46 +++++++++++-------- .../frontend/src/canvas/Reply.tsx | 32 ++++++++++++- .../frontend/src/view/View.tsx | 40 ++++++++++++++++ 6 files changed, 125 insertions(+), 30 deletions(-) create mode 100644 packages/social-media-app/frontend/src/view/View.tsx diff --git a/packages/social-media-app/frontend/src/Home.tsx b/packages/social-media-app/frontend/src/Home.tsx index e68b27c4..01a1c32f 100644 --- a/packages/social-media-app/frontend/src/Home.tsx +++ b/packages/social-media-app/frontend/src/Home.tsx @@ -1,9 +1,12 @@ import { CanvasAndReplies } from "./canvas/CanvasAndReplies"; +import { ViewProvider } from "./view/View"; export const Home = () => { return ( <> - + + + ); }; diff --git a/packages/social-media-app/frontend/src/canvas/CanvasAndReplies.tsx b/packages/social-media-app/frontend/src/canvas/CanvasAndReplies.tsx index 34f800d5..c398ced3 100644 --- a/packages/social-media-app/frontend/src/canvas/CanvasAndReplies.tsx +++ b/packages/social-media-app/frontend/src/canvas/CanvasAndReplies.tsx @@ -14,9 +14,9 @@ export const CanvasAndReplies = () => { const { peer } = usePeer(); const { root, path: canvases, loading } = useCanvases(); const [lastCanvas, setLastCanvas] = useState(undefined); - const [sortCriteria, setSortCriteria] = useState<"new" | "old" | "best">( - "new" - ); + const [sortCriteria, setSortCriteria] = useState< + "new" | "old" | "best" | "chat" + >("new"); // Refs for header, toolbar, and scroll container const toolbarRef = useRef(null); diff --git a/packages/social-media-app/frontend/src/canvas/Preview.tsx b/packages/social-media-app/frontend/src/canvas/Preview.tsx index 3e4d4921..802a39e3 100644 --- a/packages/social-media-app/frontend/src/canvas/Preview.tsx +++ b/packages/social-media-app/frontend/src/canvas/Preview.tsx @@ -13,7 +13,12 @@ import { Frame } from "../content/Frame"; import { rectIsStaticMarkdownText } from "./utils/rect"; interface CanvasPreviewProps { - variant: "tiny" | "post" | "breadcrumb" | "expanded-breadcrumb"; + variant: + | "tiny" + | "post" + | "breadcrumb" + | "expanded-breadcrumb" + | "chat-message"; onClick?: () => void; } @@ -113,7 +118,12 @@ const seperateAndSortRects = (rects: Element[]) => { }; type RectsForVariant< - V extends "tiny" | "post" | "breadcrumb" | "expanded-breadcrumb" + V extends + | "tiny" + | "post" + | "breadcrumb" + | "expanded-breadcrumb" + | "chat-message" > = V extends "tiny" ? Element | undefined : V extends "breadcrumb" @@ -122,10 +132,17 @@ type RectsForVariant< ? { text?: Element; other: Element[] } : V extends "post" ? { text?: Element; other: Element[] } + : V extends "chat-message" + ? { text?: Element; other: Element[] } : never; function getRectsForVariant< - Variant extends "tiny" | "post" | "breadcrumb" | "expanded-breadcrumb" + Variant extends + | "tiny" + | "post" + | "breadcrumb" + | "expanded-breadcrumb" + | "chat-message" >(separatedRects: SeparatedRects, variant: Variant): RectsForVariant { switch (variant) { case "tiny": @@ -135,6 +152,7 @@ function getRectsForVariant< undefined) as RectsForVariant; case "post": case "expanded-breadcrumb": + case "chat-message": return { text: separatedRects.text[0], other: separatedRects.other, @@ -229,7 +247,7 @@ export const CanvasPreview = ({ variant, onClick }: CanvasPreviewProps) => { ); } - if (variant === "post") { + if (variant === "post" || variant === "chat-message") { const [firstApp, ...secondaryApps] = ( variantRects as RectsForVariant<"post"> ).other; diff --git a/packages/social-media-app/frontend/src/canvas/Replies.tsx b/packages/social-media-app/frontend/src/canvas/Replies.tsx index 590d96f1..b5bcec76 100644 --- a/packages/social-media-app/frontend/src/canvas/Replies.tsx +++ b/packages/social-media-app/frontend/src/canvas/Replies.tsx @@ -6,8 +6,9 @@ import { SearchRequest } from "@peerbit/document-interface"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import { ChevronDownIcon } from "@radix-ui/react-icons"; import { Reply } from "./Reply"; +import { useView } from "../view/View"; -type SortCriteria = "new" | "old" | "best"; +type SortCriteria = "new" | "old" | "best" | "chat"; interface RepliesProps { canvas?: CanvasDB; @@ -20,8 +21,16 @@ export const Replies = (props: RepliesProps) => { const [query, setQuery] = useState< { query: SearchRequest; id: string } | undefined >(undefined); + const { setView, view } = useView(); useEffect(() => { + // Set the view based on sortCriteria + if (sortCriteria === "chat") { + setView("chat"); + } else { + setView("thread"); + } + if (sortCriteria === "best") { setQuery({ query: new SearchRequest({ @@ -54,7 +63,7 @@ export const Replies = (props: RepliesProps) => { id: sortCriteria, }); } - }, [sortCriteria]); + }, [sortCriteria, setView]); const sortedReplies = useLocal(canvas?.replies, query); @@ -72,24 +81,20 @@ export const Replies = (props: RepliesProps) => { style={{ padding: "0.5rem", minWidth: "150px" }} className="bg-neutral-50 dark:bg-neutral-950 rounded-md shadow-lg" > - setSortCriteria("new")} - > - New - - setSortCriteria("old")} - > - Old - - setSortCriteria("best")} - > - Best - + {["new", "old", "best", "chat"].map( + (sortCriterium, index) => ( + + setSortCriteria(sortCriterium as any) + } + > + {sortCriterium.charAt(0).toUpperCase() + + sortCriterium.slice(1)} + + ) + )} @@ -100,6 +105,7 @@ export const Replies = (props: RepliesProps) => { key={reply.idString} canvas={reply} variant="large" + view={view} /> ))} diff --git a/packages/social-media-app/frontend/src/canvas/Reply.tsx b/packages/social-media-app/frontend/src/canvas/Reply.tsx index d5324df6..c174bb76 100644 --- a/packages/social-media-app/frontend/src/canvas/Reply.tsx +++ b/packages/social-media-app/frontend/src/canvas/Reply.tsx @@ -9,6 +9,7 @@ import { getCanvasPath } from "../routes"; import { Header } from "./header/Header"; import { CanvasWrapper } from "./CanvasWrapper"; import { LuMessageSquare } from "react-icons/lu"; +import { useView, ViewType } from "../view/View"; // Debounce helper that triggers on the leading edge and then ignores calls for the next delay ms. function debounceLeading(func: (...args: any[]) => void, delay: number) { @@ -40,14 +41,29 @@ const ReplyButton = ({ ); }; +/** + * Reply component for displaying a Canvas reply. + * @param props - Component props + * @param props.canvas - The canvas data object to display + * @param props.variant - Display size variant + * - "tiny": Compact display for breadcrumbs or nested view + * - "large": Full-sized display with more controls, e.g. used in post in threaded view + * @param props.view - Optional view type (defaults to "threaded" if not supplied) + * - "chat": Optimized for chat-like display + * - "threaded": Standard threaded view + * @param props.index - Optional index of the reply in a list + * @param props.onClick - Optional click handler for the reply + */ export const Reply = ({ canvas, variant, index, onClick, + view, }: { canvas: WithContext; variant: "tiny" | "large"; + view?: ViewType; index?: number; onClick?: () => void; }) => { @@ -122,11 +138,23 @@ export const Reply = ({ navigate(getCanvasPath(canvas), {}); onClick && onClick(); }} - className="w-full flex flex-row p-0 overflow-hidden" + className={`w-full flex flex-row p-0 overflow-hidden ${ + view === "chat" ? "border rounded-md" : "" + }`} > {variant === "large" ? ( - showMore ? ( + /* chat view */ + view === "chat" ? ( + showMore ? ( + + ) : ( + + ) + ) : /* thread view */ showMore ? ( ) : ( diff --git a/packages/social-media-app/frontend/src/view/View.tsx b/packages/social-media-app/frontend/src/view/View.tsx new file mode 100644 index 00000000..b5838625 --- /dev/null +++ b/packages/social-media-app/frontend/src/view/View.tsx @@ -0,0 +1,40 @@ +import React, { createContext, useState, useContext } from "react"; + +// Define the view type +export type ViewType = "chat" | "thread"; + +// Create the context +const ViewContext = createContext< + | { + view: ViewType; + setView: React.Dispatch>; + } + | undefined +>(undefined); + +// Custom hook to use the view context +export const useView = () => { + const context = useContext(ViewContext); + if (!context) { + throw new Error("useView must be used within a ViewProvider"); + } + return context; +}; + +// Provider component +export const ViewProvider = ({ + children, + initialView = "chat" as ViewType, +}) => { + const [view, setView] = useState(initialView); + + // Value object to be provided to consumers + const value = { + view, + setView, + }; + + return ( + {children} + ); +}; From 8107460eb586d947a40d5e4ddb5ce5c962345c49 Mon Sep 17 00:00:00 2001 From: Marcus Pousette Date: Wed, 19 Mar 2025 17:56:33 +0100 Subject: [PATCH 02/10] fix: generate posts with different identities --- .../src/canvas/toolbar/DebugGeneratePostButton.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/social-media-app/frontend/src/canvas/toolbar/DebugGeneratePostButton.tsx b/packages/social-media-app/frontend/src/canvas/toolbar/DebugGeneratePostButton.tsx index d7f26e8e..c4029207 100644 --- a/packages/social-media-app/frontend/src/canvas/toolbar/DebugGeneratePostButton.tsx +++ b/packages/social-media-app/frontend/src/canvas/toolbar/DebugGeneratePostButton.tsx @@ -10,6 +10,7 @@ import { StaticImage, StaticMarkdownText, } from "@dao-xyz/social"; +import { Ed25519Keypair } from "@peerbit/crypto"; const generateATextInMarkdown = (length: number = 100) => { let text = ""; @@ -111,14 +112,20 @@ export const DebugGeneratePostButton = () => { ["image", "image"], "text", ]; - for (const type of postsToCreate) { + + for (const [px, type] of postsToCreate.entries()) { + let publicKeyTuse = + px % 2 === 0 + ? peer.identity.publicKey + : (await Ed25519Keypair.create()).publicKey; + const typeArray = Array.isArray(type) ? type : [type]; // create a post (canvas) that references its parent const canvas = new Canvas({ parent: new CanvasAddressReference({ canvas: path[path.length - 1], }), - publicKey: peer.identity.publicKey, + publicKey: publicKeyTuse, }); // open it (so we can insert elements) @@ -156,7 +163,7 @@ export const DebugGeneratePostButton = () => { breakpoint: "md", }), ], - publicKey: peer.identity.publicKey, + publicKey: publicKeyTuse, }) ); } From 0e907badb1cb5e3b31f3b0a7b220bc2785f5ff19 Mon Sep 17 00:00:00 2001 From: Benjamin Preiss Date: Wed, 19 Mar 2025 19:39:01 +0100 Subject: [PATCH 03/10] feat: enhance chat UI with improved message alignment and identity features - Add left/right message alignment based on sender identity - Improve chat bubble styling and container dimensions - Implement proper avatar sizing in message headers - Fix chat view sorting to display messages chronologically - Add isMe utility function to identify user's messages across devices - Fix height and spacing issues in conversation components --- .../frontend/src/canvas/Preview.tsx | 2 +- .../frontend/src/canvas/Replies.tsx | 8 +- .../frontend/src/canvas/Reply.tsx | 73 ++++++++++++------- .../frontend/src/canvas/header/Header.tsx | 44 ++++++++--- .../frontend/src/identity/useIdentities.tsx | 40 +++++++++- 5 files changed, 127 insertions(+), 40 deletions(-) diff --git a/packages/social-media-app/frontend/src/canvas/Preview.tsx b/packages/social-media-app/frontend/src/canvas/Preview.tsx index 802a39e3..d31c3347 100644 --- a/packages/social-media-app/frontend/src/canvas/Preview.tsx +++ b/packages/social-media-app/frontend/src/canvas/Preview.tsx @@ -253,7 +253,7 @@ export const CanvasPreview = ({ variant, onClick }: CanvasPreviewProps) => { ).other; const text = (variantRects as RectsForVariant<"post">).text; return ( -
+
{firstApp && (
{ sort: new Sort({ key: ["__context", "created"], direction: - sortCriteria === "new" + sortCriteria === "new" || sortCriteria === "chat" ? SortDirection.ASC : SortDirection.DESC, }), @@ -99,7 +99,11 @@ export const Replies = (props: RepliesProps) => {
{sortedReplies.length > 0 ? ( -
+
{sortedReplies.map((reply) => (