import { BaseBoxShapeUtil, TLBaseShape } from "tldraw" import { useEffect, useState, useRef } from "react" import { WORKER_URL } from "../routes/Board" import DailyIframe from "@daily-co/daily-js" interface Window { DailyIframe: { setLogLevel(level: "error" | "warn" | "info" | "debug"): void } } export type IVideoChatShape = TLBaseShape< "VideoChat", { w: number h: number roomUrl: string | null userName: string } > // Simplified component using Daily Prebuilt const VideoChatComponent = ({ roomUrl }: { roomUrl: string }) => { const wrapperRef = useRef(null) const callFrameRef = useRef | null>(null) useEffect(() => { if (!wrapperRef.current || !roomUrl) return // Create and configure the Daily call frame callFrameRef.current = DailyIframe.createFrame(wrapperRef.current, { iframeStyle: { width: "100%", height: "100%", border: "0", borderRadius: "4px", }, showLeaveButton: true, showFullscreenButton: true, }) // Join the room callFrameRef.current.join({ url: roomUrl }) // Cleanup return () => { if (callFrameRef.current) { callFrameRef.current.destroy() } } }, [roomUrl]) return (
) } export class VideoChatShape extends BaseBoxShapeUtil { static override type = "VideoChat" indicator(_shape: IVideoChatShape) { return null } getDefaultProps(): IVideoChatShape["props"] { return { roomUrl: null, w: 640, h: 480, userName: "", } } async ensureRoomExists(shape: IVideoChatShape) { if (shape.props.roomUrl !== null) { return } try { const roomName = `canvas-room-${shape.id.replace(/[^A-Za-z0-9-_]/g, "-")}` // Create room using Daily.co API directly const response = await fetch(`https://api.daily.co/v1/rooms`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${import.meta.env.VITE_DAILY_API_KEY}`, }, body: JSON.stringify({ name: roomName, privacy: "public", properties: { enable_chat: true, start_audio_off: true, start_video_off: true, enable_screenshare: true, enable_recording: "cloud", max_participants: 8, enable_network_ui: true, enable_prejoin_ui: true, enable_people_ui: true, enable_pip_ui: true, enable_emoji_reactions: true, enable_hand_raising: true, enable_noise_cancellation_ui: true, }, }), }) if (!response.ok) { const errorData = (await response.json()) as { message: string } // If room already exists, construct the URL using the room name if (errorData.message.includes("already exists")) { const url = `https://${import.meta.env.VITE_DAILY_DOMAIN}/${roomName}` this.editor.updateShape({ id: shape.id, type: "VideoChat", props: { ...shape.props, roomUrl: url, }, }) return } throw new Error(errorData.message || "Failed to create room") } const { url } = (await response.json()) as { url: string } // Update the shape with the room URL this.editor.updateShape({ id: shape.id, type: "VideoChat", props: { ...shape.props, roomUrl: url, }, }) } catch (error) { console.error("Failed to create Daily room:", error) throw error } } component(shape: IVideoChatShape) { const [isInRoom, setIsInRoom] = useState(false) const [error, setError] = useState("") const [isLoading, setIsLoading] = useState(false) useEffect(() => { setIsLoading(true) this.ensureRoomExists(shape) .catch((err) => setError(err.message)) .finally(() => setIsLoading(false)) }, []) // useEffect(() => { // // Disable Daily.co debug logs // if (window.DailyIframe) { // window.DailyIframe.setLogLevel("error") // } // }, []) if (isLoading) { return (
Initializing video chat...
) } if (!shape.props.roomUrl) { return (
Creating room...
) } return (
{!isInRoom ? ( ) : ( )} {error && (
{error}
)}
) } }