Sync BIM attachment versions from viewer storage
This commit is contained in:
parent
4ed460ae45
commit
df4767a085
|
|
@ -28,10 +28,12 @@ import {
|
||||||
buildBeamViewerUrl,
|
buildBeamViewerUrl,
|
||||||
dispatchBeamViewerOpenEvent,
|
dispatchBeamViewerOpenEvent,
|
||||||
fetchBeamConversionStatus,
|
fetchBeamConversionStatus,
|
||||||
|
fetchBeamModelVersions,
|
||||||
getBeamModelVersionRecords,
|
getBeamModelVersionRecords,
|
||||||
getBeamVersionViewerUrl,
|
getBeamVersionViewerUrl,
|
||||||
getBeamViewerAttachment,
|
getBeamViewerAttachment,
|
||||||
isBeamModelFile,
|
isBeamModelFile,
|
||||||
|
mergeBeamModelVersionRecordLists,
|
||||||
syncCurrentBeamVersionRecord,
|
syncCurrentBeamVersionRecord,
|
||||||
type TBeamModelVersionRecord,
|
type TBeamModelVersionRecord,
|
||||||
type TBeamViewerAttachment,
|
type TBeamViewerAttachment,
|
||||||
|
|
@ -85,7 +87,8 @@ const withBeamViewerSettingsSrc = (
|
||||||
url: string | null | undefined,
|
url: string | null | undefined,
|
||||||
settingsSrc: string | undefined
|
settingsSrc: string | undefined
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
if (!url || !settingsSrc) return url;
|
if (!url) return undefined;
|
||||||
|
if (!settingsSrc) return url;
|
||||||
try {
|
try {
|
||||||
const parsedUrl = new URL(url, "http://localhost");
|
const parsedUrl = new URL(url, "http://localhost");
|
||||||
if (!parsedUrl.searchParams.has("settingsSrc")) parsedUrl.searchParams.set("settingsSrc", settingsSrc);
|
if (!parsedUrl.searchParams.has("settingsSrc")) parsedUrl.searchParams.set("settingsSrc", settingsSrc);
|
||||||
|
|
@ -241,9 +244,14 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
|
||||||
const [deletingVersionKey, setDeletingVersionKey] = useState<string | null>(null);
|
const [deletingVersionKey, setDeletingVersionKey] = useState<string | null>(null);
|
||||||
const [isVersionUploading, setIsVersionUploading] = useState(false);
|
const [isVersionUploading, setIsVersionUploading] = useState(false);
|
||||||
const [isThumbnailError, setIsThumbnailError] = useState(false);
|
const [isThumbnailError, setIsThumbnailError] = useState(false);
|
||||||
|
const [liveBeamVersions, setLiveBeamVersions] = useState<TBeamModelVersionRecord[]>([]);
|
||||||
const versionUploadInputRef = useRef<HTMLInputElement>(null);
|
const versionUploadInputRef = useRef<HTMLInputElement>(null);
|
||||||
const beamVersions = useMemo(() => getBeamModelVersionRecords(beamViewer, attachment), [attachment, beamViewer]);
|
const localBeamVersions = useMemo(() => getBeamModelVersionRecords(beamViewer, attachment), [attachment, beamViewer]);
|
||||||
const rawVersion = beamViewer?.version ?? attachment?.attributes.version;
|
const beamVersions = useMemo(
|
||||||
|
() => mergeBeamModelVersionRecordLists(localBeamVersions, liveBeamVersions),
|
||||||
|
[localBeamVersions, liveBeamVersions]
|
||||||
|
);
|
||||||
|
const rawVersion = beamViewer?.version ?? (attachment?.attributes as { version?: number | string } | undefined)?.version;
|
||||||
const versionLabel =
|
const versionLabel =
|
||||||
typeof rawVersion === "number"
|
typeof rawVersion === "number"
|
||||||
? `v${rawVersion}`
|
? `v${rawVersion}`
|
||||||
|
|
@ -414,6 +422,29 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!beamViewer?.src) {
|
||||||
|
setLiveBeamVersions([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isMounted = true;
|
||||||
|
fetchBeamModelVersions(beamViewer)
|
||||||
|
.then((history) => {
|
||||||
|
if (!isMounted) return;
|
||||||
|
setLiveBeamVersions(Array.isArray(history.versions) ? history.versions : []);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (!isMounted) return;
|
||||||
|
console.warn("BIM version history unavailable:", error);
|
||||||
|
setLiveBeamVersions([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
};
|
||||||
|
}, [beamViewer?.assetId, beamViewer?.projectId, beamViewer?.src, beamViewer?.versionId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!beamViewer || !beamViewer.src) return;
|
if (!beamViewer || !beamViewer.src) return;
|
||||||
if (beamViewer.conversion?.status === "ready" && storedModelViewerUrl) return;
|
if (beamViewer.conversion?.status === "ready" && storedModelViewerUrl) return;
|
||||||
|
|
@ -973,8 +1004,8 @@ const BeamVersionHistoryModal = (props: TBeamVersionHistoryModal) => {
|
||||||
<div>Статус</div>
|
<div>Статус</div>
|
||||||
<div className="text-right">Действия</div>
|
<div className="text-right">Действия</div>
|
||||||
</div>
|
</div>
|
||||||
{versions
|
{[...versions]
|
||||||
.toSorted((first, second) => second.version - first.version)
|
.sort((first, second) => second.version - first.version)
|
||||||
.map((version) => {
|
.map((version) => {
|
||||||
const viewerUrl = getBeamVersionViewerUrl(version);
|
const viewerUrl = getBeamVersionViewerUrl(version);
|
||||||
const isCurrent = currentVersionId
|
const isCurrent = currentVersionId
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ export class IssueAttachmentService extends APIService {
|
||||||
{
|
{
|
||||||
...nextBeamViewer,
|
...nextBeamViewer,
|
||||||
assetId,
|
assetId,
|
||||||
|
projectId: nextBeamViewer.projectId || previousBeamViewer.projectId,
|
||||||
previewAvailable: nextBeamViewer.previewAvailable,
|
previewAvailable: nextBeamViewer.previewAvailable,
|
||||||
uploadedBy,
|
uploadedBy,
|
||||||
version: nextVersion,
|
version: nextVersion,
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,12 @@ export type TBeamModelVersionRecord = {
|
||||||
downloadUrl: string;
|
downloadUrl: string;
|
||||||
originalFilename: string;
|
originalFilename: string;
|
||||||
previewAvailable: boolean;
|
previewAvailable: boolean;
|
||||||
|
projectId?: string;
|
||||||
sha256?: string;
|
sha256?: string;
|
||||||
size: number;
|
size: number;
|
||||||
|
sourceSrc?: string;
|
||||||
src: string;
|
src: string;
|
||||||
|
status?: string;
|
||||||
type: string;
|
type: string;
|
||||||
uploadedBy?: string;
|
uploadedBy?: string;
|
||||||
uploadedAt: string;
|
uploadedAt: string;
|
||||||
|
|
@ -39,6 +42,7 @@ export type TBeamViewerAttachment = {
|
||||||
downloadUrl: string;
|
downloadUrl: string;
|
||||||
originalFilename: string;
|
originalFilename: string;
|
||||||
previewAvailable: boolean;
|
previewAvailable: boolean;
|
||||||
|
projectId?: string;
|
||||||
sha256?: string;
|
sha256?: string;
|
||||||
src: string;
|
src: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -72,6 +76,16 @@ export type TBeamConversionStatus = {
|
||||||
versionId?: string;
|
versionId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TBeamVersionHistoryResponse = {
|
||||||
|
assetId?: string;
|
||||||
|
currentVersion?: number;
|
||||||
|
currentVersionId?: string;
|
||||||
|
ok: boolean;
|
||||||
|
projectId?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
versions: TBeamModelVersionRecord[];
|
||||||
|
};
|
||||||
|
|
||||||
export const BEAM_VIEWER_OPEN_EVENT = "nodedc:beam-viewer-open";
|
export const BEAM_VIEWER_OPEN_EVENT = "nodedc:beam-viewer-open";
|
||||||
|
|
||||||
export type TBeamViewerOpenEventDetail = {
|
export type TBeamViewerOpenEventDetail = {
|
||||||
|
|
@ -192,7 +206,7 @@ export const getBeamViewerAttachment = (attachment: TIssueAttachment | undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAttachmentVersionNumber = (attachment: TIssueAttachment | undefined): number => {
|
const getAttachmentVersionNumber = (attachment: TIssueAttachment | undefined): number => {
|
||||||
const rawVersion = attachment?.attributes.version;
|
const rawVersion = (attachment?.attributes as { version?: number | string } | undefined)?.version;
|
||||||
if (typeof rawVersion === "number" && Number.isFinite(rawVersion)) return rawVersion;
|
if (typeof rawVersion === "number" && Number.isFinite(rawVersion)) return rawVersion;
|
||||||
if (typeof rawVersion === "string") {
|
if (typeof rawVersion === "string") {
|
||||||
const parsed = Number.parseInt(rawVersion.replace(/^v/i, ""), 10);
|
const parsed = Number.parseInt(rawVersion.replace(/^v/i, ""), 10);
|
||||||
|
|
@ -222,9 +236,12 @@ export const createBeamModelVersionRecord = (
|
||||||
downloadUrl: beamViewer.downloadUrl,
|
downloadUrl: beamViewer.downloadUrl,
|
||||||
originalFilename: beamViewer.originalFilename,
|
originalFilename: beamViewer.originalFilename,
|
||||||
previewAvailable: beamViewer.previewAvailable,
|
previewAvailable: beamViewer.previewAvailable,
|
||||||
|
projectId: beamViewer.projectId,
|
||||||
sha256: beamViewer.sha256,
|
sha256: beamViewer.sha256,
|
||||||
size: options.size ?? beamViewer.conversion?.size ?? Number(options.attachment?.attributes.size ?? 0),
|
size: options.size ?? beamViewer.conversion?.size ?? Number(options.attachment?.attributes.size ?? 0),
|
||||||
|
sourceSrc: beamViewer.conversion?.sourceSrc || beamViewer.src,
|
||||||
src: beamViewer.src,
|
src: beamViewer.src,
|
||||||
|
status: beamViewer.conversion?.status || "ready",
|
||||||
type: beamViewer.type,
|
type: beamViewer.type,
|
||||||
uploadedBy: beamViewer.uploadedBy || options.uploadedBy || options.attachment?.created_by,
|
uploadedBy: beamViewer.uploadedBy || options.uploadedBy || options.attachment?.created_by,
|
||||||
uploadedAt: beamViewer.uploadedAt,
|
uploadedAt: beamViewer.uploadedAt,
|
||||||
|
|
@ -251,7 +268,7 @@ export const getBeamModelVersionRecords = (
|
||||||
|
|
||||||
return versions
|
return versions
|
||||||
.filter((version) => typeof version?.version === "number" && Number.isFinite(version.version))
|
.filter((version) => typeof version?.version === "number" && Number.isFinite(version.version))
|
||||||
.toSorted((first, second) => first.version - second.version);
|
.sort((first, second) => first.version - second.version);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mergeBeamModelVersionRecords = (
|
export const mergeBeamModelVersionRecords = (
|
||||||
|
|
@ -268,11 +285,26 @@ export const mergeBeamModelVersionRecords = (
|
||||||
) ||
|
) ||
|
||||||
nextBeamViewer.versions?.[0] ||
|
nextBeamViewer.versions?.[0] ||
|
||||||
createBeamModelVersionRecord(nextBeamViewer);
|
createBeamModelVersionRecord(nextBeamViewer);
|
||||||
return [...previousVersions.filter((version) => !isSameBeamVersionRecord(version, nextRecord)), nextRecord].toSorted(
|
return [...previousVersions.filter((version) => !isSameBeamVersionRecord(version, nextRecord)), nextRecord].sort(
|
||||||
(first, second) => first.version - second.version
|
(first, second) => first.version - second.version
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mergeBeamModelVersionRecordLists = (
|
||||||
|
firstVersions: TBeamModelVersionRecord[] = [],
|
||||||
|
secondVersions: TBeamModelVersionRecord[] = []
|
||||||
|
): TBeamModelVersionRecord[] => {
|
||||||
|
const byKey = new Map<string, TBeamModelVersionRecord>();
|
||||||
|
[...firstVersions, ...secondVersions].forEach((version) => {
|
||||||
|
const key = version.versionId || `version-${version.version}`;
|
||||||
|
const previous = byKey.get(key);
|
||||||
|
byKey.set(key, { ...(previous || {}), ...version });
|
||||||
|
});
|
||||||
|
return [...byKey.values()]
|
||||||
|
.filter((version) => typeof version?.version === "number" && Number.isFinite(version.version))
|
||||||
|
.sort((first, second) => first.version - second.version);
|
||||||
|
};
|
||||||
|
|
||||||
export const syncCurrentBeamVersionRecord = (
|
export const syncCurrentBeamVersionRecord = (
|
||||||
beamViewer: TBeamViewerAttachment,
|
beamViewer: TBeamViewerAttachment,
|
||||||
attachment?: TIssueAttachment
|
attachment?: TIssueAttachment
|
||||||
|
|
@ -287,6 +319,14 @@ export const syncCurrentBeamVersionRecord = (
|
||||||
|
|
||||||
export const getBeamVersionViewerUrl = (version: TBeamModelVersionRecord): string | undefined => {
|
export const getBeamVersionViewerUrl = (version: TBeamModelVersionRecord): string | undefined => {
|
||||||
if (version.viewerUrl) return version.viewerUrl;
|
if (version.viewerUrl) return version.viewerUrl;
|
||||||
|
if (!version.conversion && version.previewAvailable && version.src) {
|
||||||
|
return buildBeamViewerUrl({
|
||||||
|
name: version.originalFilename,
|
||||||
|
settingsSrc: version.sourceSrc || version.src,
|
||||||
|
src: version.src,
|
||||||
|
type: version.type,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (version.conversion?.status !== "ready" || !version.conversion.artifactSrc) return undefined;
|
if (version.conversion?.status !== "ready" || !version.conversion.artifactSrc) return undefined;
|
||||||
const artifactType = version.conversion.artifactType || version.conversion.targetFormat || "gltf";
|
const artifactType = version.conversion.artifactType || version.conversion.targetFormat || "gltf";
|
||||||
return buildBeamViewerUrl({
|
return buildBeamViewerUrl({
|
||||||
|
|
@ -315,6 +355,25 @@ export const fetchBeamConversionStatus = async (beamViewer: TBeamViewerAttachmen
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchBeamModelVersions = async (
|
||||||
|
beamViewer: TBeamViewerAttachment
|
||||||
|
): Promise<TBeamVersionHistoryResponse> => {
|
||||||
|
const versionsUrl = new URL("/api/uploads/versions", `${getBeamApiBaseUrl()}/`);
|
||||||
|
if (beamViewer.projectId && beamViewer.assetId) {
|
||||||
|
versionsUrl.searchParams.set("projectId", beamViewer.projectId);
|
||||||
|
versionsUrl.searchParams.set("assetId", beamViewer.assetId);
|
||||||
|
} else {
|
||||||
|
versionsUrl.searchParams.set("src", toBeamRelativeUploadSrc(beamViewer.src));
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(versionsUrl.toString(), { credentials: "include" });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`BIM version history failed: HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await response.json()) as TBeamVersionHistoryResponse;
|
||||||
|
};
|
||||||
|
|
||||||
export const uploadBeamModelFile = (
|
export const uploadBeamModelFile = (
|
||||||
file: File,
|
file: File,
|
||||||
options: {
|
options: {
|
||||||
|
|
@ -369,6 +428,7 @@ export const uploadBeamModelFile = (
|
||||||
assetId?: string;
|
assetId?: string;
|
||||||
conversion?: TBeamViewerAttachment["conversion"];
|
conversion?: TBeamViewerAttachment["conversion"];
|
||||||
originalFilename?: string;
|
originalFilename?: string;
|
||||||
|
projectId?: string;
|
||||||
sha256?: string;
|
sha256?: string;
|
||||||
src?: string;
|
src?: string;
|
||||||
uploadedAt?: string;
|
uploadedAt?: string;
|
||||||
|
|
@ -387,6 +447,7 @@ export const uploadBeamModelFile = (
|
||||||
downloadUrl: downloadUrl.toString(),
|
downloadUrl: downloadUrl.toString(),
|
||||||
originalFilename: response.originalFilename || file.name,
|
originalFilename: response.originalFilename || file.name,
|
||||||
previewAvailable: !!directViewerType,
|
previewAvailable: !!directViewerType,
|
||||||
|
projectId: response.projectId,
|
||||||
sha256: response.sha256,
|
sha256: response.sha256,
|
||||||
src: downloadUrl.toString(),
|
src: downloadUrl.toString(),
|
||||||
type,
|
type,
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,12 @@ export type TBeamModelVersionRecord = {
|
||||||
downloadUrl: string;
|
downloadUrl: string;
|
||||||
originalFilename: string;
|
originalFilename: string;
|
||||||
previewAvailable: boolean;
|
previewAvailable: boolean;
|
||||||
|
projectId?: string;
|
||||||
sha256?: string;
|
sha256?: string;
|
||||||
size: number;
|
size: number;
|
||||||
|
sourceSrc?: string;
|
||||||
src: string;
|
src: string;
|
||||||
|
status?: string;
|
||||||
type: string;
|
type: string;
|
||||||
uploadedBy?: string;
|
uploadedBy?: string;
|
||||||
uploadedAt: string;
|
uploadedAt: string;
|
||||||
|
|
@ -44,6 +47,7 @@ export type TBeamViewerAttachment = {
|
||||||
downloadUrl: string;
|
downloadUrl: string;
|
||||||
originalFilename: string;
|
originalFilename: string;
|
||||||
previewAvailable: boolean;
|
previewAvailable: boolean;
|
||||||
|
projectId?: string;
|
||||||
sha256?: string;
|
sha256?: string;
|
||||||
src: string;
|
src: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue