Skip to content

Instantly share code, notes, and snippets.

@zinedkaloc
Last active March 20, 2024 11:04
Show Gist options
  • Save zinedkaloc/61074ef5d6546dc74a85798021f51223 to your computer and use it in GitHub Desktop.
Save zinedkaloc/61074ef5d6546dc74a85798021f51223 to your computer and use it in GitHub Desktop.
Real-Time Collaborative Drawing App with React and Agnost
import React, { useState, useEffect } from "react";
import { createClient } from "@agnost/client";
import styled from "styled-components";
import * as fal from "@fal-ai/serverless-client";
fal.config({
// Can also be auto-configured using environment variables:
// Either a single FAL_KEY or a combination of FAL_KEY_ID and FAL_KEY_SECRET
credentials:
"181f3044-5f0b.......65775f5bbcceb424",
});
const seed = Math.floor(Math.random() * 100000);
const baseArgs = {
sync_mode: true,
strength: 0.99,
seed,
};
const Container = styled.div`
max-width: 800px;
margin: 0 auto;
padding: 20px;
display: flex;
justify-content: space-between;
`;
const CanvasContainer = styled.div`
position: relative;
`;
const Canvas = styled.canvas`
border: 1px solid #ccc;
cursor: crosshair;
`;
const ColorPalette = styled.div`
display: flex;
margin-bottom: 20px;
`;
const ColorButton = styled.button`
width: 30px;
height: 30px;
border: none;
margin-right: 5px;
cursor: pointer;
background-color: ${({ color }) => color};
`;
const BrushSizeSlider = styled.input`
width: 100px;
margin-right: 10px;
`;
const SaveButton = styled.button`
padding: 10px;
background-color: #007bff;
color: #fff;
border: none;
cursor: pointer;
`;
const Form = styled.form`
margin-bottom: 20px;
`;
const LoadButton = styled.button`
padding: 10px;
background-color: #28a745;
color: #fff;
border: none;
cursor: pointer;
`;
const UserList = styled.ul`
list-style: none;
padding: 0;
`;
const UserListItem = styled.li`
margin-bottom: 5px;
`;
const baseUrl = "https://cloudflex.app/env-p3....k7g";
const clientKey = "ak-xp0hbz.....oipc9x3b5as7";
let options = {
signInRedirect: "http://localhost:3000/auth-redirect",
realtime: {
autoJoinChannels: false,
bufferMessages: true,
echoMessages: true,
reconnectionDelay: 2000,
timeout: 30000,
},
};
const agnost = createClient(baseUrl, clientKey, options);
function App() {
const [userName, setUserName] = useState("");
const [isDrawing, setIsDrawing] = useState(false);
const [prevPosition, setPrevPosition] = useState(null);
const [cursorPosition, setCursorPosition] = useState({ x: 0, y: 0 });
const [color, setColor] = useState("#000000");
const [brushSize, setBrushSize] = useState(2);
const [users, setUsers] = useState([]);
const [joinedRoom, setJoinedRoom] = useState(false);
const [generatedImage, setGeneratedImage] = useState(null); // State to hold the generated image
useEffect(() => {
// Open connection
agnost.realtime.open();
// Listen for drawing events from other users
agnost.realtime.on("draw", handleDrawingEvent);
// Listen for user join and leave events
agnost.realtime.onJoin(handleUserJoin);
agnost.realtime.onLeave(handleUserLeave);
// Cleanup function
return () => {
agnost.realtime.close();
agnost.realtime.off("draw", handleDrawingEvent);
agnost.realtime.offAny(handleUserJoin);
agnost.realtime.onLeave(handleUserLeave);
};
}, []);
const handleUserJoin = async () => {
const membersData = await agnost.realtime.getMembers("drawing-room");
const memberIds = membersData.data.map((member) => member.data.username);
console.log("Members:", membersData.data);
setUsers(memberIds);
};
const handleUserLeave = async () => {
const membersData = await agnost.realtime.getMembers("drawing-room");
const memberIds = membersData.data.map((member) => member.id);
console.log("Members:", membersData.data);
setUsers(memberIds);
};
const handleDrawingEvent = (payload) => {
if (
payload.message &&
payload.message.position &&
payload.message.prevPosition
) {
const { position, prevPosition, color, brushSize } = payload.message;
const { x, y } = position;
const { x: prevX, y: prevY } = prevPosition;
drawLine(prevX, prevY, x, y, color, brushSize);
}
};
const startDrawing = (event) => {
const canvas = event.target;
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
setIsDrawing(true);
setPrevPosition({ x, y });
};
const endDrawing = async () => {
setIsDrawing(false);
setPrevPosition(null);
// Generate image when the drawing ends
await handleGenerateImage();
};
const draw = (event) => {
event.preventDefault(); // Prevent default browser actions
if (!isDrawing) return;
const canvas = event.target;
const rect = canvas.getBoundingClientRect();
const x = event.pageX - rect.left;
const y = event.pageY - rect.top;
const { x: prevX, y: prevY } = prevPosition;
drawLine(prevX, prevY, x, y, color, brushSize);
setPrevPosition({ x, y });
// Broadcast the drawing event to other users
agnost.realtime.broadcast("draw", {
position: { x, y },
prevPosition,
color,
brushSize,
});
};
const handleMouseMove = (event) => {
const canvas = event.target;
const rect = canvas.getBoundingClientRect();
const x = event.pageX - rect.left;
const y = event.pageY - rect.top;
setCursorPosition({ x, y });
};
const drawLine = (prevX, prevY, x, y, color, brushSize) => {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(prevX, prevY);
ctx.lineTo(x, y);
ctx.strokeStyle = color;
ctx.lineWidth = brushSize;
ctx.lineCap = "round";
ctx.stroke();
};
const handleNameChange = (event) => {
setUserName(event.target.value);
};
const handleJoinRoom = async (event) => {
event.preventDefault();
// Join the room with the given user name
agnost.realtime.join("drawing-room");
await agnost.realtime.updateProfile({ username: userName });
setJoinedRoom(true); // Set joinedRoom to true after joining the room
};
const handleLeaveRoom = async () => {
// Leave the room
await agnost.realtime.leave("drawing-room");
setJoinedRoom(false); // Set joinedRoom to false after leaving the room
};
const handleColorChange = (color) => {
setColor(color);
};
const handleBrushSizeChange = (event) => {
setBrushSize(parseInt(event.target.value));
};
const handleSaveDrawing = () => {
const canvas = document.getElementById("canvas");
const dataURL = canvas.toDataURL("image/png");
// Implement saving the drawing dataURL to the server or local storage
console.log("Drawing saved:", dataURL);
};
const handleLoadDrawing = () => {
// Implement loading a saved drawing from the server or local storage
console.log("Loading drawing...");
};
const handleGenerateImage = async () => {
// Extract drawing data from canvas
const canvas = document.getElementById("canvas");
const dataURL = canvas.toDataURL("image/png");
// Communicate with Falcon AI to generate the image
const response = await fal.realtime.connect(
"110602490-sdxl-turbo-realtime",
{
connectionKey: "realtime-nextjs-app",
onResult: (result) => {
if (result.error) {
console.error("Error generating image:", result.error);
} else {
setGeneratedImage(result.images[0].url);
}
},
}
);
// Send the drawing data to Falcon AI for image generation
response.send({
image_url: dataURL, // Sending the drawing data as prompt for Falcon AI
...baseArgs,
prompt: "A drawing generated by a user",
// You can include additional parameters here as needed
});
};
return (
<Container>
<div>
<h2>Real-Time Drawing</h2>
{/* Conditionally render based on whether the user has joined the room or not */}
{!joinedRoom && (
<Form onSubmit={handleJoinRoom}>
<label>Enter your name:</label>
<input type="text" value={userName} onChange={handleNameChange} />
<button type="submit">Join Room</button>
</Form>
)}
{/* Conditionally render based on whether the user has joined the room or not */}
{joinedRoom && (
<Form onSubmit={handleLeaveRoom}>
<button type="submit">Leave Room</button>
</Form>
)}
<ColorPalette>
<ColorButton
color="#000000"
onClick={() => handleColorChange("#000000")}
/>
<ColorButton
color="#ff0000"
onClick={() => handleColorChange("#ff0000")}
/>
<ColorButton
color="#00ff00"
onClick={() => handleColorChange("#00ff00")}
/>
<ColorButton
color="#0000ff"
onClick={() => handleColorChange("#0000ff")}
/>
</ColorPalette>
<BrushSizeSlider
type="range"
min="1"
max="20"
value={brushSize}
onChange={handleBrushSizeChange}
/>
<SaveButton onClick={handleSaveDrawing}>Save Drawing</SaveButton>
<LoadButton onClick={handleLoadDrawing}>Load Drawing</LoadButton>
<UserList>
{users.map((user, index) => (
<UserListItem key={index}>{user}</UserListItem>
))}
</UserList>
</div>
<CanvasContainer>
<Canvas
id="canvas"
width={800}
height={600}
onMouseDown={startDrawing}
onMouseUp={endDrawing}
onMouseOut={endDrawing}
onMouseMove={(event) => {
draw(event);
}}
/>
</CanvasContainer>
{/* Display the generated image if available */}
{generatedImage && (
<div>
<h2>Generated Image</h2>
<img
src={generatedImage}
width={400}
height={400}
alt="Generated Image"
/>
</div>
)}
</Container>
);
}
export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment