Skip to content

Commit 72b63b6

Browse files
committed
Merge remote-tracking branch 'origin/feature_branch' into 16-implement-shape-component
# Conflicts: # client/package-lock.json # client/package.json # client/src/app/page.tsx
2 parents 86ff558 + cd6d62c commit 72b63b6

File tree

5 files changed

+207
-14
lines changed

5 files changed

+207
-14
lines changed

client/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
"format": "prettier --write \"src/**/*.{ts,tsx}\""
1111
},
1212
"dependencies": {
13+
"@radix-ui/react-dialog": "^1.1.14",
14+
"@radix-ui/react-separator": "^1.1.7",
15+
"@radix-ui/react-slot": "^1.2.3",
16+
"@radix-ui/react-tooltip": "^1.2.7",
1317
"@radix-ui/react-popover": "^1.1.13",
1418
"@radix-ui/react-select": "^2.2.5",
15-
"@radix-ui/react-separator": "^1.1.6",
1619
"@radix-ui/react-slider": "^1.3.4",
17-
"@radix-ui/react-slot": "^1.2.2",
1820
"@radix-ui/react-tabs": "^1.1.11",
1921
"@radix-ui/themes": "^3.2.1",
2022
"@xyflow/react": "^12.6.1",

client/src/app/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
1313
});
1414

1515
export const metadata: Metadata = {
16-
title: "Create Next App",
17-
description: "Generated by create next app",
16+
title: "AI-Powered Whiteboard",
17+
description: "AI-Powered Whiteboard",
1818
};
1919

2020
export default function RootLayout({

client/src/components/TextNode.tsx

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import React, { useState } from 'react';
2+
import { NodeProps } from '@xyflow/react';
3+
import { Bold, Italic, Underline, Settings, Eye, EyeOff } from 'lucide-react';
4+
import { Button } from '@/components/ui/button';
5+
6+
interface TextNodeStyle {
7+
color?: string;
8+
}
9+
10+
export function TextNode({ data }: NodeProps) {
11+
const [isEditing, setIsEditing] = useState(false);
12+
const [text, setText] = useState<string>(data.label as string);
13+
const [style, setStyle] = useState<TextNodeStyle>(data.style || {});
14+
const [isBold, setIsBold] = useState<boolean>(false);
15+
const [isItalic, setIsItalic] = useState<boolean>(false);
16+
const [isUnderline, setIsUnderline] = useState<boolean>(false);
17+
const [showFormatting, setShowFormatting] = useState(false);
18+
const [showSettings, setShowSettings] = useState(true);
19+
20+
const updateStyle = () => {
21+
const computedStyle: React.CSSProperties = {
22+
fontWeight: isBold ? 'bold' : 'normal',
23+
fontStyle: isItalic ? 'italic' : 'normal',
24+
textDecoration: isUnderline ? 'underline' : 'none',
25+
color: style.color || '#000000',
26+
};
27+
data.style = computedStyle;
28+
return computedStyle;
29+
};
30+
31+
const handleColorChange = (color: string) => {
32+
const newStyle = { ...style, color };
33+
setStyle(newStyle);
34+
};
35+
36+
return (
37+
<div className="relative group">
38+
<Button
39+
variant="ghost"
40+
size="sm"
41+
onClick={() => setShowSettings(!showSettings)}
42+
className="absolute -right-10 -top-8 h-7 w-7 p-1 opacity-0 group-hover:opacity-100 transition-opacity"
43+
>
44+
{showSettings ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
45+
</Button>
46+
47+
{showSettings && (
48+
<Button
49+
variant="ghost"
50+
size="sm"
51+
onClick={() => setShowFormatting(!showFormatting)}
52+
className="absolute -left-10 top-2 h-7 w-7 p-1"
53+
>
54+
<Settings className="h-4 w-4" />
55+
</Button>
56+
)}
57+
58+
{showFormatting && (
59+
<div className="absolute -left-32 top-0 flex flex-col gap-1 p-2 bg-white rounded-md shadow-lg border border-gray-200 z-10">
60+
<Button
61+
variant="ghost"
62+
size="sm"
63+
onClick={() => setIsBold(!isBold)}
64+
className={`h-7 w-7 p-1 ${isBold ? 'bg-slate-200' : ''}`}
65+
>
66+
<Bold className="h-4 w-4" />
67+
</Button>
68+
<Button
69+
variant="ghost"
70+
size="sm"
71+
onClick={() => setIsItalic(!isItalic)}
72+
className={`h-7 w-7 p-1 ${isItalic ? 'bg-slate-200' : ''}`}
73+
>
74+
<Italic className="h-4 w-4" />
75+
</Button>
76+
<Button
77+
variant="ghost"
78+
size="sm"
79+
onClick={() => setIsUnderline(!isUnderline)}
80+
className={`h-7 w-7 p-1 ${isUnderline ? 'bg-slate-200' : ''}`}
81+
>
82+
<Underline className="h-4 w-4" />
83+
</Button>
84+
<input
85+
type="color"
86+
onChange={(e) => handleColorChange(e.target.value)}
87+
value={style.color || '#000000'}
88+
className="w-7 h-7 rounded cursor-pointer"
89+
/>
90+
</div>
91+
)}
92+
93+
<div
94+
className="p-4 border rounded bg-white min-w-[100px] shadow-sm"
95+
onDoubleClick={(e) => {
96+
e.stopPropagation();
97+
setIsEditing(true);
98+
}}
99+
>
100+
{isEditing ? (
101+
<input
102+
type="text"
103+
value={text}
104+
onChange={(e) => setText(e.target.value)}
105+
onBlur={() => {
106+
setIsEditing(false);
107+
data.label = text;
108+
}}
109+
onKeyDown={(e) => {
110+
if (e.key === 'Enter') {
111+
setIsEditing(false);
112+
data.label = text;
113+
}
114+
}}
115+
autoFocus
116+
className="bg-transparent outline-none w-full border-none p-0"
117+
style={updateStyle()}
118+
/>
119+
) : (
120+
<p style={updateStyle()}>{text}</p>
121+
)}
122+
</div>
123+
</div>
124+
);
125+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"use client";
2+
3+
import React, { useCallback } from "react";
4+
import {
5+
ReactFlow,
6+
Background,
7+
BackgroundVariant,
8+
Controls,
9+
Node,
10+
useNodesState,
11+
} from "@xyflow/react";
12+
import { Button } from "@/components/ui/button";
13+
import { Type } from "lucide-react";
14+
import "@xyflow/react/dist/style.css";
15+
import { TextNode} from "@/components/TextNode";
16+
17+
const nodeTypes = {
18+
text: TextNode,
19+
};
20+
21+
export function WhiteBoard() {
22+
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
23+
24+
const onAddTextNode = useCallback(() => {
25+
const newNode: Node = {
26+
id: `text-${nodes.length + 1}`,
27+
type: 'text',
28+
position: { x: 100, y: 100 },
29+
data: {
30+
label: 'Add your text here',
31+
style: {
32+
fontWeight: 'normal',
33+
fontStyle: 'normal',
34+
textDecoration: 'none',
35+
color: '#000000'
36+
}
37+
},
38+
};
39+
setNodes((nds) => [...nds, newNode]);
40+
}, [nodes, setNodes]);
41+
42+
return (
43+
<div style={{ width: "100vw", height: "100vh" }}>
44+
<div className="fixed left-4 top-1/2 -translate-y-1/2 z-10 flex flex-col gap-2 bg-white/50 backdrop-blur-sm p-3 rounded-xl shadow-lg border border-gray-200/50 w-40 h-[200px] items-center justify-center">
45+
<Button
46+
variant="ghost"
47+
size="icon"
48+
onClick={onAddTextNode}
49+
className="hover:bg-slate-100 w-8 h-8 rounded-lg"
50+
>
51+
<Type className="size-10"/>
52+
</Button>
53+
</div>
54+
55+
<ReactFlow
56+
nodes={nodes}
57+
onNodesChange={onNodesChange}
58+
nodeTypes={nodeTypes}
59+
fitView
60+
>
61+
<Controls position="bottom-right" />
62+
<Background variant={BackgroundVariant.Dots} gap={16} size={1} />
63+
</ReactFlow>
64+
</div>
65+
);
66+
}

client/src/components/ui/button.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import * as React from "react";
2-
import { Slot } from "@radix-ui/react-slot";
3-
import { cva, type VariantProps } from "class-variance-authority";
1+
import * as React from "react"
2+
import { Slot } from "@radix-ui/react-slot"
3+
import { cva, type VariantProps } from "class-variance-authority"
44

5-
import { cn } from "@/util/utils";
5+
import { cn } from "@/util/utils"
66

77
const buttonVariants = cva(
88
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
@@ -32,8 +32,8 @@ const buttonVariants = cva(
3232
variant: "default",
3333
size: "default",
3434
},
35-
},
36-
);
35+
}
36+
)
3737

3838
function Button({
3939
className,
@@ -43,17 +43,17 @@ function Button({
4343
...props
4444
}: React.ComponentProps<"button"> &
4545
VariantProps<typeof buttonVariants> & {
46-
asChild?: boolean;
46+
asChild?: boolean
4747
}) {
48-
const Comp = asChild ? Slot : "button";
48+
const Comp = asChild ? Slot : "button"
4949

5050
return (
5151
<Comp
5252
data-slot="button"
5353
className={cn(buttonVariants({ variant, size, className }))}
5454
{...props}
5555
/>
56-
);
56+
)
5757
}
5858

59-
export { Button, buttonVariants };
59+
export { Button, buttonVariants }

0 commit comments

Comments
 (0)