import { useMemo, useRef, useState } from "react"; import { ReactPhotoSphereViewer } from "react-photo-sphere-viewer"; import { ReactMarkdownComponent } from "../../ReactMarkdown"; import { ThreeViewErrorBoundary } from "../../MediaViewer/ThreeViewErrorBoundary"; import { SightFrameThreeView, ThreeViewHandle, } from "./SightFrameThreeView"; import { MinusIcon, PlusIcon } from "./SightFrameThreeViewIcons"; import "./SightFramePreview.css"; interface ArticleMedia { id: string; media_type: number; filename: string; } interface Article { id: number; heading: string; body: string; media: ArticleMedia[]; } interface PreviewMediaData { id: string; media_type: number; filename?: string; } interface SightFramePreviewProps { sightName: string; previewMedia: PreviewMediaData | null; articles: Article[]; onArticleSelect: (index: number) => void; previewFontSize?: number; } // Matches SightFrame.jsx renderCurrentMedia — same structure, same CSS classes function renderCurrentMedia( media: PreviewMediaData | ArticleMedia | null, token: string, threeViewResetKey: number, threeViewControlRef: React.MutableRefObject, onZoomIn: () => void, onZoomOut: () => void, onThreeViewReset: () => void ) { if (!media) return null; const src = `${import.meta.env.VITE_KRBL_MEDIA}${media.id}/download?token=${token}`; const className = "sfp-sight-frame-media-item"; switch (media.media_type) { // image (1), gif (3), sprite (4) case 1: case 3: case 4: return ( { (e.target as HTMLImageElement).style.display = "none"; }} /> ); // video case 2: return ( ); // panorama case 5: return (
); // 3d case 6: return (
); default: return (
Неподдерживаемый тип медиа
); } } export function SightFramePreview({ sightName, previewMedia, articles, onArticleSelect, previewFontSize, }: SightFramePreviewProps) { const token = localStorage.getItem("token") ?? ""; // -1 = intro (section 0 in SightFrame) const [selectedSection, setSelectedSection] = useState(-1); const [threeViewResetKey, setThreeViewResetKey] = useState(0); const threeViewControlRef = useRef(null); const currentArticle = selectedSection >= 0 && selectedSection < articles.length ? articles[selectedSection] : null; const currentMedia: PreviewMediaData | ArticleMedia | null = currentArticle ? (currentArticle.media[0] ?? null) : previewMedia; const isCurrentMedia3D = currentMedia?.media_type === 6; // Replicates processedSightName from SightFrame.jsx const processedSightName = useMemo(() => { if (!sightName) return sightName; const namePattern = /([А-Яа-яA-Za-z0-9]\. [А-Яа-яA-Za-z0-9]\. [А-Яа-яA-Za-z0-9]+)/g; const parts = sightName.split(namePattern); if (parts.length > 1) { return parts.map((part, index) => { const initialsPattern = /^[А-Яа-яA-Za-z0-9]\. [А-Яа-яA-Za-z0-9]\. [А-Яа-яA-Za-z0-9]+$/; if (initialsPattern.test(part)) { return (
{part}
); } return {part}; }); } return sightName; }, [sightName]); // Replicates titleLineHeight from SightFrame.jsx const titleLineHeight = useMemo(() => { if (!sightName) return "120%"; const textLength = sightName.length; const calculatedLineHeight = Math.max( 100, Math.min(120, 120 - (textLength / 10) * 1) ); return `${calculatedLineHeight}%`; }, [sightName]); const isIntro = selectedSection === -1; return (
{/* media stack — always rendered, matches .sight-frame-media-stack */}
{currentMedia ? ( renderCurrentMedia( currentMedia, token, threeViewResetKey, threeViewControlRef, () => threeViewControlRef.current?.zoomIn?.(), () => threeViewControlRef.current?.zoomOut?.(), () => setThreeViewResetKey((k) => k + 1) ) ) : (
{isIntro ? "Медиа не добавлено" : "Нет медиа"}
)}
{/* content */}
{/* title: intro-title (300px, 40px centered) or regular (24px with border) */}

{isIntro ? processedSightName : sightName}

{/* text body — only for article sections */} {!isIntro && currentArticle && (
)}
{/* menu — matches .sight-frame-menu */}
{/* back arrow — shown when not on intro */} {!isIntro && ( )} {/* article nav buttons — matches .sight-frame-menu-point */} {articles.map((article, index) => ( ))}
); }