Skip to content

Commit 4bb8de2

Browse files
committed
Implement collaboration features in Excalidraw app: add room management with a sidebar for active rooms, enhance connection dialog to display current room ID, and integrate room switching functionality. Update styles for room display and improve element reconciliation during collaboration.
1 parent 85948af commit 4bb8de2

File tree

8 files changed

+712
-40
lines changed

8 files changed

+712
-40
lines changed

excalidraw-app/src/App.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ function App() {
88
const [showDialog, setShowDialog] = useState(true);
99
const [serverConfig, setServerConfig] = useState<ServerConfig | null>(null);
1010

11+
const [roomId, setRoomId] = useState<string | null>(null);
12+
1113
useEffect(() => {
1214
// Check if we have a saved config in localStorage
1315
const saved = localStorage.getItem('excalidraw-server-config');
@@ -30,25 +32,38 @@ function App() {
3032
return () => window.removeEventListener('keydown', handleKeyDown);
3133
}, []);
3234

33-
const handleConnect = (config: ServerConfig) => {
35+
const handleConnect = (config: ServerConfig, newRoomId?: string) => {
3436
setServerConfig(config);
37+
if (newRoomId) {
38+
setRoomId(newRoomId);
39+
}
3540
setShowDialog(false);
3641
};
3742

3843
if (!serverConfig) {
39-
return <ConnectionDialog onConnect={handleConnect} onClose={() => setShowDialog(false)} />;
44+
return (
45+
<ConnectionDialog
46+
onConnect={handleConnect}
47+
onClose={() => setShowDialog(false)}
48+
isConnected={false}
49+
/>
50+
);
4051
}
4152

4253
return (
4354
<>
4455
<ExcalidrawWrapper
4556
serverConfig={serverConfig}
4657
onOpenSettings={() => setShowDialog(true)}
58+
onRoomIdChange={setRoomId}
59+
initialRoomId={roomId}
4760
/>
4861
{showDialog && (
4962
<ConnectionDialog
5063
onConnect={handleConnect}
51-
onClose={() => setShowDialog(false)}
64+
onClose={() => setShowDialog(false)}
65+
currentRoomId={roomId || undefined}
66+
isConnected={serverConfig.enabled && !!roomId}
5267
/>
5368
)}
5469
</>

excalidraw-app/src/components/ConnectionDialog.css

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,60 @@
9595
cursor: not-allowed;
9696
}
9797

98+
.room-info {
99+
background-color: #f8f9fa;
100+
border: 1px solid #e0e0e0;
101+
border-radius: 6px;
102+
padding: 1rem;
103+
margin-bottom: 1.5rem;
104+
}
105+
106+
.room-id-display label {
107+
display: block;
108+
margin-bottom: 0.5rem;
109+
color: #333;
110+
font-weight: 600;
111+
}
112+
113+
.room-id-box {
114+
display: flex;
115+
align-items: center;
116+
gap: 0.5rem;
117+
background-color: white;
118+
border: 1px solid #ddd;
119+
border-radius: 4px;
120+
padding: 0.75rem;
121+
margin-bottom: 0.5rem;
122+
}
123+
124+
.room-id-box code {
125+
flex: 1;
126+
font-family: 'Monaco', 'Courier New', monospace;
127+
font-size: 0.9rem;
128+
color: #6965db;
129+
font-weight: 600;
130+
user-select: all;
131+
}
132+
133+
.btn-copy {
134+
padding: 0.4rem 0.8rem;
135+
border: 1px solid #ddd;
136+
border-radius: 4px;
137+
background-color: white;
138+
color: #333;
139+
font-size: 0.85rem;
140+
cursor: pointer;
141+
white-space: nowrap;
142+
transition: all 0.2s;
143+
}
144+
145+
.btn-copy:hover {
146+
background-color: #f0f0f0;
147+
border-color: #6965db;
148+
}
149+
150+
.room-id-display small {
151+
color: #666;
152+
font-size: 0.85rem;
153+
}
154+

excalidraw-app/src/components/ConnectionDialog.tsx

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ import { getServerConfig, saveServerConfig, ServerConfig } from '../lib/api';
33
import './ConnectionDialog.css';
44

55
interface ConnectionDialogProps {
6-
onConnect: (config: ServerConfig) => void;
6+
onConnect: (config: ServerConfig, roomId?: string) => void;
77
onClose: () => void;
8+
currentRoomId?: string;
9+
isConnected?: boolean;
810
}
911

10-
export function ConnectionDialog({ onConnect, onClose }: ConnectionDialogProps) {
12+
export function ConnectionDialog({ onConnect, onClose, currentRoomId, isConnected }: ConnectionDialogProps) {
1113
const [serverUrl, setServerUrl] = useState(() => getServerConfig().url);
14+
const [roomId, setRoomId] = useState(currentRoomId || '');
1215
const [isConnecting, setIsConnecting] = useState(false);
16+
const [copied, setCopied] = useState(false);
1317

1418
const handleConnect = async () => {
1519
setIsConnecting(true);
@@ -19,7 +23,7 @@ export function ConnectionDialog({ onConnect, onClose }: ConnectionDialogProps)
1923
enabled: true,
2024
};
2125
saveServerConfig(config);
22-
onConnect(config);
26+
onConnect(config, roomId || undefined);
2327
} catch (error) {
2428
console.error('Failed to connect:', error);
2529
alert('Failed to connect to server');
@@ -37,12 +41,39 @@ export function ConnectionDialog({ onConnect, onClose }: ConnectionDialogProps)
3741
onConnect(config);
3842
};
3943

44+
const copyRoomId = async () => {
45+
if (currentRoomId) {
46+
await navigator.clipboard.writeText(currentRoomId);
47+
setCopied(true);
48+
setTimeout(() => setCopied(false), 2000);
49+
}
50+
};
51+
4052
return (
41-
<div className="connection-dialog-overlay">
42-
<div className="connection-dialog">
43-
<h2>Excalidraw</h2>
53+
<div className="connection-dialog-overlay" onClick={onClose}>
54+
<div className="connection-dialog" onClick={(e) => e.stopPropagation()}>
55+
<h2>Server Settings</h2>
4456
<p>Connect to a collaboration server or work offline</p>
4557

58+
{isConnected && currentRoomId && (
59+
<div className="room-info">
60+
<div className="room-id-display">
61+
<label>Current Room ID:</label>
62+
<div className="room-id-box">
63+
<code>{currentRoomId}</code>
64+
<button
65+
onClick={copyRoomId}
66+
className="btn-copy"
67+
title="Copy room ID"
68+
>
69+
{copied ? '✓ Copied!' : '📋 Copy'}
70+
</button>
71+
</div>
72+
<small>Share this ID with friends to collaborate</small>
73+
</div>
74+
</div>
75+
)}
76+
4677
<div className="input-group">
4778
<label htmlFor="server-url">Server URL:</label>
4879
<input
@@ -51,25 +82,59 @@ export function ConnectionDialog({ onConnect, onClose }: ConnectionDialogProps)
5182
value={serverUrl}
5283
onChange={(e) => setServerUrl(e.target.value)}
5384
placeholder="http://localhost:3002"
85+
disabled={isConnecting || isConnected}
86+
/>
87+
</div>
88+
89+
<div className="input-group">
90+
<label htmlFor="room-id">
91+
{isConnected ? 'Switch to Room ID:' : 'Room ID (leave blank for new room):'}
92+
</label>
93+
<input
94+
id="room-id"
95+
type="text"
96+
value={roomId}
97+
onChange={(e) => setRoomId(e.target.value)}
98+
placeholder={isConnected ? 'Enter room ID to switch' : 'Enter room ID or leave blank'}
5499
disabled={isConnecting}
55100
/>
56101
</div>
57102

58103
<div className="button-group">
59-
<button
60-
onClick={handleConnect}
61-
disabled={isConnecting || !serverUrl}
62-
className="btn-primary"
63-
>
64-
{isConnecting ? 'Connecting...' : 'Connect to Server'}
65-
</button>
66-
<button
67-
onClick={handleOffline}
68-
disabled={isConnecting}
69-
className="btn-secondary"
70-
>
71-
Work Offline
72-
</button>
104+
{!isConnected ? (
105+
<>
106+
<button
107+
onClick={handleConnect}
108+
disabled={isConnecting || !serverUrl}
109+
className="btn-primary"
110+
>
111+
{isConnecting ? 'Connecting...' : 'Connect to Server'}
112+
</button>
113+
<button
114+
onClick={handleOffline}
115+
disabled={isConnecting}
116+
className="btn-secondary"
117+
>
118+
Work Offline
119+
</button>
120+
</>
121+
) : (
122+
<>
123+
<button
124+
onClick={handleConnect}
125+
disabled={isConnecting || !roomId || roomId === currentRoomId}
126+
className="btn-primary"
127+
>
128+
Switch Room
129+
</button>
130+
<button
131+
onClick={onClose}
132+
className="btn-secondary"
133+
>
134+
Close
135+
</button>
136+
</>
137+
)}
73138
</div>
74139
</div>
75140
</div>

0 commit comments

Comments
 (0)