import { truncateString } from "./utils.js"; import { setupObject, setupCamera, toastHelper } from './viewer-utils.js'; import { core } from './core.js'; import { t } from "./i18n-utils.js"; function escapeHtml(value) { return String(value ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function buildMetadataRow(label, value) { if (!label || typeof value === "undefined" || value === null || value === "") { return ""; } return ( '
' ); } /** * Formats WissKI metadata labels and values for display. */ export function addWissKIMetadata(label, value) { if (typeof label !== "undefined" && typeof value !== "undefined") { var _str = ""; label = label.replace("wisski_path_3d_model__", ""); switch (label) { case "title": _str = t("metadata.title", "Title"); break; case "author_name": _str = t("metadata.author", "Author"); break; case "author_affiliation": _str = t("metadata.authorAffiliation", "Author affiliation"); break; case "license": _str = t("metadata.license", "License"); break; case "description": _str = t("metadata.description", "Description"); break; case "object_type": _str = t("metadata.objectType", "Object type"); break; case "reconstruction_authors": _str = t("metadata.reconstructionAuthors", "Reconstruction authors"); break; case "reconstruction_period": _str = t("metadata.reconstructionPeriod", "Reconstruction period"); break; default: _str = ""; break; } if (_str !== "") { return buildMetadataRow(_str, value); } } } export function lilGUIhasFolder(folder, name) { return folder.folders.some(f => f._title === name); } export function lilGUIgetFolder(gui, name) { return gui?.folders?.find(f => f._title === name) || null; } /** * Expands/collapses the metadata panel. */ export function expandMetadata() { const content = document.getElementById("metadata-content"); const toggle = document.getElementById("metadata-collapse"); const card = document.getElementById("metadata-card"); if (!content || !toggle) return; const expanded = content.classList.toggle("expanded"); toggle.classList.toggle("metadata-collapsed", !expanded); card?.classList.toggle("metadata-open", expanded); // accessibility toggle.setAttribute("aria-expanded", expanded); if (!expanded) { card?.classList.remove("metadata-card-overflowing"); content.querySelectorAll(".metadata-row-pinned").forEach((row) => { row.classList.remove("metadata-row-pinned"); }); return; } updateMetadataOverflow(); } function updateMetadataOverflow() { const content = document.getElementById("metadata-content"); const card = document.getElementById("metadata-card"); if (!content || !card || !content.classList.contains("expanded")) return; const hasOverflow = content.scrollHeight - content.clientHeight > 8; card.classList.toggle("metadata-card-overflowing", hasOverflow); content.querySelectorAll(".metadata-row").forEach((row) => { const value = row.querySelector(".metadata-value"); if (!value) return; const wasPinned = row.classList.contains("metadata-row-pinned"); row.classList.remove("metadata-row-pinned", "metadata-row-expandable"); const isExpandable = value.scrollHeight - value.clientHeight > 4; row.classList.toggle("metadata-row-expandable", isExpandable); if (wasPinned && isExpandable) { row.classList.add("metadata-row-pinned"); } }); } function bindMetadataInteractions() { if (core.metadataContainer.dataset.boundCollapse === "true") return; core.metadataContainer.addEventListener("click", (e) => { const toggle = e.target.closest("#metadata-collapse"); if (toggle) { expandMetadata(e); return; } const card = document.getElementById("metadata-card"); const content = document.getElementById("metadata-content"); if (!card || !content || !content.classList.contains("expanded")) return; const row = e.target.closest(".metadata-row"); if (!row) return; const willPin = !row.classList.contains("metadata-row-pinned"); content.querySelectorAll(".metadata-row-pinned").forEach((pinnedRow) => { pinnedRow.classList.remove("metadata-row-pinned"); }); if (willPin) row.classList.add("metadata-row-pinned"); }); window.addEventListener("resize", updateMetadataOverflow); core.metadataContainer.dataset.boundCollapse = "true"; } /** * Appends metadata HTML to the DOM. */ export function appendMetadata( metadataContent ) { core.metadataContainer.innerHTML = metadataContent; if (!core.container.contains(core.metadataContainer)) { core.container.appendChild(core.metadataContainer); } } async function fetchEntityMetadata() { if (!core.CONFIG.entity.metadata.sourceType || core.CONFIG.entity.metadata.url === "") { return ""; } const entityComponent = encodeURIComponent(core.CONFIG.entity.id) ?? core.CONFIG.entity.id ?? typeof core.CONFIG.entity.id === "undefined" ? "" : ""; if (!entityComponent) { console.warn("Entity ID is missing or invalid. Skipping metadata fetch."); return ""; } const metadataUrl = core.CONFIG.entity.metadata.url + entityComponent; try { const response = await fetch(metadataUrl, { cache: "no-cache" }); if (!response.ok) { console.warn("Metadata request failed with status:", response.status); return ""; } const responseText = await response.text(); try { const jsonData = JSON.parse(responseText); const record = Array.isArray(jsonData) ? jsonData[0] : jsonData; if (!record || typeof record !== "object") { return ""; } console.log("Processing JSON metadata:", record); const jsonFieldMap = { title: "title", reconstruction_authors: "author_name", reconstruction_authors_affiliation: "author_affiliation", reconstruction_license: "license", reconstruction_time_frame: "reconstruction_period", object_description: "description", object_type: "object_type", }; let entityMetadataContent = ""; for (const [jsonField, metadataLabel] of Object.entries(jsonFieldMap)) { if (record[jsonField]) { const fetchedValue = addWissKIMetadata(metadataLabel, record[jsonField]); if (typeof fetchedValue !== "undefined") { entityMetadataContent += fetchedValue; } } } return entityMetadataContent; } catch (_jsonError) { const parser = new DOMParser(); const doc = parser.parseFromString(responseText, "application/xml"); if (doc.documentElement.tagName === "parsererror") { console.error("XML parsing error:", doc.documentElement.textContent); return ""; } let entityMetadataContent = ""; if (doc.documentElement.childNodes.length > 0) { var data = doc.documentElement.childNodes[0].childNodes; if (data !== undefined) { for (var i = 0; i < data.length; i++) { var fetchedValue = addWissKIMetadata(data[i].tagName, data[i].textContent); if (typeof fetchedValue !== "undefined") { entityMetadataContent += fetchedValue; } } } } return entityMetadataContent; } } catch (error) { console.error("Error processing metadata:", error); return ""; } } export function fetchMetadata(_object, _type) { if (!_object?.geometry) return 0; const indexedCount = _object.geometry.index?.count; const positionCount = _object.geometry.attributes?.position?.count ?? 0; switch (_type) { case "vertices": return positionCount; case "faces": return (indexedCount ?? positionCount) / 3; default: return 0; } } /** * Handles metadata response and builds the metadata UI. */ export async function handleMetadataResponse( data, metadata, object, ) { Viewer.clearHierarchySubmenu(); var tempArray = []; if (Array.isArray(object)) { setupObject(object[0], data); await setupCamera(object[0], data); } else if (object.name === "Scene" || object.children.length > 0 || object.type == "Mesh") { setupObject(object, data); object.traverse(function (child) { if (child.isMesh) { metadata["vertices"] += fetchMetadata(child, "vertices"); metadata["faces"] += fetchMetadata(child, "faces"); if (child.name === "") child.name = "Mesh"; var shortChildName = truncateString(child.name, 35); Viewer.addHierarchySubmenuItem(shortChildName, child.id); child.traverse(function (children) { if (children.isMesh && children.name !== child.name) { if (children.name === "") children.name = "ChildrenMesh"; var shortChildrenName = truncateString(children.name, 35); Viewer.addHierarchySubmenuItem(shortChildrenName, children.id); } }); } }); await setupCamera(object, data); } else { setupObject(object, data); await setupCamera(object, data); metadata["vertices"] += fetchMetadata(object, "vertices"); metadata["faces"] += fetchMetadata(object, "faces"); if (object.name === "") { Viewer.addHierarchySubmenuItem("Mesh", object.id); object.name = object.id; } else { Viewer.addHierarchySubmenuItem(object.name, object.id); } } if (!core.metadataContainer) { core.metadataContainer = document.createElement("div"); core.metadataContainer.id = "metadata-container"; } core.metadataContainer.setAttribute("data-viewer-theme", core.container?.closest(".viewer-wrapper")?.getAttribute("data-viewer-theme") || "dark"); var metadataContent = '