Modal Jitsi flotante: ventana dragable, redimensionable y aviso automático al chat
Modal Jitsi flotante: ventana dragable, redimensionable y aviso automático al chat
PR: #59 · Merge: 2026-05-20 · Autor: @Esquembri
Contexto
El modal de reunión Jitsi era originalmente una ventana full-screen con backdrop opaco que tapaba por completo el workspace. Tras feedback de Edu, se rediseña en dos ejes: la ventana pasa a ser flotante y manipulable sin interrumpir el trabajo, y al abrirse avisa automáticamente al resto del equipo en el chat.
Cambios implementados
1. Ventana flotante dragable y redimensionable
| Característica | Detalle |
|---|---|
| Tamaño inicial | 640 × 480 px, centrada en pantalla |
| Tamaño mínimo | 360 × 280 px |
| Drag | Pointer Events (setPointerCapture) desde el header. Compatible con mouse y touch |
| Resize | Handle visual en esquina inferior derecha (nwse-resize). Limita al viewport |
| Minimizar | Colapsa la ventana a 56 px de altura (barra de título). El iframe no se desmonta, la sesión Jitsi se mantiene activa |
| Restaurar | Botón en la barra colapsada vuelve al tamaño anterior |
| Abrir aparte | Enlace target="_blank" a meet.jit.si/<ROOM> |
| Cerrar | Botón «Cerrar» o tecla Esc |
| z-index | 1000 — flota sobre todo el workspace sin backdrop ni bloqueo de scroll |
Sala fija del equipo:
ROOM = 'CrearackSL-Workspace-EduDaniTxell-Sala'
MEETING_URL = https://meet.jit.si/CrearackSL-Workspace-EduDaniTxell-Sala#config.prejoinPageEnabled=true
Quien entra primero crea la sala; los demás se unen al mismo link. Sin cuenta ni signup (servidor público Jitsi).
Implementación técnica del drag
const startDrag = (e: React.PointerEvent) => {
(e.target as HTMLElement).setPointerCapture(e.pointerId);
dragRef.current = { startX: e.clientX, startY: e.clientY, origX: pos.x, origY: pos.y };
};
setPointerCapture garantiza que los eventos siguen llegando al elemento aunque el puntero salga del header durante el arrastre rápido. clampPos mantiene la ventana siempre parcialmente visible dentro del viewport.
El iframe no se desmonta al minimizar (usa display: none sobre el contenedor) para conservar la conexión WebRTC activa.
2. Aviso automático al chat del equipo
Al abrir el modal, se hace un POST /api/chat/messages una sola vez por apertura (flag announcedRef):
📞 {author} ha iniciado una reunión. Únete: {origin}/?meeting=open o directamente {MEETING_URL}
- Visibilidad: los demás miembros lo ven en el polling de 15 s del chat.
- Notificación pasiva: si tienen el tab oculto, dispara beep + cambio de favicon (mecanismo pre-existente del chat, s77 del backlog).
- Click en el link: cualquier miembro entra a la misma sala Jitsi sin ningún signaling propio ni lista de emails.
- Condición de envío: solo en producción (
hostname !== 'localhost'). No se envía en dev local. - Prop nueva:
author?: string— se pasa desdeTeamChatcomome(nombre del usuario activo).
// TeamChat.tsx — único cambio en este archivo
{meetingOpen && <JitsiMeetingModal author={me} onClose={() => setMeetingOpen(false)} />}
Arquitectura del componente
JitsiMeetingModal
├── useEffect [onEsc] — cierra con Esc
├── useEffect [author] — POST /api/chat/messages (una vez)
├── state: pos {x,y} — posición de la ventana
├── state: size {w,h} — dimensiones
├── state: collapsed — modo minimizado
├── dragRef — estado drag activo
├── resizeRef — estado resize activo
├── announcedRef — flag one-shot para el aviso
└── Render (portal → document.body)
├── Header (dragable)
│ ├── Título + subtítulo
│ └── Botones: Aparte | Minimizar/Restaurar | Cerrar
└── Body (display:none si collapsed)
├── <iframe src={MEETING_URL} allow="camera;microphone;..." />
└── Resize handle (bottom-right)
Comportamiento esperado
- Botón «Reunión» en el TeamChat → modal aparece centrado, 640×480, sin tapar el dashboard.
- Drag desde el header → reposiciona. Resize desde esquina → redimensiona. Límites respetados.
- «Minimizar» → barra de 56 px; iframe vivo, sesión Jitsi activa. «Restaurar» vuelve al tamaño.
- En ≤15 s aparece en el chat de Dani y Txell:
📞 Edu ha iniciado una reunión. Únete: … - Click en el link del mensaje → cualquier miembro entra a la misma sala.
Esco «Cerrar» cierra y desmonta el modal.
Limitaciones conocidas
- La sala Jitsi es fija (
CrearackSL-Workspace-EduDaniTxell-Sala). No hay salas múltiples ni privadas. - El aviso automático usa
authorcomo nombre de texto plano (no hay autenticación del mensaje en el POST). - En localhost el aviso no se envía (guarda silencio intencionadamente).
- Resize no tiene snapping ni tamaño predefinido por resolución.
Archivos modificados
| Archivo | Tipo de cambio |
|---|---|
src/components/widgets/JitsiMeetingModal.tsx | Reescritura casi completa (+174 / -97 LOC) |
src/components/widgets/TeamChat.tsx | +1 LOC — prop author={me} |
Véase también
- [[workspace—que-es-workspace]]