From 677916389f2a887f03ada3cffec2329af5c9b3ba Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Thu, 4 Jun 2026 22:49:43 +0300 Subject: [PATCH] beam: refine model attachment status icon --- .../attachment/attachment-list-item.tsx | 83 ++++++++++++++----- 1 file changed, 63 insertions(+), 20 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 8037832..dc000cc 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 @@ -15,9 +15,9 @@ import type { TIssueServiceType } from "@plane/types"; import { EIssueServiceType } from "@plane/types"; // ui import type { TContextMenuItem } from "@plane/ui"; -import { ActionDropdown, EModalPosition, EModalWidth, ModalCore } from "@plane/ui"; +import { ActionDropdown, EModalPosition, EModalWidth, ModalCore, Spinner } from "@plane/ui"; import { convertBytesToSize, getFileExtension, getFileName, getFileURL, renderFormattedDate } from "@plane/utils"; -import { Box, Download, Eye, ImageIcon, Play, X } from "lucide-react"; +import { AlertTriangle, Box, Download, Eye, ImageIcon, Play, X } from "lucide-react"; // components // import { ButtonAvatars } from "@/components/dropdowns/member/avatar"; @@ -90,6 +90,7 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt const previewURL = appendSearchParam(fileURL, "preview", "true"); const beamViewer = getBeamViewerAttachment(attachment); const [beamConversionStatus, setBeamConversionStatus] = useState(null); + const [beamConversionError, setBeamConversionError] = useState(null); const resolvedArtifactUrl = beamConversionStatus?.status === "ready" ? beamConversionStatus.artifactUrl : undefined; const modelViewerUrl = beamViewer?.viewerUrl || @@ -102,11 +103,22 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt : undefined); const modelDownloadUrl = beamViewer?.downloadUrl || beamViewer?.src; const previewDownloadUrl = modelDownloadUrl || fileURL; - const beamConversionStatusLabel = getBeamConversionStatusLabel( - beamConversionStatus?.status ?? beamViewer?.conversion?.status - ); - const isBeamAwaitingPreview = !!beamViewer && !modelViewerUrl; const isBeamModel = isBeamModelFile(fullFileName); + const beamEffectiveStatus = beamConversionStatus?.status ?? beamViewer?.conversion?.status; + const beamConversionStatusLabel = getBeamConversionStatusLabel(beamEffectiveStatus); + const beamStatusErrorMessage = + beamConversionError || + beamConversionStatus?.error || + (beamConversionStatus?.status === "ready" && !modelViewerUrl + ? "Beam вернул статус готовности, но не вернул viewer-артефакт." + : null); + const isBeamConversionFailed = beamEffectiveStatus === "failed" || !!beamStatusErrorMessage; + const isBeamAwaitingPreview = !!beamViewer && !modelViewerUrl && !isBeamConversionFailed; + const beamStatusTooltipContent = isBeamConversionFailed + ? beamStatusErrorMessage || beamConversionStatus?.message || "Ошибка подготовки дерева компонентов." + : beamConversionStatus?.message || + beamViewer?.conversion?.message || + "Оригинальная модель загружена в Beam. Preview появится после подготовки GLB/XKT и дерева компонентов."; const [isPreviewOpen, setIsPreviewOpen] = useState(false); const [isModelViewerOpen, setIsModelViewerOpen] = useState(false); const [isThumbnailError, setIsThumbnailError] = useState(false); @@ -148,21 +160,23 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt const { isMobile } = usePlatformOS(); useEffect(() => { - if (!beamViewer || beamViewer.viewerUrl || !beamViewer.conversion) return; + if (!beamViewer || beamViewer.viewerUrl || !beamViewer.src) return; let isMounted = true; - let timeoutId: ReturnType | undefined; + let timeoutId: number | undefined; const pollStatus = async () => { try { const status = await fetchBeamConversionStatus(beamViewer); if (!isMounted) return; + setBeamConversionError(null); setBeamConversionStatus(status); if (status.status !== "ready" && status.status !== "failed") { timeoutId = window.setTimeout(pollStatus, 5000); } - } catch (_error) { + } catch (error) { if (!isMounted) return; + setBeamConversionError(error instanceof Error ? error.message : "Не удалось получить статус Beam."); timeoutId = window.setTimeout(pollStatus, 8000); } }; @@ -211,7 +225,25 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt ) : (
{isBeamModel ? ( - + isBeamConversionFailed ? ( + +
+ +
+
+ ) : isBeamAwaitingPreview ? ( + +
+ +
+
+ ) : ( + + ) ) : previewType === "file" ? ( fileIcon ) : ( @@ -231,11 +263,6 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt {convertBytesToSize(attachment.attributes.size)}
- {beamConversionStatusLabel && ( -
- {beamConversionStatusLabel} -
- )} {attachment?.created_by && ( @@ -311,17 +338,33 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt ) : previewType === "pdf" && previewURL ? ( - ) : isBeamAwaitingPreview ? ( + ) : isBeamAwaitingPreview || isBeamConversionFailed ? (
-
- +
+ {isBeamConversionFailed ? ( + + ) : ( + + )}
- {beamConversionStatusLabel ?? "Ожидает дерево"} + {isBeamConversionFailed ? "Ошибка дерева" : beamConversionStatusLabel ?? "Ожидает дерево"}
- Оригинальный STEP загружен в Beam. Preview появится после подготовки GLB/XKT и дерева компонентов. + {isBeamConversionFailed + ? beamStatusTooltipContent + : "Оригинальный STEP загружен в Beam. Preview появится после подготовки GLB/XKT и дерева компонентов."}
{modelDownloadUrl && (