fix(media): centre gravite auteurs + bouton vue partagee + poignee draggable barre
- CartePensees.vue : pre-positionner auteurs sur ecole.x_hint/y_hint + jitter 80px pour eviter le rush initial vers la droite au chargement - media.vue : bouton Vue partagee repositionne entre Carte plein ecran et Chatbot plein ecran, style homogene avec les autres boutons - media.vue : poignee draggable sur barre separation carte/chatbot en mode split - ratio clamp 20/80, localStorage media-split-ratio, triggerResize D3 au mouseup, desactivee sur mobile Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -166,11 +166,18 @@ async function initGraph() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// ---- SIMULATION D3 (auteurs) ----
|
// ---- SIMULATION D3 (auteurs) ----
|
||||||
const auteurNodes: any[] = props.data.auteurs.map(a => ({
|
// Pre-positionner chaque auteur pres de son ecole + jitter aleatoire pour eviter le rush initial vers la droite
|
||||||
|
const auteurNodes: any[] = props.data.auteurs.map(a => {
|
||||||
|
const ecole = ecoleMap.get(a.ecole_principale)
|
||||||
|
const jitter = () => (Math.random() - 0.5) * 80
|
||||||
|
return {
|
||||||
id: a.id, type: 'auteur', nom: a.nom, dates: a.dates, bio_courte: a.bio_courte,
|
id: a.id, type: 'auteur', nom: a.nom, dates: a.dates, bio_courte: a.bio_courte,
|
||||||
ecole_principale: a.ecole_principale,
|
ecole_principale: a.ecole_principale,
|
||||||
color: ecoleMap.get(a.ecole_principale)?.color ?? '#888', r: 11,
|
color: ecole?.color ?? '#888', r: 11,
|
||||||
}))
|
x: W * (ecole?.x_hint ?? 0.5) + jitter(),
|
||||||
|
y: H * (ecole?.y_hint ?? 0.5) + jitter(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Liens appartenance auteur -> ecole (vers centroid fixe)
|
// Liens appartenance auteur -> ecole (vers centroid fixe)
|
||||||
const links: any[] = []
|
const links: any[] = []
|
||||||
|
|||||||
124
pages/media.vue
124
pages/media.vue
@@ -24,6 +24,7 @@
|
|||||||
layoutMode === 'carte-full' ? 'carte-full' : '',
|
layoutMode === 'carte-full' ? 'carte-full' : '',
|
||||||
layoutMode === 'chatbot-full' ? 'carte-hidden' : '',
|
layoutMode === 'chatbot-full' ? 'carte-hidden' : '',
|
||||||
]"
|
]"
|
||||||
|
:style="layoutMode === 'split' ? { flexBasis: carteFlexBasis } : {}"
|
||||||
>
|
>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<CartePensees
|
<CartePensees
|
||||||
@@ -54,6 +55,17 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Carte plein ecran
|
Carte plein ecran
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="layoutMode !== 'split'"
|
||||||
|
@click="setLayoutMode('split')"
|
||||||
|
class="toggle-btn"
|
||||||
|
title="Vue partagee"
|
||||||
|
>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="12" x2="21" y2="12"/>
|
||||||
|
</svg>
|
||||||
|
Vue partagee
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="setLayoutMode('chatbot-full')"
|
@click="setLayoutMode('chatbot-full')"
|
||||||
:class="{ active: layoutMode === 'chatbot-full' }"
|
:class="{ active: layoutMode === 'chatbot-full' }"
|
||||||
@@ -65,17 +77,16 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Chatbot plein ecran
|
Chatbot plein ecran
|
||||||
</button>
|
</button>
|
||||||
<button
|
</div>
|
||||||
v-if="layoutMode !== 'split'"
|
|
||||||
@click="setLayoutMode('split')"
|
<!-- Poignee draggable (visible uniquement en mode split, pas sur mobile) -->
|
||||||
class="toggle-btn toggle-btn-reset"
|
<div
|
||||||
title="Vue partagee"
|
v-if="layoutMode === 'split'"
|
||||||
|
class="split-handle"
|
||||||
|
@mousedown.prevent="onHandleMousedown"
|
||||||
|
title="Redimensionner"
|
||||||
>
|
>
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
<span class="split-handle-grip"></span>
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="12" x2="21" y2="12"/>
|
|
||||||
</svg>
|
|
||||||
Vue partagee
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Slot chatbot inline -->
|
<!-- Slot chatbot inline -->
|
||||||
@@ -86,6 +97,7 @@
|
|||||||
layoutMode === 'chatbot-full' ? 'chatbot-full-mode' : '',
|
layoutMode === 'chatbot-full' ? 'chatbot-full-mode' : '',
|
||||||
layoutMode === 'carte-full' ? 'chatbot-hidden' : '',
|
layoutMode === 'carte-full' ? 'chatbot-hidden' : '',
|
||||||
]"
|
]"
|
||||||
|
:style="layoutMode === 'split' ? { flexBasis: chatbotFlexBasis } : {}"
|
||||||
>
|
>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<ChatbotPensees :auteurContext="chatbotAuteur" :inline="true" />
|
<ChatbotPensees :auteurContext="chatbotAuteur" :inline="true" />
|
||||||
@@ -116,6 +128,8 @@ interface PenseesData { meta: any; ecoles: EcoleData[]; auteurs: AuteurData[] }
|
|||||||
type LayoutMode = 'split' | 'carte-full' | 'chatbot-full'
|
type LayoutMode = 'split' | 'carte-full' | 'chatbot-full'
|
||||||
|
|
||||||
const STORAGE_KEY = 'media-layout-mode'
|
const STORAGE_KEY = 'media-layout-mode'
|
||||||
|
const SPLIT_RATIO_KEY = 'media-split-ratio'
|
||||||
|
const DEFAULT_SPLIT_RATIO = 0.66
|
||||||
|
|
||||||
const ficheOpen = ref(false)
|
const ficheOpen = ref(false)
|
||||||
const ficheAuteurId = ref<string | null>(null)
|
const ficheAuteurId = ref<string | null>(null)
|
||||||
@@ -124,8 +138,45 @@ const penseesData = ref<PenseesData | null>(null)
|
|||||||
const layoutMode = ref<LayoutMode>('split')
|
const layoutMode = ref<LayoutMode>('split')
|
||||||
const cartePenseesRef = ref<{ triggerResize: () => void } | null>(null)
|
const cartePenseesRef = ref<{ triggerResize: () => void } | null>(null)
|
||||||
|
|
||||||
|
// Ratio de la carte vs chatbot en mode split (0.2 a 0.8)
|
||||||
|
const splitRatio = ref(DEFAULT_SPLIT_RATIO)
|
||||||
|
const carteFlexBasis = computed(() => `${splitRatio.value * 100}%`)
|
||||||
|
const chatbotFlexBasis = computed(() => `${(1 - splitRatio.value) * 100}%`)
|
||||||
|
|
||||||
const corpusCount = computed(() => penseesData.value?.auteurs.length ?? 0)
|
const corpusCount = computed(() => penseesData.value?.auteurs.length ?? 0)
|
||||||
|
|
||||||
|
// Logique poignee draggable
|
||||||
|
let dragStartY = 0
|
||||||
|
let dragStartRatio = DEFAULT_SPLIT_RATIO
|
||||||
|
let containerHeight = 0
|
||||||
|
|
||||||
|
function onHandleMousedown(e: MouseEvent) {
|
||||||
|
dragStartY = e.clientY
|
||||||
|
dragStartRatio = splitRatio.value
|
||||||
|
// Hauteur du layout-container (carte + handle + chatbot)
|
||||||
|
const container = (e.target as HTMLElement)?.closest('.layout-container') as HTMLElement | null
|
||||||
|
containerHeight = container ? container.clientHeight : window.innerHeight
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', onHandleMousemove)
|
||||||
|
window.addEventListener('mouseup', onHandleMouseup)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHandleMousemove(e: MouseEvent) {
|
||||||
|
const delta = e.clientY - dragStartY
|
||||||
|
const newRatio = dragStartRatio + delta / containerHeight
|
||||||
|
splitRatio.value = Math.min(0.80, Math.max(0.20, newRatio))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHandleMouseup() {
|
||||||
|
window.removeEventListener('mousemove', onHandleMousemove)
|
||||||
|
window.removeEventListener('mouseup', onHandleMouseup)
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem(SPLIT_RATIO_KEY, String(splitRatio.value))
|
||||||
|
}
|
||||||
|
// Notifier D3 du resize apres relachement
|
||||||
|
cartePenseesRef.value?.triggerResize()
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// Restaurer le mode de layout depuis localStorage
|
// Restaurer le mode de layout depuis localStorage
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -133,6 +184,10 @@ onMounted(async () => {
|
|||||||
if (saved && ['split', 'carte-full', 'chatbot-full'].includes(saved)) {
|
if (saved && ['split', 'carte-full', 'chatbot-full'].includes(saved)) {
|
||||||
layoutMode.value = saved
|
layoutMode.value = saved
|
||||||
}
|
}
|
||||||
|
const savedRatio = parseFloat(localStorage.getItem(SPLIT_RATIO_KEY) ?? '')
|
||||||
|
if (!isNaN(savedRatio) && savedRatio >= 0.20 && savedRatio <= 0.80) {
|
||||||
|
splitRatio.value = savedRatio
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
penseesData.value = await $fetch<PenseesData>('/data/auteurs-pensees.json')
|
penseesData.value = await $fetch<PenseesData>('/data/auteurs-pensees.json')
|
||||||
@@ -209,11 +264,11 @@ useHead({ title: 'AEP - Media - Carte FRACAS Bonpote' })
|
|||||||
.carte-slot {
|
.carte-slot {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: flex-basis 0.3s ease, height 0.3s ease, opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.carte-split {
|
.carte-split {
|
||||||
flex: 2 1 0;
|
flex: 0 0 66%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -270,26 +325,55 @@ useHead({ title: 'AEP - Media - Carte FRACAS Bonpote' })
|
|||||||
border-color: var(--nav-primary);
|
border-color: var(--nav-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-btn-reset {
|
/* --- Poignee draggable entre carte et chatbot --- */
|
||||||
margin-left: auto;
|
.split-handle {
|
||||||
background: var(--nav-surface);
|
flex-shrink: 0;
|
||||||
color: var(--nav-text);
|
height: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: row-resize;
|
||||||
|
background: transparent;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-btn-reset:hover {
|
.split-handle:hover {
|
||||||
background: var(--nav-bg-alt);
|
background: rgba(180, 170, 160, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-handle-grip {
|
||||||
|
display: block;
|
||||||
|
width: 32px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(160, 150, 140, 0.55) 0px,
|
||||||
|
rgba(160, 150, 140, 0.55) 1px,
|
||||||
|
transparent 1px,
|
||||||
|
transparent 3px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Masquer la poignee sur mobile (ratio fixe) */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.split-handle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Slot chatbot --- */
|
/* --- Slot chatbot --- */
|
||||||
.chatbot-slot {
|
.chatbot-slot {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: flex-basis 0.3s ease, height 0.3s ease, opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
border-top: 1px solid rgba(180, 170, 160, 0.28);
|
border-top: 1px solid rgba(180, 170, 160, 0.28);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatbot-split {
|
.chatbot-split {
|
||||||
flex: 1 1 0;
|
flex: 0 0 34%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user