Files
Loki/internal/loki/ui/src/js/19-lightbox.js
T
MichaelandClaude Opus 5 88e1b38582 Images du fil : miniatures des deux côtés, loupe au clic
Une pièce jointe n'affichait que son nom de fichier, et une image posée
par l'IA sortait à sa taille NATIVE — .chatimg portait la classe mais
aucune règle CSS, donc une capture remplissait la colonne et poussait le
texte hors de vue.

- pièce jointe image : vignette dans le composeur (objectURL local, rien
  n'est encore parti au serveur) et tuile 200x160 dans le message envoyé,
  hydratée comme les captures (fetch authentifié + blob:) ;
- images de l'IA plafonnées à 280px de haut ;
- clic sur n'importe quelle image = loupe plein écran (Échap ou clic sur
  le fond pour fermer). La source est relue AU CLIC, l'hydratation
  remplaçant le src par un blob: après coup.

align-items:flex-start sur .msg-files au passage : une pastille texte
voisine d'une vignette s'étirait à sa hauteur et, arrondie à 999px,
devenait un gros ovale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 17:35:54 +02:00

59 lines
2.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ===== Loupe d'image =======================================================
// Les images du fil sont PLAFONNÉES (CSS .chatimg / .msg-img) : sans plafond,
// une capture sortait à sa taille native et mangeait tout l'écran, ce qui rendait
// la conversation illisible. Le plafond ne se paie donc pas en information : un
// clic rouvre l'image ici, aussi grande que la fenêtre le permet.
//
// On réutilise la source DÉJÀ chargée par la vignette (blob: hydraté par
// hydrateImages, ou objectURL local d'une pièce jointe en attente) : rien n'est
// re-téléchargé, et ça marche derrière la clé de pilotage — qu'une balise <img>
// ne sait pas porter, d'où le blob: en premier lieu.
function lightboxEl(){
let el=document.getElementById('lightbox');
if(el) return el;
el=document.createElement('div');
el.id='lightbox';
el.innerHTML='<button type="button" class="lb-x" title="fermer" aria-label="fermer">×</button>'
+'<img alt=""><div class="lb-name"></div>';
// Clic sur le FOND (ou sur le ×) = fermer ; clic sur l'image = ne rien faire,
// sinon on referme dès qu'on veut la regarder de près.
el.onclick=e=>{ if(e.target===el || e.target.classList.contains('lb-x')) closeLightbox(); };
document.body.appendChild(el);
return el;
}
// Capture (3e argument à true) : sans elle, Échap serait d'abord vu par les
// écouteurs des modales déjà en place et la loupe ne se fermerait pas.
function _lbKey(e){
if(e.key==='Escape'){ e.preventDefault(); e.stopPropagation(); closeLightbox(); }
}
function openLightbox(src, name){
if(!src) return;
const el=lightboxEl(), img=el.querySelector('img');
img.src=src;
img.alt=name||'';
el.querySelector('.lb-name').textContent=name||'';
showModal(el);
document.addEventListener('keydown', _lbKey, true);
}
function closeLightbox(){
const el=document.getElementById('lightbox'); if(!el) return;
document.removeEventListener('keydown', _lbKey, true);
hideModal(el);
// La source n'est lâchée qu'APRÈS la transition : la vider tout de suite
// laissait un cadre vide clignoter pendant la fermeture.
setTimeout(()=>{
if(!el.classList.contains('show')) el.querySelector('img').removeAttribute('src');
}, 200);
}
// Rend une image cliquable (idempotent : renderBody reconstruit le DOM à chaque
// delta du streaming, et hydrateImages nous rappelle sur les mêmes éléments).
//
// On lit currentSrc/src AU CLIC et non à la liaison : l'hydratation remplace la
// source par un blob: après coup, et figer l'URL /api/chat/image ici rouvrirait
// une image que le navigateur n'a pas le droit de charger seul.
function bindZoom(img, name){
if(!img || img.dataset.zoom) return;
img.dataset.zoom='1';
img.addEventListener('click', ()=>openLightbox(img.currentSrc||img.src, name||img.alt||''));
}