diff --git a/src/components/Board.tsx b/src/components/Board.tsx index 6d28e36..b6d3574 100644 --- a/src/components/Board.tsx +++ b/src/components/Board.tsx @@ -10,55 +10,146 @@ import { import { useParams } from 'react-router-dom' // Add this import import { ChatBoxTool } from '@/tools/ChatBoxTool' import { IChatBoxShape, ChatBoxShape } from '@/shapes/ChatBoxShape' +import { VideoChatTool } from '@/tools/VideoChatTool' +import { IVideoChatShape, VideoChatShape } from '@/shapes/VideoChatShape' import { multiplayerAssetStore } from '../client/multiplayerAssetStore' // Adjusted path if necessary import { customSchema } from '../../worker/TldrawDurableObject' import './ChatBoxStyles.css' // Add a CSS file for styles +import React, { useState, useEffect, useRef } from 'react'; // Ensure useRef is imported +import { ChatBox } from '@/shapes/ChatBoxShape'; // Add this import + import { VideoChat } from '@/shapes/VideoChatShape'; + const WORKER_URL = `https://jeffemmett-canvas.jeffemmett.workers.dev` -const shapeUtils = [ChatBoxShape] -const tools = [ChatBoxTool] +const shapeUtils = [ChatBoxShape, VideoChatShape] +const tools = [ChatBoxTool, VideoChatTool]; // Array of tools + +// Function to register tools +const registerTools = (store: any, registeredToolsRef: React.MutableRefObject>) => { + const typedStore = store as unknown as { + registerTool: (tool: any) => void; + hasTool: (id: string) => boolean; + }; + + tools.forEach(tool => { + if (!registeredToolsRef.current.has(tool.id) && typedStore.hasTool && !typedStore.hasTool(tool.id)) { + typedStore.registerTool(tool); // Register the tool + registeredToolsRef.current.add(tool.id); // Mark this tool as registered + } + }); +}; + export function Board() { - // Extract the slug from the URL - const { slug } = useParams<{ slug: string }>() - - // Use the slug as the roomId, or fallback to 'default-room' if not provided - const roomId = slug || 'default-room' + const { slug } = useParams<{ slug: string }>(); // Ensure this is inside the Board component + const roomId = slug || 'default-room'; // Declare roomId here - // Create a store connected to multiplayer. const store = useSync({ - // Use the dynamic roomId in the URI uri: `${WORKER_URL}/connect/${roomId}`, - // ...and how to handle static assets like images & videos assets: multiplayerAssetStore, - shapeUtils: shapeUtils, - schema: customSchema - }) + shapeUtils: shapeUtils, + schema: customSchema + }); + + const [isChatBoxVisible, setChatBoxVisible] = useState(false); + const [userName, setUserName] = useState(''); + const [isVideoChatVisible, setVideoChatVisible] = useState(false); // Added state for video chat visibility + const registeredToolsRef = useRef(new Set()); // Ref to track registered tools + + // Call the function to register tools only once + useEffect(() => { + registerTools(store, registeredToolsRef); + }, [store]); // Ensure this effect runs when the store changes + +// const videoChatTool = { +// id: 'videoChatTool', // Ensure this ID is unique +// // ... other properties of the tool +// }; + + //const typedStore = store as unknown as { + // registerTool: (tool: any) => void; + // hasTool: (id: string) => boolean; // Ensure hasTool is included + //}; + + //if (typedStore.hasTool && !typedStore.hasTool(videoChatTool.id)) { // Check if the tool ID is unique + // typedStore.registerTool(videoChatTool); // Register the video chat tool + //} else { + // console.error(`Tool with id "${videoChatTool.id}" is already registered. Cannot register again.`); // Log an error + //} + +// useEffect(() => { +// tools.forEach(tool => { +// const typedStore = store as unknown as { +// registerTool: (tool: any) => void; +// hasTool: (id: string) => boolean; // Ensure hasTool is included +// }; +// if (typedStore.hasTool && !typedStore.hasTool(tool.id)) { // Check if hasTool exists +// typedStore.registerTool(tool); // Use the typed store +// } else { +// console.warn(`Tool with id "${tool.id}" is already registered. Cannot register again.`); // Log an error +// } +// }); +// }, [store]); // Run this effect when the store changes + + const handleNameChange = (event: React.ChangeEvent) => { + setUserName(event.target.value); + }; return (
{ - // when the editor is ready, we need to register out bookmark unfurling service editor.registerExternalAssetHandler('url', unfurlBookmarkUrl) - editor.createShape({ - type: 'chatBox', - x: 0, - y: 0, - props: { - w: 200, - h: 200, - roomId: roomId, - }, - }) + editor.createShape({ + type: 'chatBox', + x: 0, + y: 0, + props: { + w: 200, + h: 200, + roomId: roomId, + }, + }); + //if (isVideoChatVisible) { + //editor.registerExternalAssetHandler('url', unfurlBookmarkUrl) + editor.createShape({ + type: 'videoChat', + x: 300, // Adjust position as needed + y: 0, + props: { + roomUrl: 'https://whereby.com/default-room', // Default room URL + w: 640, + h: 480, + }, + }); + //} }} /> + {isChatBoxVisible && ( +
+ + +
+ )} + {isVideoChatVisible && ( // Render the button to join video chat + + )}
) } diff --git a/src/physics/simulation.ts b/src/physics/simulation.ts index b67a8e5..f657648 100644 --- a/src/physics/simulation.ts +++ b/src/physics/simulation.ts @@ -66,6 +66,9 @@ export class PhysicsWorld { this.createGroup(shape as TLGroupShape); break; // Add cases for any new shape types here + case "videoChat": + this.createShape (shape as TLGeoShape); + break; } } } diff --git a/src/shapes/ChatBoxShape.tsx b/src/shapes/ChatBoxShape.tsx index b1b80fa..99a47fe 100644 --- a/src/shapes/ChatBoxShape.tsx +++ b/src/shapes/ChatBoxShape.tsx @@ -7,6 +7,7 @@ export type IChatBoxShape = TLBaseShape< w: number h: number roomId: string + userName: string } > @@ -18,6 +19,7 @@ export class ChatBoxShape extends BaseBoxShapeUtil { roomId: 'default-room', w: 100, h: 100, + userName: '', } } @@ -27,7 +29,7 @@ export class ChatBoxShape extends BaseBoxShapeUtil { component(shape: IChatBoxShape) { return ( - + ) } } @@ -39,11 +41,18 @@ interface Message { timestamp: Date; } -// Add this new component after the ChatBoxShape class -function ChatBox({ roomId, width, height }: { roomId: string, width: number, height: number }) { +interface ChatBoxProps { + roomId: string; + width: number; + height: number; + userName: string; // Add this line +} + +// Update the ChatBox component to accept userName +export const ChatBox: React.FC = ({ roomId, width, height, userName }) => { const [messages, setMessages] = useState([]); const [inputMessage, setInputMessage] = useState(""); - const [username, setUsername] = useState("jeff"); + const [username, setUsername] = useState(userName); const messagesEndRef = useRef(null); useEffect(() => { diff --git a/src/shapes/VideoChatShape.tsx b/src/shapes/VideoChatShape.tsx new file mode 100644 index 0000000..5062981 --- /dev/null +++ b/src/shapes/VideoChatShape.tsx @@ -0,0 +1,93 @@ +import { BaseBoxShapeUtil, TLBaseShape } from "tldraw"; +import React, { useEffect, useState } from "react"; // Updated import for React + +export type IVideoChatShape = TLBaseShape< + 'videoChat', + { + w: number; + h: number; + roomUrl: string; // Changed from roomId to roomUrl for Whereby + userName: string; + } +>; + +export class VideoChatShape extends BaseBoxShapeUtil { + static override type = 'videoChat'; + + getDefaultProps(): IVideoChatShape['props'] { + return { + roomUrl: 'https://whereby.com/default-room', // Default Whereby room URL + w: 640, + h: 480, + userName: '' + }; + } + + indicator(shape: IVideoChatShape) { + return ; + } + + component(shape: IVideoChatShape) { + return ; + } +} + +interface VideoChatProps { + roomUrl: string; + // Remove width and height as they are not used + // width: number; + // height: number; + // Remove userName as it is not used + // userName: string; +} + +// VideoChat component using Whereby +export const VideoChat: React.FC = () => { // Removed roomUrl from props + // Remove unused destructured props + // const [roomUrl, setRoomUrl] = useState(initialRoomUrl); // Use initialRoomUrl to avoid duplicate identifier + // const [roomUrl, setRoomUrl] = useState(roomId); // Use roomId instead + const [isInRoom, setIsInRoom] = useState(false); + const [error, setError] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + // Automatically show the button on load + useEffect(() => { + joinRoom(); + }, []); + + const joinRoom = async () => { + setError(""); + setIsLoading(true); + try { + const response = await fetch('/api/get-or-create-room', { method: 'GET' }); + const data: { roomUrl?: string; error?: string } = await response.json(); // Explicitly type 'data' + if (data.roomUrl) { + // setRoomUrl(data.roomUrl); // Remove this line + setIsInRoom(true); + } else { + setError(data.error || "Failed to join room. Please try again."); + } + } catch (e) { + console.error("Error joining room:", e); + setError("An error occurred. Please try again."); + } + setIsLoading(false); + }; + + const leaveRoom = () => { + setIsInRoom(false); + // setRoomUrl(""); // Remove this line + }; + + return ( +
+ {!isInRoom && ( // Show button if not in room + + )} + {/* Render Video Chat UI here when isInRoom is true */} + {isInRoom &&
Video Chat UI goes here
} +
+ ); +} \ No newline at end of file diff --git a/src/tools/ChatBoxTool.ts b/src/tools/ChatBoxTool.ts index ce64403..6dd5e40 100644 --- a/src/tools/ChatBoxTool.ts +++ b/src/tools/ChatBoxTool.ts @@ -1,6 +1,6 @@ import { BaseBoxShapeTool } from "tldraw"; export class ChatBoxTool extends BaseBoxShapeTool { - shapeType = 'chatBox' - override initial = 'idle' + shapeType = 'chatBox'; + override initial = 'idle'; } \ No newline at end of file diff --git a/src/tools/VideoChatTool.ts b/src/tools/VideoChatTool.ts new file mode 100644 index 0000000..c3dde15 --- /dev/null +++ b/src/tools/VideoChatTool.ts @@ -0,0 +1,8 @@ +import { BaseBoxShapeTool } from "tldraw"; + +export class VideoChatTool extends BaseBoxShapeTool { + shapeType = 'videoChat'; + override initial = 'idle'; + + // Additional methods for handling video chat functionality can be added here +} \ No newline at end of file diff --git a/worker/TldrawDurableObject.ts b/worker/TldrawDurableObject.ts index f878c83..ed42779 100644 --- a/worker/TldrawDurableObject.ts +++ b/worker/TldrawDurableObject.ts @@ -11,10 +11,11 @@ import { AutoRouter, IRequest, error } from 'itty-router' import throttle from 'lodash.throttle' import { Environment } from './types' import { ChatBoxShape } from '@/shapes/ChatBoxShape' +import { VideoChatShape } from '@/shapes/VideoChatShape' // add custom shapes and bindings here if needed: export const customSchema = createTLSchema({ - shapes: { ...defaultShapeSchemas, chatBox: ChatBoxShape }, + shapes: { ...defaultShapeSchemas, chatBox: ChatBoxShape, videoChat: VideoChatShape }, // bindings: { ...defaultBindingSchemas }, })