ARCH - TASKER BIM: серверный CAD-шлюз и независимый просмотр из Ops

This commit is contained in:
DCCONSTRUCTIONS
2026-07-30 07:35:23 +03:00
parent 24481ea432
commit ad04910209
10 changed files with 1252 additions and 499 deletions
@@ -27,16 +27,12 @@ import { getFileIcon } from "@/components/icons";
import {
buildBeamViewerUrl,
dispatchBeamViewerOpenEvent,
fetchBeamConversionStatus,
fetchBeamModelVersions,
getBeamModelVersionRecords,
getBeamVersionViewerUrl,
getBeamViewerAttachment,
isBeamModelFile,
mergeBeamModelVersionRecordLists,
syncCurrentBeamVersionRecord,
type TBeamModelVersionRecord,
type TBeamViewerAttachment,
type TBeamConversionStatus,
} from "@/helpers/beam-viewer";
import { IssueAttachmentPdfPreview, IssueAttachmentPdfThumbnail } from "./attachment-pdf-preview";
@@ -125,46 +121,6 @@ const sanitizeBeamStatusMessage = (message: string | undefined): string | undefi
const getBeamVersionRecordKey = (version: TBeamModelVersionRecord): string =>
version.versionId || `version-${version.version}`;
const buildSyncedBeamViewerAttachment = (
beamViewer: TBeamViewerAttachment,
status: TBeamConversionStatus,
fullFileName: string
): TBeamViewerAttachment | null => {
if (status.status !== "ready" || !status.artifactUrl) return null;
const artifactType = status.artifactType || status.targetFormat || "gltf";
const targetFormat: "glb" | "xkt" = status.targetFormat || (artifactType === "xkt" ? "xkt" : "glb");
return syncCurrentBeamVersionRecord({
...beamViewer,
assetId: beamViewer.assetId || status.assetId,
previewAvailable: true,
sha256: beamViewer.sha256 || status.sha256,
version: beamViewer.version || status.version,
versionId: beamViewer.versionId || status.versionId,
viewerUrl: buildBeamViewerUrl({
name: fullFileName,
settingsSrc: status.sourceSrc || beamViewer.conversion?.sourceSrc || beamViewer.src,
src: status.artifactUrl,
type: artifactType,
}),
conversion: {
...beamViewer.conversion,
artifactSrc: status.artifactSrc,
artifactType,
componentTreeRequired: status.componentTreeRequired ?? beamViewer.conversion?.componentTreeRequired ?? true,
message: status.message || beamViewer.conversion?.message,
metadataSrc: status.metadataSrc,
size: status.size ?? beamViewer.conversion?.size,
sourceFormat: status.sourceFormat || beamViewer.conversion?.sourceFormat || beamViewer.type,
sourceSrc: status.sourceSrc || beamViewer.conversion?.sourceSrc,
status: "ready",
targetFormat,
updatedAt: status.updatedAt || beamViewer.conversion?.updatedAt,
},
});
};
export const IssueAttachmentsListItem = observer(function IssueAttachmentsListItem(props: TIssueAttachmentsListItem) {
const { t } = useTranslation();
// props
@@ -213,6 +169,9 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
type: beamConversionStatus?.artifactType || "gltf",
})
: undefined) || storedModelViewerUrlWithSettings;
const canOpenModelViewer =
!!beamViewer &&
(beamViewer.previewAvailable || beamEffectiveStatus === "ready" || !!modelViewerUrl);
const modelDownloadUrl = beamViewer?.downloadUrl || beamViewer?.src;
const previewDownloadUrl = modelDownloadUrl || fileURL;
const isBeamModel = isBeamModelFile(fullFileName);
@@ -224,7 +183,7 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
? "BIM Viewer вернул статус готовности, но не вернул viewer-артефакт."
: null);
const isBeamConversionFailed = beamEffectiveStatus === "failed" || !!beamStatusErrorMessage;
const isBeamAwaitingPreview = !!beamViewer && !modelViewerUrl && !isBeamConversionFailed;
const isBeamAwaitingPreview = !!beamViewer && !canOpenModelViewer && !isBeamConversionFailed;
const rawBeamStatusTooltipContent = isBeamConversionFailed
? beamStatusErrorMessage || beamConversionStatus?.message || "Ошибка подготовки дерева компонентов."
: beamConversionStatus?.message ||
@@ -268,30 +227,57 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
if (!userId) return "—";
return getUserDetails(userId)?.display_name ?? "—";
};
const openModelViewer = () => {
if (!modelViewerUrl) return;
dispatchBeamViewerOpenEvent({
downloadUrl: modelDownloadUrl,
fileExtension,
fileName: fullFileName,
fileSize: attachment?.attributes.size ?? 0,
issueId,
viewerUrl: modelViewerUrl,
});
const openModelViewer = async () => {
if (!canOpenModelViewer || !attachment) return;
try {
const viewerUrl = await attachmentService.getBeamIssueAttachmentViewerUrl(
workspaceSlug,
projectId,
issueId,
attachment.id
);
dispatchBeamViewerOpenEvent({
downloadUrl: modelDownloadUrl,
fileExtension,
fileName: fullFileName,
fileSize: attachment.attributes.size ?? 0,
issueId,
viewerUrl,
});
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Модель не открыта",
message: error instanceof Error ? error.message : "Не удалось запустить BIM Viewer через Ops.",
});
}
};
const openBeamVersionViewer = (version: TBeamModelVersionRecord) => {
const viewerUrl = getBeamVersionViewerUrl(version);
if (!viewerUrl) return;
setIsVersionHistoryOpen(false);
dispatchBeamViewerOpenEvent({
downloadUrl: version.downloadUrl,
fileExtension: getFileExtension(version.originalFilename),
fileName: version.originalFilename,
fileSize: version.size || version.conversion?.size || 0,
issueId,
viewerUrl,
});
const openBeamVersionViewer = async (version: TBeamModelVersionRecord) => {
if (!attachment || !getBeamVersionViewerUrl(version)) return;
try {
const viewerUrl = await attachmentService.getBeamIssueAttachmentViewerUrl(
workspaceSlug,
projectId,
issueId,
attachment.id,
version.versionId || String(version.version)
);
setIsVersionHistoryOpen(false);
dispatchBeamViewerOpenEvent({
downloadUrl: version.downloadUrl,
fileExtension: getFileExtension(version.originalFilename),
fileName: version.originalFilename,
fileSize: version.size || version.conversion?.size || 0,
issueId,
viewerUrl,
});
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Версия не открыта",
message: error instanceof Error ? error.message : "Не удалось запустить версию BIM-модели.",
});
}
};
const startVersionUpload = () => {
versionUploadInputRef.current?.click();
@@ -362,7 +348,7 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
}
};
const menuItems: TContextMenuItem[] = [
...(modelViewerUrl
...(canOpenModelViewer
? [
{
key: "view-model",
@@ -429,7 +415,8 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
}
let isMounted = true;
fetchBeamModelVersions(beamViewer)
attachmentService
.getBeamIssueAttachmentVersions(workspaceSlug, projectId, issueId, attachmentId)
.then((history) => {
if (!isMounted) return;
setLiveBeamVersions(Array.isArray(history.versions) ? history.versions : []);
@@ -443,7 +430,17 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
return () => {
isMounted = false;
};
}, [beamViewer?.assetId, beamViewer?.projectId, beamViewer?.src, beamViewer?.versionId]);
}, [
attachmentId,
attachmentService,
beamViewer?.assetId,
beamViewer?.projectId,
beamViewer?.src,
beamViewer?.versionId,
issueId,
projectId,
workspaceSlug,
]);
useEffect(() => {
if (!beamViewer || !beamViewer.src) return;
@@ -454,17 +451,15 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
const pollStatus = async () => {
try {
const status = await fetchBeamConversionStatus(beamViewer);
const status = await attachmentService.getBeamIssueAttachmentStatus(
workspaceSlug,
projectId,
issueId,
attachmentId
);
if (!isMounted) return;
setBeamConversionError(null);
setBeamConversionStatus(status);
const syncedBeamViewer = buildSyncedBeamViewerAttachment(beamViewer, status, fullFileName);
if (syncedBeamViewer && syncedBeamViewer.viewerUrl !== beamViewer.viewerUrl) {
attachmentService
.updateBeamIssueAttachmentReference(workspaceSlug, projectId, issueId, attachmentId, syncedBeamViewer)
.then(() => fetchAttachments(workspaceSlug, projectId, issueId))
.catch((error) => console.error("Error in syncing Beam attachment metadata:", error));
}
if (status.status !== "ready" && status.status !== "failed") {
timeoutId = window.setTimeout(pollStatus, 5000);
}
@@ -485,8 +480,6 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
attachmentId,
attachmentService,
beamViewer,
fetchAttachments,
fullFileName,
issueId,
projectId,
storedModelViewerUrl,
@@ -506,7 +499,7 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (modelViewerUrl) openModelViewer();
if (canOpenModelViewer) void openModelViewer();
else setIsPreviewOpen(true);
}}
>
@@ -586,9 +579,9 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
</button>
</Tooltip>
)}
{(modelViewerUrl || previewURL) && (
{(canOpenModelViewer || previewURL) && (
<Tooltip
tooltipContent={modelViewerUrl ? "Посмотреть модель" : "Открыть предпросмотр"}
tooltipContent={canOpenModelViewer ? "Посмотреть модель" : "Открыть предпросмотр"}
isMobile={isMobile}
>
<button
@@ -597,7 +590,7 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (modelViewerUrl) openModelViewer();
if (canOpenModelViewer) void openModelViewer();
else setIsPreviewOpen(true);
}}
>
@@ -683,7 +676,7 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (modelViewerUrl) openModelViewer();
if (canOpenModelViewer) void openModelViewer();
else setIsPreviewOpen(true);
}}
>