feat: PC3 mindmap Carte O (D3 force-directed) + scrape AEP/Articles + tabs centre HAUT
- scripts/build-carte-o.js : scan recursif AEP/Articles/, parse YAML + legacy header, extract wikilinks, infer 5 famille - src/components/vue/CarteO.vue : D3 v7 force-directed avec drag, zoom + pan, click handler, tooltips, ResizeObserver - src/components/vue/CarteOModal.vue : modal recap intention avec Teleport, Esc + backdrop close, transitions - src/components/vue/CarteOWrapper.vue : fetch /data/carte-o.json, etat selectionne, fallback mobile (msg + miniature SVG) - src/components/astro/ColCentre.astro : tabs Carte O / Chatbot, panneaux ARIA - package.json : prebuild + predev hooks, build:carte-o script - public/data/carte-o.json : 84 nodes / 94 edges sur 21 themes, distribution familles equilibree Drill-down V1 = zoom + pan seul (V2 recursif backlog). Pattern adapte de nav-carte/components/codev/CodevGraph.vue (sans coupling Nuxt). Build Astro 6.3.1 OK, bundle CarteOWrapper 69KB. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
170
src/components/vue/CarteOWrapper.vue
Normal file
170
src/components/vue/CarteOWrapper.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<script setup lang="ts">
|
||||
// Wrapper Carte O : fetch /data/carte-o.json + state modal.
|
||||
// Vue island Astro hydratée client:visible.
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import CarteO from './CarteO.vue'
|
||||
import CarteOModal from './CarteOModal.vue'
|
||||
|
||||
interface CarteNode {
|
||||
id: string
|
||||
label: string
|
||||
family: string
|
||||
intention?: string
|
||||
slug?: string
|
||||
theme?: string
|
||||
}
|
||||
|
||||
interface CarteEdge {
|
||||
source: string
|
||||
target: string
|
||||
}
|
||||
|
||||
interface CarteData {
|
||||
meta?: {
|
||||
nodeCount?: number
|
||||
edgeCount?: number
|
||||
familyDistribution?: Record<string, number>
|
||||
familyColors?: Record<string, string>
|
||||
}
|
||||
nodes: CarteNode[]
|
||||
edges: CarteEdge[]
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
src?: string
|
||||
}>(), {
|
||||
src: '/data/carte-o.json',
|
||||
})
|
||||
|
||||
const data = ref<CarteData | null>(null)
|
||||
const error = ref<string | null>(null)
|
||||
const selectedNode = ref<CarteNode | null>(null)
|
||||
const isMobileScreen = ref(false)
|
||||
|
||||
const familyColors = computed(() =>
|
||||
data.value?.meta?.familyColors || {
|
||||
penseur: '#3b82f6',
|
||||
concept: '#10b981',
|
||||
methode: '#f59e0b',
|
||||
collectif: '#ef4444',
|
||||
ressource: '#8b5cf6',
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
isMobileScreen.value = window.innerWidth < 768
|
||||
try {
|
||||
const res = await fetch(props.src)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
data.value = await res.json()
|
||||
} catch (e: any) {
|
||||
console.error('[CarteO] failed to load', e)
|
||||
error.value = e?.message || 'Erreur de chargement'
|
||||
}
|
||||
|
||||
// Update mobile flag on resize.
|
||||
window.addEventListener('resize', () => {
|
||||
isMobileScreen.value = window.innerWidth < 768
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<!-- Mobile fallback (V1) -->
|
||||
<div v-if="isMobileScreen" class="mobile-fallback">
|
||||
<div class="mini-map">
|
||||
<svg viewBox="0 0 200 120" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<circle cx="100" cy="60" r="8" fill="#3b82f6" />
|
||||
<circle cx="50" cy="35" r="5" fill="#10b981" />
|
||||
<circle cx="150" cy="35" r="5" fill="#f59e0b" />
|
||||
<circle cx="55" cy="90" r="5" fill="#ef4444" />
|
||||
<circle cx="145" cy="90" r="5" fill="#8b5cf6" />
|
||||
<line x1="100" y1="60" x2="50" y2="35" stroke="#94a3b8" stroke-width="0.8" opacity="0.5" />
|
||||
<line x1="100" y1="60" x2="150" y2="35" stroke="#94a3b8" stroke-width="0.8" opacity="0.5" />
|
||||
<line x1="100" y1="60" x2="55" y2="90" stroke="#94a3b8" stroke-width="0.8" opacity="0.5" />
|
||||
<line x1="100" y1="60" x2="145" y2="90" stroke="#94a3b8" stroke-width="0.8" opacity="0.5" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="msg">
|
||||
Carte O optimisée desktop. Retournez sur grand écran pour explorer la mindmap interactive.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
<div v-else-if="!data && !error" class="state">
|
||||
<span>Chargement de la Carte O…</span>
|
||||
</div>
|
||||
|
||||
<!-- Error state -->
|
||||
<div v-else-if="error" class="state error">
|
||||
<span>Impossible de charger la Carte O ({{ error }})</span>
|
||||
</div>
|
||||
|
||||
<!-- Carte O -->
|
||||
<template v-else-if="data">
|
||||
<CarteO
|
||||
:nodes="data.nodes"
|
||||
:edges="data.edges"
|
||||
:family-colors="familyColors"
|
||||
@node-click="selectedNode = $event"
|
||||
/>
|
||||
<CarteOModal
|
||||
:node="selectedNode"
|
||||
:family-colors="familyColors"
|
||||
@close="selectedNode = null"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.state {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9ca3af;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.state.error {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.mobile-fallback {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.mini-map {
|
||||
width: 70%;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.mini-map svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.msg {
|
||||
color: #6b7280;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.45;
|
||||
font-style: italic;
|
||||
max-width: 24rem;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user