import { BaseBoxShapeUtil, TLBaseShape } from "tldraw" import { useCallback, useState } from "react" import * as React from "react" export type ISharedPianoShape = TLBaseShape< "SharedPiano", { w: number h: number isMinimized?: boolean interactionState?: { scrollPosition?: { x: number; y: number } } } > const getDefaultDimensions = (): { w: number; h: number } => { // Default dimensions for the Shared Piano (16:9 ratio) return { w: 800, h: 600 } } export class SharedPianoShape extends BaseBoxShapeUtil { static override type = "SharedPiano" getDefaultProps(): ISharedPianoShape["props"] { const { w, h } = getDefaultDimensions() return { w, h, isMinimized: false, } } indicator(shape: ISharedPianoShape) { return ( ) } component(shape: ISharedPianoShape) { // Guard against undefined shape or props if (!shape || !shape.props) { return null } const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) // Suppress Chrome Music Lab console errors React.useEffect(() => { const originalError = console.error const originalWarn = console.warn // Filter out errors from Chrome Music Lab const errorHandler = (message: any, ...args: any[]) => { const messageStr = String(message) if (messageStr.includes('musiclab.chromeexperiments.com') || messageStr.includes('Uncaught (in promise) false')) { // Suppress these errors silently return } originalError(message, ...args) } const warnHandler = (message: any, ...args: any[]) => { const messageStr = String(message) if (messageStr.includes('musiclab.chromeexperiments.com')) { // Suppress these warnings silently return } originalWarn(message, ...args) } // Override console methods console.error = errorHandler console.warn = warnHandler // Also catch unhandled promise rejections from the iframe const unhandledRejectionHandler = (event: PromiseRejectionEvent) => { const reason = event.reason if (reason === false || (typeof reason === 'string' && reason.includes('musiclab.chromeexperiments.com'))) { event.preventDefault() return } } window.addEventListener('unhandledrejection', unhandledRejectionHandler) return () => { // Restore original console methods console.error = originalError console.warn = originalWarn window.removeEventListener('unhandledrejection', unhandledRejectionHandler) } }, []) const handleIframeLoad = useCallback(() => { setIsLoading(false) setError(null) }, []) const handleIframeError = useCallback(() => { setIsLoading(false) setError("Failed to load Shared Piano. Please check your browser permissions for MIDI and audio access.") }, []) const handleToggleMinimize = (e: React.MouseEvent) => { e.stopPropagation() if (!shape.props) return this.editor.updateShape({ id: shape.id, type: "SharedPiano", props: { ...shape.props, isMinimized: !shape.props.isMinimized, }, }) } const isMinimized = shape.props?.isMinimized ?? false const controls = (
) const sharedPianoUrl = "https://musiclab.chromeexperiments.com/Shared-Piano/#jQB715bFJ" return (
{controls} {isMinimized ? (
🎹 Shared Piano
) : (
{isLoading && (
🎹
Loading Shared Piano...
)} {error && (
{error}
)}