video chat attempt
This commit is contained in:
parent
836d37df76
commit
8ce8dec8f7
|
|
@ -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<Set<string>>) => {
|
||||
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<string>()); // 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<HTMLInputElement>) => {
|
||||
setUserName(event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ position: 'fixed', inset: 0 }}>
|
||||
<Tldraw
|
||||
// we can pass the connected store into the Tldraw component which will handle
|
||||
// loading states & enable multiplayer UX like cursors & a presence menu
|
||||
store={store}
|
||||
shapeUtils={shapeUtils}
|
||||
tools={tools}
|
||||
onMount={(editor) => {
|
||||
// when the editor is ready, we need to register out bookmark unfurling service
|
||||
editor.registerExternalAssetHandler('url', unfurlBookmarkUrl)
|
||||
editor.createShape<IChatBoxShape>({
|
||||
type: 'chatBox',
|
||||
x: 0,
|
||||
y: 0,
|
||||
props: {
|
||||
w: 200,
|
||||
h: 200,
|
||||
roomId: roomId,
|
||||
},
|
||||
})
|
||||
editor.createShape<IChatBoxShape>({
|
||||
type: 'chatBox',
|
||||
x: 0,
|
||||
y: 0,
|
||||
props: {
|
||||
w: 200,
|
||||
h: 200,
|
||||
roomId: roomId,
|
||||
},
|
||||
});
|
||||
//if (isVideoChatVisible) {
|
||||
//editor.registerExternalAssetHandler('url', unfurlBookmarkUrl)
|
||||
editor.createShape<IVideoChatShape>({
|
||||
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 && (
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={userName}
|
||||
onChange={handleNameChange}
|
||||
placeholder="Enter your name"
|
||||
/>
|
||||
<ChatBox
|
||||
userName={userName}
|
||||
roomId={roomId} // Added roomId
|
||||
width={200} // Set appropriate width
|
||||
height={200} // Set appropriate height
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isVideoChatVisible && ( // Render the button to join video chat
|
||||
<button onClick={() => setVideoChatVisible(false)} className="bg-green-500 text-white px-4 py-2 rounded">
|
||||
Join Video Call
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<IChatBoxShape> {
|
|||
roomId: 'default-room',
|
||||
w: 100,
|
||||
h: 100,
|
||||
userName: '',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +29,7 @@ export class ChatBoxShape extends BaseBoxShapeUtil<IChatBoxShape> {
|
|||
|
||||
component(shape: IChatBoxShape) {
|
||||
return (
|
||||
<ChatBox roomId={shape.props.roomId} width={shape.props.w} height={shape.props.h} />
|
||||
<ChatBox roomId={shape.props.roomId} width={shape.props.w} height={shape.props.h} userName="" />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ChatBoxProps> = ({ roomId, width, height, userName }) => {
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [inputMessage, setInputMessage] = useState("");
|
||||
const [username, setUsername] = useState("jeff");
|
||||
const [username, setUsername] = useState(userName);
|
||||
const messagesEndRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -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 <IVideoChatShape> {
|
||||
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 <rect x={0} y={0} width={shape.props.w} height={shape.props.h} />;
|
||||
}
|
||||
|
||||
component(shape: IVideoChatShape) {
|
||||
return <VideoChat roomUrl={shape.props.roomUrl} />;
|
||||
}
|
||||
}
|
||||
|
||||
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<VideoChatProps> = () => { // 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 (
|
||||
<div>
|
||||
{!isInRoom && ( // Show button if not in room
|
||||
<button onClick={() => setIsInRoom(true)} className="bg-green-500 text-white px-4 py-2 rounded">
|
||||
Join Video Chat
|
||||
</button>
|
||||
)}
|
||||
{/* Render Video Chat UI here when isInRoom is true */}
|
||||
{isInRoom && <div>Video Chat UI goes here</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { BaseBoxShapeTool } from "tldraw";
|
||||
|
||||
export class ChatBoxTool extends BaseBoxShapeTool {
|
||||
shapeType = 'chatBox'
|
||||
override initial = 'idle'
|
||||
shapeType = 'chatBox';
|
||||
override initial = 'idle';
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 },
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue