beam: refine model attachment status icon
This commit is contained in:
parent
3e41c451c1
commit
677916389f
|
|
@ -15,9 +15,9 @@ import type { TIssueServiceType } from "@plane/types";
|
||||||
import { EIssueServiceType } from "@plane/types";
|
import { EIssueServiceType } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import type { TContextMenuItem } from "@plane/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 { 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
|
// components
|
||||||
//
|
//
|
||||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||||
|
|
@ -90,6 +90,7 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
|
||||||
const previewURL = appendSearchParam(fileURL, "preview", "true");
|
const previewURL = appendSearchParam(fileURL, "preview", "true");
|
||||||
const beamViewer = getBeamViewerAttachment(attachment);
|
const beamViewer = getBeamViewerAttachment(attachment);
|
||||||
const [beamConversionStatus, setBeamConversionStatus] = useState<TBeamConversionStatus | null>(null);
|
const [beamConversionStatus, setBeamConversionStatus] = useState<TBeamConversionStatus | null>(null);
|
||||||
|
const [beamConversionError, setBeamConversionError] = useState<string | null>(null);
|
||||||
const resolvedArtifactUrl = beamConversionStatus?.status === "ready" ? beamConversionStatus.artifactUrl : undefined;
|
const resolvedArtifactUrl = beamConversionStatus?.status === "ready" ? beamConversionStatus.artifactUrl : undefined;
|
||||||
const modelViewerUrl =
|
const modelViewerUrl =
|
||||||
beamViewer?.viewerUrl ||
|
beamViewer?.viewerUrl ||
|
||||||
|
|
@ -102,11 +103,22 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
|
||||||
: undefined);
|
: undefined);
|
||||||
const modelDownloadUrl = beamViewer?.downloadUrl || beamViewer?.src;
|
const modelDownloadUrl = beamViewer?.downloadUrl || beamViewer?.src;
|
||||||
const previewDownloadUrl = modelDownloadUrl || fileURL;
|
const previewDownloadUrl = modelDownloadUrl || fileURL;
|
||||||
const beamConversionStatusLabel = getBeamConversionStatusLabel(
|
|
||||||
beamConversionStatus?.status ?? beamViewer?.conversion?.status
|
|
||||||
);
|
|
||||||
const isBeamAwaitingPreview = !!beamViewer && !modelViewerUrl;
|
|
||||||
const isBeamModel = isBeamModelFile(fullFileName);
|
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 [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
||||||
const [isModelViewerOpen, setIsModelViewerOpen] = useState(false);
|
const [isModelViewerOpen, setIsModelViewerOpen] = useState(false);
|
||||||
const [isThumbnailError, setIsThumbnailError] = useState(false);
|
const [isThumbnailError, setIsThumbnailError] = useState(false);
|
||||||
|
|
@ -148,21 +160,23 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!beamViewer || beamViewer.viewerUrl || !beamViewer.conversion) return;
|
if (!beamViewer || beamViewer.viewerUrl || !beamViewer.src) return;
|
||||||
|
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
let timeoutId: ReturnType<typeof window.setTimeout> | undefined;
|
let timeoutId: number | undefined;
|
||||||
|
|
||||||
const pollStatus = async () => {
|
const pollStatus = async () => {
|
||||||
try {
|
try {
|
||||||
const status = await fetchBeamConversionStatus(beamViewer);
|
const status = await fetchBeamConversionStatus(beamViewer);
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
|
setBeamConversionError(null);
|
||||||
setBeamConversionStatus(status);
|
setBeamConversionStatus(status);
|
||||||
if (status.status !== "ready" && status.status !== "failed") {
|
if (status.status !== "ready" && status.status !== "failed") {
|
||||||
timeoutId = window.setTimeout(pollStatus, 5000);
|
timeoutId = window.setTimeout(pollStatus, 5000);
|
||||||
}
|
}
|
||||||
} catch (_error) {
|
} catch (error) {
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
|
setBeamConversionError(error instanceof Error ? error.message : "Не удалось получить статус Beam.");
|
||||||
timeoutId = window.setTimeout(pollStatus, 8000);
|
timeoutId = window.setTimeout(pollStatus, 8000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -211,7 +225,25 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
{isBeamModel ? (
|
{isBeamModel ? (
|
||||||
<Box className="size-9 text-[rgb(var(--nodedc-accent-rgb))]" />
|
isBeamConversionFailed ? (
|
||||||
|
<Tooltip tooltipContent={beamStatusTooltipContent} isMobile={isMobile}>
|
||||||
|
<div className="grid size-9 place-items-center text-red-400">
|
||||||
|
<AlertTriangle className="size-9" />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
) : isBeamAwaitingPreview ? (
|
||||||
|
<Tooltip tooltipContent={beamStatusTooltipContent} isMobile={isMobile}>
|
||||||
|
<div className="grid size-9 place-items-center">
|
||||||
|
<Spinner
|
||||||
|
height="36px"
|
||||||
|
width="36px"
|
||||||
|
className="fill-[rgb(var(--nodedc-accent-rgb))] text-[rgba(var(--nodedc-accent-rgb),0.18)]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Box className="size-9 text-[rgb(var(--nodedc-accent-rgb))]" />
|
||||||
|
)
|
||||||
) : previewType === "file" ? (
|
) : previewType === "file" ? (
|
||||||
fileIcon
|
fileIcon
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -231,11 +263,6 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
|
||||||
<span className="size-1 rounded-full bg-layer-1" />
|
<span className="size-1 rounded-full bg-layer-1" />
|
||||||
<span>{convertBytesToSize(attachment.attributes.size)}</span>
|
<span>{convertBytesToSize(attachment.attributes.size)}</span>
|
||||||
</div>
|
</div>
|
||||||
{beamConversionStatusLabel && (
|
|
||||||
<div className="mt-2 w-fit whitespace-nowrap rounded-full bg-[rgba(var(--nodedc-accent-rgb),0.12)] px-2 py-0.5 text-10 font-semibold text-[rgb(var(--nodedc-accent-rgb))]">
|
|
||||||
{beamConversionStatusLabel}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{attachment?.created_by && (
|
{attachment?.created_by && (
|
||||||
|
|
@ -311,17 +338,33 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
|
||||||
</div>
|
</div>
|
||||||
) : previewType === "pdf" && previewURL ? (
|
) : previewType === "pdf" && previewURL ? (
|
||||||
<IssueAttachmentPdfPreview fileURL={previewURL} />
|
<IssueAttachmentPdfPreview fileURL={previewURL} />
|
||||||
) : isBeamAwaitingPreview ? (
|
) : isBeamAwaitingPreview || isBeamConversionFailed ? (
|
||||||
<div className="flex h-full flex-col items-center justify-center gap-4 p-8 text-center">
|
<div className="flex h-full flex-col items-center justify-center gap-4 p-8 text-center">
|
||||||
<div className="grid size-20 place-items-center rounded-3xl bg-[rgba(var(--nodedc-accent-rgb),0.12)] text-[rgb(var(--nodedc-accent-rgb))]">
|
<div
|
||||||
<Box className="size-10" />
|
className={`grid size-20 place-items-center rounded-3xl ${
|
||||||
|
isBeamConversionFailed
|
||||||
|
? "bg-red-500/10 text-red-400"
|
||||||
|
: "bg-[rgba(var(--nodedc-accent-rgb),0.12)] text-[rgb(var(--nodedc-accent-rgb))]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isBeamConversionFailed ? (
|
||||||
|
<AlertTriangle className="size-10" />
|
||||||
|
) : (
|
||||||
|
<Spinner
|
||||||
|
height="40px"
|
||||||
|
width="40px"
|
||||||
|
className="fill-[rgb(var(--nodedc-accent-rgb))] text-[rgba(var(--nodedc-accent-rgb),0.18)]"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="max-w-md">
|
<div className="max-w-md">
|
||||||
<div className="text-15 font-semibold text-primary">
|
<div className="text-15 font-semibold text-primary">
|
||||||
{beamConversionStatusLabel ?? "Ожидает дерево"}
|
{isBeamConversionFailed ? "Ошибка дерева" : beamConversionStatusLabel ?? "Ожидает дерево"}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 text-14 text-secondary">
|
<div className="mt-2 text-14 text-secondary">
|
||||||
Оригинальный STEP загружен в Beam. Preview появится после подготовки GLB/XKT и дерева компонентов.
|
{isBeamConversionFailed
|
||||||
|
? beamStatusTooltipContent
|
||||||
|
: "Оригинальный STEP загружен в Beam. Preview появится после подготовки GLB/XKT и дерева компонентов."}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{modelDownloadUrl && (
|
{modelDownloadUrl && (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue