From 2aab1bc9b466f3176f34f6ab9c7fe73046da0b9c Mon Sep 17 00:00:00 2001 From: CODEX Date: Sat, 20 Jun 2026 18:58:39 +0300 Subject: [PATCH] Show BIM comment API failures --- frontend/dcViewer.css | 6 +++++ frontend/dcViewer.js | 57 +++++++++++++++++++++++++++++++++++-------- frontend/index.html | 4 +-- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/frontend/dcViewer.css b/frontend/dcViewer.css index 3ed8c96..cabe8f2 100644 --- a/frontend/dcViewer.css +++ b/frontend/dcViewer.css @@ -2197,6 +2197,12 @@ header { border-top: 1px solid rgba(255, 255, 255, 0.04); } +.comment-card-modal__footer-error { + grid-column: 1 / -1; + min-height: 0; + padding: 0 2px 2px; +} + .comment-card-modal__footer .primary-btn, .comment-card-modal__footer .secondary-btn { min-width: 0; diff --git a/frontend/dcViewer.js b/frontend/dcViewer.js index ce1c1da..2ec67e1 100644 --- a/frontend/dcViewer.js +++ b/frontend/dcViewer.js @@ -544,6 +544,13 @@ const formatDateTime = (value) => { } }; +const setCommentModalError = (message = "") => { + if (!commentModalError) return; + const text = String(message || "").trim(); + commentModalError.textContent = text; + commentModalError.hidden = !text; +}; + const bringFloatingWindowToFront = (id) => { if (!id) return; floatingWindowOrder = [...floatingWindowOrder.filter((item) => item !== id), id]; @@ -938,8 +945,8 @@ const readAttachmentFiles = async (fileList) => { .filter((file) => file && file.size <= COMMENT_FILE_LIMIT_BYTES) .slice(0, 8); const skipped = Array.from(fileList || []).filter((file) => file && file.size > COMMENT_FILE_LIMIT_BYTES).length; - if (skipped && commentModalError) { - commentModalError.textContent = `Файлы больше 50 МБ не добавлены: ${skipped}`; + if (skipped) { + setCommentModalError(`Файлы больше 50 МБ не добавлены: ${skipped}`); } const readOne = (file) => new Promise((resolve) => { const reader = new FileReader(); @@ -1215,7 +1222,7 @@ const openCommentModalForTarget = () => { ? `Последнее редактирование: новая точка ${x.toFixed(2)}, ${y.toFixed(2)}, ${z.toFixed(2)}` : "Последнее редактирование: новый комментарий"; } - if (commentModalError) commentModalError.textContent = ""; + setCommentModalError(""); resetCommentModalAttachments(); renderCommentThread(null); commentModalBackdrop?.classList.remove("hidden"); @@ -1242,7 +1249,7 @@ const openCommentModalForComment = (commentId) => { if (commentModalEditedMeta) { commentModalEditedMeta.textContent = `Последнее редактирование: ${formatDateTime(comment.updatedAt || comment.createdAt)}`; } - if (commentModalError) commentModalError.textContent = ""; + setCommentModalError(""); resetCommentModalAttachments(); commentDraftSubitems = Array.isArray(comment.subitems) ? comment.subitems.map((item) => ({ ...item })) @@ -1266,7 +1273,7 @@ const closeCommentModal = () => { activeCommentId = null; commentModalMode = "create"; commentModalCommentId = null; - if (commentModalError) commentModalError.textContent = ""; + setCommentModalError(""); renderCommentsPanel(); renderCommentTargets(); }; @@ -1329,9 +1336,9 @@ const applyCommentModal = async ({ closeOnSave = false } = {}) => { const title = commentTitleInput?.value?.trim() || ""; const body = commentBodyInput?.value?.trim() || ""; const replyBody = commentReplyInput?.value?.trim() || ""; - if (commentModalError) commentModalError.textContent = ""; + setCommentModalError(""); if (commentModalMode === "create" && !commentContextTarget?.worldPos) { - if (commentModalError) commentModalError.textContent = "Не выбрана точка комментария"; + setCommentModalError("Не выбрана точка комментария"); return; } commentSaveInFlight = true; @@ -1399,7 +1406,7 @@ const applyCommentModal = async ({ closeOnSave = false } = {}) => { } catch (err) { console.error(err); const message = err.message || "Не удалось сохранить комментарий"; - if (commentModalError) commentModalError.textContent = message; + setCommentModalError(message); setStatus(message); } finally { commentSaveInFlight = false; @@ -3627,8 +3634,38 @@ const guessTypeFromName = (name) => { return null; }; +const isApiRequestUrl = (url) => { + try { + return new URL(url, window.location.href).pathname.startsWith("/api/"); + } catch (_) { + return String(url || "").startsWith("/api/"); + } +}; + +const formatFetchFailureMessage = (url, res, text, data) => { + const apiRequest = isApiRequestUrl(url); + if (apiRequest && res.status === 401) { + return "BIM API не принял запрос: нужна авторизация."; + } + if (apiRequest && res.status === 404) { + return "BIM API недоступен на этом адресе. Открой viewer через node server/index.js, не через статический http-server."; + } + if (apiRequest && /^\s* { - const res = await fetch(url, options); + let res = null; + try { + res = await fetch(url, options); + } catch (err) { + if (isApiRequestUrl(url)) { + throw new Error(`BIM API недоступен: ${err?.message || "network error"}`); + } + throw err; + } const text = await res.text(); let data = null; try { @@ -3637,7 +3674,7 @@ const fetchJSON = async (url, options = {}) => { // ignore parse errors, text will be used for message } if (!res.ok) { - const message = (data && data.message) || text || `HTTP ${res.status}`; + const message = formatFetchFailureMessage(url, res, text, data); throw new Error(message); } return data; diff --git a/frontend/index.html b/frontend/index.html index c18c127..2b27f75 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -413,9 +413,9 @@
-