|
988 | 988 | const storyRoot = document.getElementById("__ml_story_recit_root"); |
989 | 989 | if (storyRoot && typeof renderRecit === "function") { |
990 | 990 | storyRoot.innerHTML = renderRecit() || ""; |
| 991 | + renderStorySubjectFallback(storyRoot); |
991 | 992 | } |
992 | 993 | } catch (e) { |
993 | 994 | console.warn("story recit render error:", e); |
|
1005 | 1006 | } catch (e) { |
1006 | 1007 | console.warn("theme grid render error:", e); |
1007 | 1008 | } |
| 1009 | + renderExplorerFocus(); |
1008 | 1010 | } |
1009 | 1011 | window.renderStoryTier = renderStoryTier; |
1010 | 1012 |
|
| 1013 | + function escapeHtml(value) { |
| 1014 | + return String(value || "") |
| 1015 | + .replace(/&/g, "&") |
| 1016 | + .replace(/</g, "<") |
| 1017 | + .replace(/>/g, ">") |
| 1018 | + .replace(/"/g, """) |
| 1019 | + .replace(/'/g, "'"); |
| 1020 | + } |
| 1021 | + |
| 1022 | + function explorerRelatedWorks(snapshot, selectedId) { |
| 1023 | + const graph = snapshot.graphe || {}; |
| 1024 | + const nodes = graph.noeuds || []; |
| 1025 | + const relations = graph.relations || []; |
| 1026 | + const nodeById = new Map(nodes.map((node) => [node.id, node])); |
| 1027 | + return relations |
| 1028 | + .filter((relation) => relation.source === selectedId && ["subject_of", "represente", "houses_work", "expose"].includes(relation.type)) |
| 1029 | + .map((relation) => nodeById.get(relation.target)) |
| 1030 | + .filter(Boolean) |
| 1031 | + .slice(0, 12); |
| 1032 | + } |
| 1033 | + |
| 1034 | + function subjectSelectionFromSnapshot(snapshot) { |
| 1035 | + const selectedId = snapshot.entite_selectionnee_id || ""; |
| 1036 | + const selected = snapshot.entite_selectionnee || {}; |
| 1037 | + const selectedRecord = selected.donnees || {}; |
| 1038 | + const selectedType = normaliseDetailEntityType(selected.type || snapshot.entite_selectionnee_type || ""); |
| 1039 | + if (!selectedId || (selectedType !== "sujet" && selectedType !== "subject")) { |
| 1040 | + return null; |
| 1041 | + } |
| 1042 | + const label = fieldValue(selectedRecord, "subjectLabel") || fieldValue(selectedRecord, "label") || runtimeState.selectedEntity.name || selectedId; |
| 1043 | + return { selectedId, selectedRecord, selectedType, label }; |
| 1044 | + } |
| 1045 | + |
| 1046 | + function renderStorySubjectFallback(root) { |
| 1047 | + const snapshot = currentSnapshot(); |
| 1048 | + const subject = subjectSelectionFromSnapshot(snapshot); |
| 1049 | + if (!subject) { |
| 1050 | + return; |
| 1051 | + } |
| 1052 | + |
| 1053 | + const relatedWorks = explorerRelatedWorks(snapshot, subject.selectedId); |
| 1054 | + const count = Number(subject.selectedRecord.artworkCount || relatedWorks.length || 0); |
| 1055 | + let html = '<article class="recit-card recit-sujet story-subject-result">'; |
| 1056 | + html += '<header class="recit-header">'; |
| 1057 | + html += '<span class="recit-badge">Theme</span>'; |
| 1058 | + html += '<h2 class="recit-titre">' + escapeHtml(subject.label || subject.selectedId) + '</h2>'; |
| 1059 | + html += '</header>'; |
| 1060 | + html += '<div class="recit-corps">'; |
| 1061 | + html += '<p class="recit-intro">' + (count ? escapeHtml(count + " artworks found for this theme.") : "Loading artworks for this theme...") + '</p>'; |
| 1062 | + if (relatedWorks.length) { |
| 1063 | + html += '<ul class="recit-liste-oeuvres">'; |
| 1064 | + relatedWorks.slice(0, 10).forEach((work) => { |
| 1065 | + html += '<li class="recit-oeuvre">' + escapeHtml(work.etiquette || work.label || work.id) + '</li>'; |
| 1066 | + }); |
| 1067 | + html += '</ul>'; |
| 1068 | + } |
| 1069 | + html += '</div>'; |
| 1070 | + html += '<footer class="recit-footer">'; |
| 1071 | + html += '<button type="button" class="recit-btn-explorer" data-explorer-open-observatory>Explore in constellation</button>'; |
| 1072 | + html += '</footer>'; |
| 1073 | + html += '</article>'; |
| 1074 | + root.innerHTML = html; |
| 1075 | + } |
| 1076 | + |
| 1077 | + function renderExplorerFocus() { |
| 1078 | + const root = document.getElementById("__ml_explorer_focus_root"); |
| 1079 | + if (!root) { |
| 1080 | + return; |
| 1081 | + } |
| 1082 | + |
| 1083 | + const snapshot = currentSnapshot(); |
| 1084 | + const selectedId = snapshot.entite_selectionnee_id || ""; |
| 1085 | + const selected = snapshot.entite_selectionnee || {}; |
| 1086 | + const selectedRecord = selected.donnees || {}; |
| 1087 | + const selectedType = normaliseDetailEntityType(selected.type || snapshot.entite_selectionnee_type || ""); |
| 1088 | + const label = fieldValue(selectedRecord, "subjectLabel") || fieldValue(selectedRecord, "museumLabel") || fieldValue(selectedRecord, "movementLabel") || fieldValue(selectedRecord, "artistLabel") || fieldValue(selectedRecord, "artworkLabel") || fieldValue(selectedRecord, "label") || runtimeState.selectedEntity.name || selectedId; |
| 1089 | + |
| 1090 | + if (!selectedId) { |
| 1091 | + root.innerHTML = [ |
| 1092 | + '<div class="explorer-empty">', |
| 1093 | + '<strong>Choose a theme or search result</strong>', |
| 1094 | + '<span>Explorer will show the selected entity and its first useful connections here.</span>', |
| 1095 | + '</div>' |
| 1096 | + ].join(""); |
| 1097 | + return; |
| 1098 | + } |
| 1099 | + |
| 1100 | + const relatedWorks = explorerRelatedWorks(snapshot, selectedId); |
| 1101 | + const count = Number(selectedRecord.artworkCount || relatedWorks.length || 0); |
| 1102 | + let html = '<article class="explorer-focus-card">'; |
| 1103 | + html += '<header class="explorer-focus-header">'; |
| 1104 | + html += '<div><span class="eyebrow">Current selection</span>'; |
| 1105 | + html += '<h3>' + escapeHtml(label || selectedId) + '</h3></div>'; |
| 1106 | + html += '<span class="chip">' + escapeHtml(selectedType || "entity") + '</span>'; |
| 1107 | + html += '</header>'; |
| 1108 | + |
| 1109 | + if (selectedType === "sujet" || selectedType === "subject") { |
| 1110 | + html += '<p class="explorer-focus-summary">' + (count ? escapeHtml(count + " artworks found for this theme.") : "Theme loaded. Try another theme if Wikidata has no direct artwork matches.") + '</p>'; |
| 1111 | + } else { |
| 1112 | + html += '<p class="explorer-focus-summary">' + escapeHtml(runtimeState.selectedEntity.meta || "Selection loaded.") + '</p>'; |
| 1113 | + } |
| 1114 | + |
| 1115 | + if (relatedWorks.length) { |
| 1116 | + html += '<div class="explorer-work-list" aria-label="Related artworks">'; |
| 1117 | + relatedWorks.forEach((work) => { |
| 1118 | + html += '<button type="button" class="explorer-work-item" data-explorer-entity-id="' + escapeHtml(work.id) + '" data-explorer-entity-type="artwork">'; |
| 1119 | + html += '<strong>' + escapeHtml(work.etiquette || work.label || work.id) + '</strong>'; |
| 1120 | + html += '<small>artwork</small>'; |
| 1121 | + html += '</button>'; |
| 1122 | + }); |
| 1123 | + html += '</div>'; |
| 1124 | + } |
| 1125 | + |
| 1126 | + html += '<div class="explorer-focus-actions">'; |
| 1127 | + html += '<button type="button" class="ghost-button" data-explorer-open-observatory>See constellation</button>'; |
| 1128 | + html += '</div>'; |
| 1129 | + html += '</article>'; |
| 1130 | + root.innerHTML = html; |
| 1131 | + } |
| 1132 | + |
| 1133 | + window.renderExplorerFocus = renderExplorerFocus; |
| 1134 | + |
1011 | 1135 | function updateTierInUrl(tier) { |
1012 | 1136 | if (!window.history || typeof window.history.replaceState !== "function") { |
1013 | 1137 | return; |
@@ -1042,6 +1166,7 @@ function setActiveTier(tier, options = {}) { |
1042 | 1166 | } |
1043 | 1167 | } else if (tier === "explorer") { |
1044 | 1168 | renderStoryTier(); |
| 1169 | + renderExplorerFocus(); |
1045 | 1170 | } else if (tier === "observatory") { |
1046 | 1171 | const guideKey = "observatory-guide-seen"; |
1047 | 1172 | if (!localStorage.getItem(guideKey)) { |
@@ -3245,6 +3370,7 @@ function setActiveTier(tier, options = {}) { |
3245 | 3370 | if (typeof window.renderDetailPanel === "function") { |
3246 | 3371 | window.renderDetailPanel(); |
3247 | 3372 | } |
| 3373 | + renderExplorerFocus(); |
3248 | 3374 | } |
3249 | 3375 |
|
3250 | 3376 | function loadEntityByNode(node) { |
@@ -4565,20 +4691,57 @@ function setActiveTier(tier, options = {}) { |
4565 | 4691 | if (!tile) return; |
4566 | 4692 | const sujetId = tile.dataset.sujetId; |
4567 | 4693 | if (!sujetId) return; |
| 4694 | + tile.classList.add("is-loading"); |
4568 | 4695 | try { |
4569 | | - if (typeof charger_sujet === "function") { |
| 4696 | + const storyRoot = document.getElementById("__ml_story_recit_root"); |
| 4697 | + const subjectLabel = tile.dataset.sujetLabel || tile.querySelector(".theme-tile-label")?.textContent || tile.textContent.trim() || sujetId; |
| 4698 | + if (storyRoot && document.body?.dataset.tier === "story") { |
| 4699 | + storyRoot.innerHTML = '<article class="recit-card recit-sujet story-subject-result"><header class="recit-header"><span class="recit-badge">Theme</span><h2 class="recit-titre">' + escapeHtml(subjectLabel) + '</h2></header><div class="recit-corps"><p class="recit-intro">Loading artworks for this theme...</p></div></article>'; |
| 4700 | + } |
| 4701 | + if (window.ui?.etat?.amorcer_entite) { |
| 4702 | + window.ui.etat.amorcer_entite(sujetId, "sujet", subjectLabel); |
| 4703 | + } |
| 4704 | + if (window.ui?.etat?.charger_sujet) { |
| 4705 | + await invokeRuntimeAction("charger_sujet", window.ui.etat.charger_sujet.bind(window.ui.etat), [sujetId]); |
| 4706 | + } else if (typeof charger_sujet === "function") { |
4570 | 4707 | await charger_sujet(sujetId); |
4571 | 4708 | } |
4572 | 4709 | renderConstellation(); |
4573 | 4710 | renderRuntimeState(); |
4574 | 4711 | await refreshMultilingualEntityLabels(); |
4575 | 4712 | window.renderDetailPanel?.(); |
4576 | 4713 | window.renderStoryTier?.(); |
| 4714 | + window.renderExplorerFocus?.(); |
4577 | 4715 | } catch (err) { |
4578 | 4716 | console.warn("charger-sujet error:", err); |
| 4717 | + } finally { |
| 4718 | + tile.classList.remove("is-loading"); |
| 4719 | + } |
| 4720 | + }); |
| 4721 | + |
| 4722 | + document.addEventListener("click", async (e) => { |
| 4723 | + const item = e.target.closest("[data-explorer-entity-id]"); |
| 4724 | + if (!item) return; |
| 4725 | + const id = item.dataset.explorerEntityId; |
| 4726 | + const type = item.dataset.explorerEntityType || "artwork"; |
| 4727 | + if (!id) return; |
| 4728 | + try { |
| 4729 | + if (typeof window.loadSearchSelection === "function") { |
| 4730 | + await window.loadSearchSelection(id, type, item.textContent.trim() || id); |
| 4731 | + } else { |
| 4732 | + await loadEntityByNode({ id, type }); |
| 4733 | + } |
| 4734 | + renderExplorerFocus(); |
| 4735 | + } catch (err) { |
| 4736 | + console.warn("explorer entity load error:", err); |
4579 | 4737 | } |
4580 | 4738 | }); |
4581 | 4739 |
|
| 4740 | + document.addEventListener("click", (e) => { |
| 4741 | + if (!e.target.closest("[data-explorer-open-observatory]")) return; |
| 4742 | + setActiveTier("observatory"); |
| 4743 | + }); |
| 4744 | + |
4582 | 4745 | // Search mode toggle in Explorer panel |
4583 | 4746 | let currentSearchMode = "entity"; |
4584 | 4747 | window.getActiveSearchMode = function getActiveSearchMode() { |
|
0 commit comments