import { BaseBoxShapeUtil, TLBaseShape } from "tldraw"; import { useCallback, useState } from "react"; export type IEmbedShape = TLBaseShape< 'Embed', { w: number; h: number; url: string | null; } >; export class EmbedShape extends BaseBoxShapeUtil { static override type = 'Embed'; getDefaultProps(): IEmbedShape['props'] { return { url: null, w: 640, h: 480, }; } indicator(shape: IEmbedShape) { return ( ); } component(shape: IEmbedShape) { const [inputUrl, setInputUrl] = useState(shape.props.url || ''); const [error, setError] = useState(''); const handleSubmit = useCallback((e: React.FormEvent) => { e.preventDefault(); let completedUrl = inputUrl.startsWith('http://') || inputUrl.startsWith('https://') ? inputUrl : `https://${inputUrl}`; // Handle YouTube links if (completedUrl.includes('youtube.com') || completedUrl.includes('youtu.be')) { const videoId = extractYouTubeVideoId(completedUrl); if (videoId) { completedUrl = `https://www.youtube.com/embed/${videoId}`; } else { setError('Invalid YouTube URL'); return; } } // Handle Google Docs links if (completedUrl.includes('docs.google.com')) { const docId = completedUrl.match(/\/d\/([a-zA-Z0-9-_]+)/)?.[1]; if (docId) { completedUrl = `https://docs.google.com/document/d/${docId}/preview`; } else { setError('Invalid Google Docs URL'); return; } } this.editor.updateShape({ id: shape.id, type: 'Embed', props: { ...shape.props, url: completedUrl } }); // Check if the URL is valid const isValidUrl = completedUrl.match(/(^\w+:|^)\/\//); if (!isValidUrl) { setError('Invalid website URL'); } else { setError(''); } }, [inputUrl]); const extractYouTubeVideoId = (url: string): string | null => { const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/; const match = url.match(regExp); return (match && match[2].length === 11) ? match[2] : null; }; const wrapperStyle = { width: `${shape.props.w}px`, height: `${shape.props.h}px`, padding: '15px', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)', backgroundColor: '#F0F0F0', borderRadius: '4px', }; const contentStyle = { pointerEvents: 'all' as const, width: '100%', height: '100%', border: '1px solid #D3D3D3', backgroundColor: '#FFFFFF', display: 'flex', justifyContent: 'center', alignItems: 'center', overflow: 'hidden', }; if (!shape.props.url) { return (
document.querySelector('input')?.focus()}>
setInputUrl(e.target.value)} placeholder="Enter URL" style={{ width: '100%', height: '100%', border: 'none', padding: '10px' }} onKeyDown={(e) => { if (e.key === 'Enter') { handleSubmit(e); } }} /> {error &&
{error}
}
); } return (