uchill/front_material/components/board/WhiteboardIframe.tsx

126 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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<HTMLDivElement>(null);
const iframeRef = useRef<HTMLIFrameElement | null>(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 (
<div
ref={containerRef}
className={className}
style={{ width: '100%', height: '100%', position: 'relative', ...style }}
/>
);
}