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 @@
-
+