'use client'; import React, { useEffect, useRef } from 'react'; interface WhiteboardIframeProps { boardId: string; username?: string; token?: string; /** Только ментор может очищать холст. */ isMentor?: boolean; onToggleView?: () => void; showingBoard?: boolean; className?: string; style?: React.CSSProperties; } /** * Интерактивная доска Excalidraw + Yjs. * Iframe создаётся императивно один раз — React не пересоздаёт его при переключении. */ export function WhiteboardIframe({ boardId, username = 'Пользователь', token: tokenProp, isMentor = false, showingBoard = true, className = '', style = {}, }: WhiteboardIframeProps) { const containerRef = useRef(null); const iframeRef = useRef(null); const createdRef = useRef(false); useEffect(() => { if (typeof window === 'undefined' || !containerRef.current || !boardId) return; if (createdRef.current) return; const token = tokenProp ?? localStorage.getItem('access_token') ?? ''; const apiBase = process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:8123/api'; const apiUrl = apiBase.replace(/\/api\/?$/, '') || 'http://127.0.0.1:8123'; const excalidrawHost = `${window.location.protocol}//${window.location.hostname}:3001`; const url = new URL('/', excalidrawHost); url.searchParams.set('boardId', boardId); url.searchParams.set('apiUrl', apiUrl); if (token) url.searchParams.set('token', token); if (isMentor) url.searchParams.set('isMentor', '1'); const iframe = document.createElement('iframe'); iframe.src = url.toString(); iframe.style.cssText = 'width:100%;height:100%;border:none;display:block'; iframe.title = 'Интерактивная доска (Excalidraw)'; iframe.setAttribute('allow', 'camera; microphone; fullscreen'); iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-forms allow-popups allow-modals'); const decodeName = (raw: string): string => { if (!raw || typeof raw !== 'string') return raw; try { if (/%[0-9A-Fa-f]{2}/.test(raw)) return decodeURIComponent(raw); return raw; } catch { return raw; } }; const sendUsername = () => { if (iframe.contentWindow) { try { iframe.contentWindow.postMessage( { type: 'excalidraw-username', username: decodeName(username) }, url.origin ); } catch (_) {} } }; iframe.onload = () => { sendUsername(); setTimeout(sendUsername, 500); }; containerRef.current.appendChild(iframe); iframeRef.current = iframe; createdRef.current = true; setTimeout(sendUsername, 300); return () => { createdRef.current = false; iframeRef.current = null; iframe.remove(); }; }, [boardId, tokenProp, isMentor]); useEffect(() => { if (!iframeRef.current?.contentWindow || !createdRef.current) return; const token = tokenProp ?? localStorage.getItem('access_token') ?? ''; const apiBase = process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:8123/api'; const apiUrl = apiBase.replace(/\/api\/?$/, '') || 'http://127.0.0.1:8123'; const excalidrawHost = `${window.location.protocol}//${window.location.hostname}:3001`; const url = new URL('/', excalidrawHost); const decodeName = (raw: string): string => { if (!raw || typeof raw !== 'string') return raw; try { if (/%[0-9A-Fa-f]{2}/.test(raw)) return decodeURIComponent(raw); return raw; } catch { return raw; } }; iframeRef.current.contentWindow.postMessage( { type: 'excalidraw-username', username: decodeName(username) }, url.origin ); }, [username]); return (
); }