ARCH - TASKER BIM: серверный CAD-шлюз и независимый просмотр из Ops
This commit is contained in:
@@ -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);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -9,7 +9,6 @@ import { API_BASE_URL } from "@plane/constants";
|
||||
// plane types
|
||||
import { getFileMetaDataForUpload, generateFileUploadPayload } from "@plane/services";
|
||||
import type {
|
||||
TFileMetaDataLite,
|
||||
TIssueAttachment,
|
||||
TIssueAttachmentUploadResponse,
|
||||
TIssueServiceType,
|
||||
@@ -17,17 +16,11 @@ import type {
|
||||
import { EIssueServiceType } from "@plane/types";
|
||||
// services
|
||||
import {
|
||||
deleteBeamModelAsset,
|
||||
deleteBeamModelVersion,
|
||||
getBeamModelVersionRecords,
|
||||
getBeamModelMimeType,
|
||||
getBeamViewerAttachment,
|
||||
getBeamViewerVersionNumber,
|
||||
isBeamModelFile,
|
||||
mergeBeamModelVersionRecords,
|
||||
type TBeamModelVersionRecord,
|
||||
type TBeamViewerAttachment,
|
||||
uploadBeamModelFile,
|
||||
type TBeamConversionStatus,
|
||||
type TBeamVersionHistoryResponse,
|
||||
} from "@/helpers/beam-viewer";
|
||||
import { APIService } from "@/services/api.service";
|
||||
import { FileUploadService } from "@/services/file-upload.service";
|
||||
@@ -49,6 +42,15 @@ export class IssueAttachmentService extends APIService {
|
||||
this.serviceType = serviceType;
|
||||
}
|
||||
|
||||
private attachmentBasePath(workspaceSlug: string, projectId: string, issueId: string): string {
|
||||
return `/api/assets/v2/workspaces/${workspaceSlug}/projects/${projectId}/${this.serviceType}/${issueId}/attachments`;
|
||||
}
|
||||
|
||||
private toBeamRequestError(error: unknown, fallback: string): Error {
|
||||
const responseData = (error as { response?: { data?: { message?: string; error?: string } } })?.response?.data;
|
||||
return new Error(responseData?.message || responseData?.error || fallback);
|
||||
}
|
||||
|
||||
private async updateIssueAttachmentUploadStatus(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
@@ -64,43 +66,6 @@ export class IssueAttachmentService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
private async createBeamIssueAttachmentReference(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
payload: TFileMetaDataLite & { beamViewer: TBeamViewerAttachment }
|
||||
): Promise<TIssueAttachment> {
|
||||
return this.post(
|
||||
`/api/assets/v2/workspaces/${workspaceSlug}/projects/${projectId}/${this.serviceType}/${issueId}/attachments/`,
|
||||
payload
|
||||
)
|
||||
.then((response) => {
|
||||
const createResponse: TIssueAttachmentCreateResponse = response?.data;
|
||||
return createResponse.attachment;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data ?? error;
|
||||
});
|
||||
}
|
||||
|
||||
async updateBeamIssueAttachmentReference(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
attachmentId: string,
|
||||
beamViewer: TBeamViewerAttachment,
|
||||
attributes: Record<string, unknown> = {}
|
||||
): Promise<TIssueAttachment> {
|
||||
return this.patch(
|
||||
`/api/assets/v2/workspaces/${workspaceSlug}/projects/${projectId}/${this.serviceType}/${issueId}/attachments/${attachmentId}/`,
|
||||
{ ...attributes, beamViewer }
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data ?? error;
|
||||
});
|
||||
}
|
||||
|
||||
async uploadBeamIssueAttachmentVersion(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
@@ -108,60 +73,26 @@ export class IssueAttachmentService extends APIService {
|
||||
attachment: TIssueAttachment,
|
||||
file: File,
|
||||
uploadProgressHandler?: AxiosRequestConfig["onUploadProgress"],
|
||||
uploadedBy?: string
|
||||
_uploadedBy?: string
|
||||
): Promise<TIssueAttachment> {
|
||||
const previousBeamViewer = getBeamViewerAttachment(attachment);
|
||||
if (!previousBeamViewer) {
|
||||
if (!getBeamViewerAttachment(attachment)) {
|
||||
throw new Error("Для этого вложения нет BIM-метаданных.");
|
||||
}
|
||||
if (!isBeamModelFile(file.name)) {
|
||||
throw new Error("Формат модели не поддерживается BIM Viewer.");
|
||||
}
|
||||
|
||||
const previousVersions = getBeamModelVersionRecords(previousBeamViewer, attachment);
|
||||
const currentVersion = Math.max(
|
||||
getBeamViewerVersionNumber(previousBeamViewer, attachment),
|
||||
...previousVersions.map((version) => version.version)
|
||||
);
|
||||
const nextVersion = currentVersion + 1;
|
||||
const assetId = previousBeamViewer.assetId || attachment.id;
|
||||
const nextBeamViewer = await uploadBeamModelFile(file, {
|
||||
assetId,
|
||||
issueId,
|
||||
onUploadProgress: uploadProgressHandler,
|
||||
projectId,
|
||||
uploadedBy,
|
||||
version: nextVersion,
|
||||
workspaceSlug,
|
||||
});
|
||||
const nextVersions = mergeBeamModelVersionRecords(
|
||||
{ ...previousBeamViewer, assetId, versions: previousVersions },
|
||||
nextBeamViewer,
|
||||
attachment
|
||||
);
|
||||
|
||||
return this.updateBeamIssueAttachmentReference(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
issueId,
|
||||
attachment.id,
|
||||
{
|
||||
...nextBeamViewer,
|
||||
assetId,
|
||||
projectId: nextBeamViewer.projectId || previousBeamViewer.projectId,
|
||||
previewAvailable: nextBeamViewer.previewAvailable,
|
||||
uploadedBy,
|
||||
version: nextVersion,
|
||||
versions: nextVersions,
|
||||
viewerUrl: nextBeamViewer.viewerUrl ?? null,
|
||||
},
|
||||
{
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: getBeamModelMimeType(file),
|
||||
version: nextVersion,
|
||||
}
|
||||
);
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return this.post(
|
||||
`${this.attachmentBasePath(workspaceSlug, projectId, issueId)}/${attachment.id}/bim-versions/`,
|
||||
formData,
|
||||
{ onUploadProgress: uploadProgressHandler }
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw this.toBeamRequestError(error, "Не удалось загрузить новую версию BIM-модели.");
|
||||
});
|
||||
}
|
||||
|
||||
async deleteBeamIssueAttachmentVersion(
|
||||
@@ -171,36 +102,69 @@ export class IssueAttachmentService extends APIService {
|
||||
attachment: TIssueAttachment,
|
||||
versionToDelete: TBeamModelVersionRecord
|
||||
): Promise<TIssueAttachment> {
|
||||
const beamViewer = getBeamViewerAttachment(attachment);
|
||||
if (!beamViewer) {
|
||||
if (!getBeamViewerAttachment(attachment)) {
|
||||
throw new Error("Для этого вложения нет BIM-метаданных.");
|
||||
}
|
||||
const versionKey = encodeURIComponent(versionToDelete.versionId || String(versionToDelete.version));
|
||||
return this.delete(
|
||||
`${this.attachmentBasePath(workspaceSlug, projectId, issueId)}/${attachment.id}/bim-versions/${versionKey}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw this.toBeamRequestError(error, "Не удалось удалить версию BIM-модели.");
|
||||
});
|
||||
}
|
||||
|
||||
const currentVersion = getBeamViewerVersionNumber(beamViewer, attachment);
|
||||
const isCurrentVersion = versionToDelete.versionId
|
||||
? beamViewer.versionId === versionToDelete.versionId
|
||||
: currentVersion === versionToDelete.version;
|
||||
if (isCurrentVersion) {
|
||||
throw new Error("Текущую версию нельзя удалить. Сначала переключите модель на другую версию.");
|
||||
}
|
||||
async getBeamIssueAttachmentStatus(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
attachmentId: string
|
||||
): Promise<TBeamConversionStatus> {
|
||||
return this.get(
|
||||
`${this.attachmentBasePath(workspaceSlug, projectId, issueId)}/${attachmentId}/bim-status/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw this.toBeamRequestError(error, "Не удалось получить статус BIM-модели.");
|
||||
});
|
||||
}
|
||||
|
||||
const versions = getBeamModelVersionRecords(beamViewer, attachment);
|
||||
const nextVersions = versions.filter((version) =>
|
||||
versionToDelete.versionId
|
||||
? version.versionId !== versionToDelete.versionId
|
||||
: version.version !== versionToDelete.version
|
||||
);
|
||||
async getBeamIssueAttachmentVersions(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
attachmentId: string
|
||||
): Promise<TBeamVersionHistoryResponse> {
|
||||
return this.get(
|
||||
`${this.attachmentBasePath(workspaceSlug, projectId, issueId)}/${attachmentId}/bim-versions/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw this.toBeamRequestError(error, "Не удалось получить историю BIM-модели.");
|
||||
});
|
||||
}
|
||||
|
||||
if (nextVersions.length === versions.length) {
|
||||
throw new Error("Версия не найдена.");
|
||||
}
|
||||
|
||||
await deleteBeamModelVersion(versionToDelete);
|
||||
|
||||
return this.updateBeamIssueAttachmentReference(workspaceSlug, projectId, issueId, attachment.id, {
|
||||
...beamViewer,
|
||||
versions: nextVersions,
|
||||
});
|
||||
async getBeamIssueAttachmentViewerUrl(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
attachmentId: string,
|
||||
versionId?: string
|
||||
): Promise<string> {
|
||||
return this.post(
|
||||
`${this.attachmentBasePath(workspaceSlug, projectId, issueId)}/${attachmentId}/bim-viewer/`,
|
||||
versionId ? { versionId } : {}
|
||||
)
|
||||
.then((response) => {
|
||||
const viewerUrl = response?.data?.viewerUrl;
|
||||
if (!viewerUrl) throw new Error("Ops не вернул ссылку запуска BIM Viewer.");
|
||||
return viewerUrl as string;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error instanceof Error && !("response" in error)) throw error;
|
||||
throw this.toBeamRequestError(error, "Не удалось открыть BIM-модель.");
|
||||
});
|
||||
}
|
||||
|
||||
async uploadIssueAttachment(
|
||||
@@ -211,19 +175,20 @@ export class IssueAttachmentService extends APIService {
|
||||
uploadProgressHandler?: AxiosRequestConfig["onUploadProgress"]
|
||||
): Promise<TIssueAttachment> {
|
||||
if (isBeamModelFile(file.name)) {
|
||||
const beamViewer = await uploadBeamModelFile(file, {
|
||||
issueId,
|
||||
onUploadProgress: uploadProgressHandler,
|
||||
projectId,
|
||||
workspaceSlug,
|
||||
});
|
||||
|
||||
return this.createBeamIssueAttachmentReference(workspaceSlug, projectId, issueId, {
|
||||
beamViewer,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: getBeamModelMimeType(file),
|
||||
});
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return this.post(
|
||||
`${this.attachmentBasePath(workspaceSlug, projectId, issueId)}/bim-upload/`,
|
||||
formData,
|
||||
{ onUploadProgress: uploadProgressHandler }
|
||||
)
|
||||
.then((response) => {
|
||||
const createResponse: TIssueAttachmentCreateResponse = response?.data;
|
||||
return createResponse.attachment;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw this.toBeamRequestError(error, "Не удалось загрузить модель через Ops.");
|
||||
});
|
||||
}
|
||||
|
||||
const fileMetaData = await getFileMetaDataForUpload(file);
|
||||
@@ -278,15 +243,8 @@ export class IssueAttachmentService extends APIService {
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
assetId: string,
|
||||
attachment?: TIssueAttachment
|
||||
_attachment?: TIssueAttachment
|
||||
): Promise<TIssueAttachment> {
|
||||
const beamViewer = getBeamViewerAttachment(attachment);
|
||||
if (beamViewer) {
|
||||
await deleteBeamModelAsset(beamViewer).catch((error) => {
|
||||
console.warn("BIM storage cleanup failed; deleting OPS attachment reference anyway.", error);
|
||||
});
|
||||
}
|
||||
|
||||
return this.delete(
|
||||
`/api/assets/v2/workspaces/${workspaceSlug}/projects/${projectId}/${this.serviceType}/${issueId}/attachments/${assetId}/`
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user