Volver a la wiki

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ísticaDetalle
Tamaño inicial640 × 480 px, centrada en pantalla
Tamaño mínimo360 × 280 px
DragPointer Events (setPointerCapture) desde el header. Compatible con mouse y touch
ResizeHandle visual en esquina inferior derecha (nwse-resize). Limita al viewport
MinimizarColapsa la ventana a 56 px de altura (barra de título). El iframe no se desmonta, la sesión Jitsi se mantiene activa
RestaurarBotón en la barra colapsada vuelve al tamaño anterior
Abrir aparteEnlace target="_blank" a meet.jit.si/<ROOM>
CerrarBotón «Cerrar» o tecla Esc
z-index1000 — 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}
// 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

  1. Botón «Reunión» en el TeamChat → modal aparece centrado, 640×480, sin tapar el dashboard.
  2. Drag desde el header → reposiciona. Resize desde esquina → redimensiona. Límites respetados.
  3. «Minimizar» → barra de 56 px; iframe vivo, sesión Jitsi activa. «Restaurar» vuelve al tamaño.
  4. En ≤15 s aparece en el chat de Dani y Txell: 📞 Edu ha iniciado una reunión. Únete: …
  5. Click en el link del mensaje → cualquier miembro entra a la misma sala.
  6. Esc o «Cerrar» cierra y desmonta el modal.

Limitaciones conocidas

Archivos modificados

ArchivoTipo de cambio
src/components/widgets/JitsiMeetingModal.tsxReescritura casi completa (+174 / -97 LOC)
src/components/widgets/TeamChat.tsx+1 LOC — prop author={me}

Véase también

Subir