From 867f7bc558a3a50b883c6228d8185c59f0060144 Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Fri, 5 Jun 2026 12:46:47 +0300 Subject: [PATCH] feat: embed Beam viewer in issue peek --- .../attachment/attachment-list-item.tsx | 97 ++-- .../components/issues/peek-overview/view.tsx | 440 ++++++++++++------ plane-src/apps/web/helpers/beam-viewer.ts | 29 +- plane-src/apps/web/styles/globals.css | 147 +++++- 4 files changed, 492 insertions(+), 221 deletions(-) diff --git a/plane-src/apps/web/core/components/issues/attachment/attachment-list-item.tsx b/plane-src/apps/web/core/components/issues/attachment/attachment-list-item.tsx index f05d1c0..1344b5d 100644 --- a/plane-src/apps/web/core/components/issues/attachment/attachment-list-item.tsx +++ b/plane-src/apps/web/core/components/issues/attachment/attachment-list-item.tsx @@ -24,6 +24,7 @@ import { ButtonAvatars } from "@/components/dropdowns/member/avatar"; import { getFileIcon } from "@/components/icons"; import { buildBeamViewerUrl, + dispatchBeamViewerOpenEvent, fetchBeamConversionStatus, getBeamViewerAttachment, isBeamModelFile, @@ -62,7 +63,9 @@ const getUsableBeamViewerUrl = (url: string | undefined): string | undefined => if (!url) return undefined; try { const parsedUrl = new URL(url, "http://localhost"); - return parsedUrl.searchParams.has("url") || parsedUrl.searchParams.has("src") || parsedUrl.searchParams.has("projectId") + return parsedUrl.searchParams.has("url") || + parsedUrl.searchParams.has("src") || + parsedUrl.searchParams.has("projectId") ? url : undefined; } catch (_error) { @@ -135,7 +138,14 @@ const buildSyncedBeamViewerAttachment = ( export const IssueAttachmentsListItem = observer(function IssueAttachmentsListItem(props: TIssueAttachmentsListItem) { const { t } = useTranslation(); // props - const { attachmentId, disabled, issueId, issueServiceType = EIssueServiceType.ISSUES, projectId, workspaceSlug } = props; + const { + attachmentId, + disabled, + issueId, + issueServiceType = EIssueServiceType.ISSUES, + projectId, + workspaceSlug, + } = props; // store hooks const { getUserDetails } = useMember(); const { @@ -188,16 +198,24 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt beamViewer?.conversion?.message || "Оригинальная модель загружена в Beam. Preview появится после подготовки GLB/XKT и дерева компонентов."; const [isPreviewOpen, setIsPreviewOpen] = useState(false); - const [isModelViewerOpen, setIsModelViewerOpen] = useState(false); const [isThumbnailError, setIsThumbnailError] = useState(false); + const openModelViewer = () => { + if (!modelViewerUrl) return; + dispatchBeamViewerOpenEvent({ + downloadUrl: modelDownloadUrl, + fileExtension, + fileName: fullFileName, + fileSize: attachment?.attributes.size ?? 0, + issueId, + viewerUrl: modelViewerUrl, + }); + }; const menuItems: TContextMenuItem[] = [ ...(modelViewerUrl ? [ { key: "view-model", - action: () => { - setIsModelViewerOpen(true); - }, + action: openModelViewer, title: "Посмотреть модель", icon: Eye, }, @@ -262,7 +280,17 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt isMounted = false; if (timeoutId) window.clearTimeout(timeoutId); }; - }, [attachmentId, attachmentService, beamViewer, fetchAttachments, fullFileName, issueId, projectId, storedModelViewerUrl, workspaceSlug]); + }, [ + attachmentId, + attachmentService, + beamViewer, + fetchAttachments, + fullFileName, + issueId, + projectId, + storedModelViewerUrl, + workspaceSlug, + ]); if (!attachment) return <>; @@ -275,7 +303,7 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt onClick={(e) => { e.preventDefault(); e.stopPropagation(); - if (modelViewerUrl) setIsModelViewerOpen(true); + if (modelViewerUrl) openModelViewer(); else setIsPreviewOpen(true); }} > @@ -302,7 +330,7 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt {isBeamModel ? ( isBeamConversionFailed ? ( -
+
@@ -434,7 +462,7 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
- {isBeamConversionFailed ? "Ошибка дерева" : beamConversionStatusLabel ?? "Ожидает дерево"} + {isBeamConversionFailed ? "Ошибка дерева" : (beamConversionStatusLabel ?? "Ожидает дерево")}
{isBeamConversionFailed @@ -466,55 +494,6 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
- - setIsModelViewerOpen(false)} - position={EModalPosition.CENTER} - width={EModalWidth.VIIXL} - className="nodedc-beam-viewer-modal h-[calc(100vh-2rem)] max-w-[calc(100vw-2rem)] overflow-hidden !border-0 !bg-black !shadow-none !ring-0 !outline-none focus:!outline-none" - > -
-
- - {modelViewerUrl && ( -