diff --git a/frontend/dcViewer.js b/frontend/dcViewer.js index 7bf0c6d..ce1c1da 100644 --- a/frontend/dcViewer.js +++ b/frontend/dcViewer.js @@ -303,6 +303,7 @@ let commentContextTarget = null; let commentModalMode = "create"; let commentModalCommentId = null; let commentModalExpanded = false; +let commentSaveInFlight = false; let activeCommentId = null; let hoverCommentId = null; let commentDraftAttachments = []; @@ -570,6 +571,17 @@ const extractUploadIdentityFromSrc = (src) => { return match ? { projectId: match[1], assetId: match[2] } : {}; }; +const getFallbackCommentSourceSrc = (entry) => { + const params = new URLSearchParams(window.location.search); + const querySrc = normalizeCommentSourceSrc(params.get("src") || params.get("url") || ""); + if (querySrc) return querySrc; + const modelId = entry?.model?.id || entry?.id || "model"; + if (currentProjectMeta?.id) { + return `project:${currentProjectMeta.id}:model:${modelId}`; + } + return `viewer:${window.location.pathname}${window.location.search || ""}:model:${modelId}`; +}; + const getCommentSourceForEntry = (entry = getPrimaryModelEntry()) => { if (!entry) return null; const meta = entry.meta || {}; @@ -578,7 +590,7 @@ const getCommentSourceForEntry = (entry = getPrimaryModelEntry()) => { normalizeCommentSourceSrc(meta.artifactSrc) || normalizeCommentSourceSrc(meta.settingsSrc) || normalizeCommentSourceSrc(entry.src) || - ""; + getFallbackCommentSourceSrc(entry); const uploadIdentity = extractUploadIdentityFromSrc(src); return { src, @@ -1095,12 +1107,14 @@ const hideCommentSubitemMenu = () => { const showCommentSubitemMenu = () => { if (!commentSubitemMenu || !commentAddSubitemButton) return; + if (commentSubitemMenu.parentElement !== document.body) { + document.body.appendChild(commentSubitemMenu); + } commentSubitemMenu.classList.remove("hidden"); const buttonRect = commentAddSubitemButton.getBoundingClientRect(); const width = commentSubitemMenu.offsetWidth || 276; - const height = commentSubitemMenu.offsetHeight || 94; - const x = Math.max(8, Math.min(window.innerWidth - width - 8, buttonRect.left + (buttonRect.width - width) / 2)); - const y = Math.max(8, Math.min(window.innerHeight - height - 8, buttonRect.bottom + 8)); + const x = Math.max(8, Math.min(window.innerWidth - width - 8, buttonRect.left)); + const y = buttonRect.bottom + 8; commentSubitemMenu.style.left = `${x}px`; commentSubitemMenu.style.top = `${y}px`; }; @@ -1311,6 +1325,7 @@ const syncCommentModalAfterSave = (comment) => { const applyCommentModal = async ({ closeOnSave = false } = {}) => { if (isGuestMode()) return; + if (commentSaveInFlight) return; const title = commentTitleInput?.value?.trim() || ""; const body = commentBodyInput?.value?.trim() || ""; const replyBody = commentReplyInput?.value?.trim() || ""; @@ -1319,6 +1334,7 @@ const applyCommentModal = async ({ closeOnSave = false } = {}) => { if (commentModalError) commentModalError.textContent = "Не выбрана точка комментария"; return; } + commentSaveInFlight = true; if (commentApplyButton) { commentApplyButton.disabled = true; commentApplyButton.textContent = "Сохранение..."; @@ -1382,8 +1398,11 @@ const applyCommentModal = async ({ closeOnSave = false } = {}) => { } } catch (err) { console.error(err); - if (commentModalError) commentModalError.textContent = err.message || "Не удалось сохранить комментарий"; + const message = err.message || "Не удалось сохранить комментарий"; + if (commentModalError) commentModalError.textContent = message; + setStatus(message); } finally { + commentSaveInFlight = false; if (commentApplyButton) { commentApplyButton.disabled = false; commentApplyButton.textContent = "Применить"; @@ -1435,6 +1454,18 @@ const setupDraggableFixedWindow = (element, handle) => { handle.addEventListener("pointercancel", endDrag); }; +const bindFooterButtonAction = (button, handler) => { + if (!button || typeof handler !== "function") return; + button.addEventListener("click", handler); + button.addEventListener("pointerup", (event) => { + const isPrimaryPointer = event.pointerType !== "mouse" || event.button === 0; + if (!isPrimaryPointer || button.disabled) return; + event.preventDefault(); + event.stopPropagation(); + handler(event); + }, true); +}; + const applyRuntimeMode = () => { document.body.dataset.viewerMode = viewerMode; if (bottomToolbar) { @@ -5217,10 +5248,10 @@ const loadProjectSnapshot = async (project) => { hideCommentContextMenu(); }); commentModalClose?.addEventListener("click", closeCommentModal); - commentCancelButton?.addEventListener("click", closeCommentModal); + bindFooterButtonAction(commentCancelButton, closeCommentModal); commentModalExpandButton?.addEventListener("click", () => setCommentModalExpanded(!commentModalExpanded)); - commentApplyButton?.addEventListener("click", () => applyCommentModal({ closeOnSave: false })); - commentSaveButton?.addEventListener("click", () => applyCommentModal({ closeOnSave: true })); + bindFooterButtonAction(commentApplyButton, () => applyCommentModal({ closeOnSave: false })); + bindFooterButtonAction(commentSaveButton, () => applyCommentModal({ closeOnSave: true })); commentApplyInlineButton?.addEventListener("click", () => applyCommentModal({ closeOnSave: false })); commentModalBackdrop?.addEventListener("click", (event) => { if (event.target === commentModalBackdrop) closeCommentModal(); diff --git a/frontend/index.html b/frontend/index.html index 8bb1963..c18c127 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -570,6 +570,6 @@ - +