Files
sgse-app/apps/web/src/lib/utils/floatingWindow.ts
deyvisonwanderley 8fc3cf08c4 feat: enhance call functionality and improve type safety
- Updated CallControls to replace the Record icon with Radio for better representation during recording states.
- Refactored CallWindow to introduce Jitsi connection and conference interfaces, improving type safety and clarity in handling Jitsi events.
- Streamlined event handling for connection and conference states, ensuring robust management of audio/video calls.
- Enhanced ChatWindow to directly import CallWindow, simplifying the component structure and improving call handling logic.
- Improved utility functions for window management to ensure compatibility with server-side rendering.
2025-11-21 16:21:01 -03:00

398 lines
9.8 KiB
TypeScript

/**
* Utilitários para criar janela flutuante redimensionável e arrastável
*/
export interface PosicaoJanela {
x: number;
y: number;
width: number;
height: number;
}
export interface LimitesJanela {
minWidth: number;
minHeight: number;
maxWidth?: number;
maxHeight?: number;
}
function getDefaultLimits(): LimitesJanela {
if (typeof window === 'undefined') {
return {
minWidth: 400,
minHeight: 300,
maxWidth: 1920,
maxHeight: 1080
};
}
return {
minWidth: 400,
minHeight: 300,
maxWidth: window.innerWidth,
maxHeight: window.innerHeight
};
}
const DEFAULT_LIMITS: LimitesJanela = getDefaultLimits();
/**
* Salvar posição da janela no localStorage
*/
export function salvarPosicaoJanela(
id: string,
posicao: PosicaoJanela
): void {
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
return;
}
try {
const key = `floating-window-${id}`;
localStorage.setItem(key, JSON.stringify(posicao));
} catch (error) {
console.warn('Erro ao salvar posição da janela:', error);
}
}
/**
* Restaurar posição da janela do localStorage
*/
export function restaurarPosicaoJanela(id: string): PosicaoJanela | null {
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
return null;
}
try {
const key = `floating-window-${id}`;
const saved = localStorage.getItem(key);
if (!saved) return null;
const posicao = JSON.parse(saved) as PosicaoJanela;
// Validar se a posição ainda é válida (dentro da tela)
if (
posicao.x >= 0 &&
posicao.y >= 0 &&
posicao.x + posicao.width <= window.innerWidth + 100 &&
posicao.y + posicao.height <= window.innerHeight + 100 &&
posicao.width >= DEFAULT_LIMITS.minWidth &&
posicao.height >= DEFAULT_LIMITS.minHeight
) {
return posicao;
}
return null;
} catch (error) {
console.warn('Erro ao restaurar posição da janela:', error);
return null;
}
}
/**
* Obter posição inicial da janela (centralizada)
*/
export function obterPosicaoInicial(
width: number = 800,
height: number = 600
): PosicaoJanela {
if (typeof window === 'undefined') {
return {
x: 100,
y: 100,
width,
height
};
}
return {
x: (window.innerWidth - width) / 2,
y: (window.innerHeight - height) / 2,
width,
height
};
}
/**
* Criar handler de arrastar para janela
*/
export function criarDragHandler(
element: HTMLElement,
handle: HTMLElement,
onPositionChange?: (x: number, y: number) => void
): () => void {
let isDragging = false;
let startX = 0;
let startY = 0;
let initialX = 0;
let initialY = 0;
function handleMouseDown(e: MouseEvent): void {
if (e.button !== 0) return; // Apenas botão esquerdo
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const rect = element.getBoundingClientRect();
initialX = rect.left;
initialY = rect.top;
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
e.preventDefault();
}
function handleMouseMove(e: MouseEvent): void {
if (!isDragging) return;
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
let newX = initialX + deltaX;
let newY = initialY + deltaY;
// Limitar movimento dentro da tela
if (typeof window === 'undefined') return;
const maxX = window.innerWidth - element.offsetWidth;
const maxY = window.innerHeight - element.offsetHeight;
newX = Math.max(0, Math.min(newX, maxX));
newY = Math.max(0, Math.min(newY, maxY));
element.style.left = `${newX}px`;
element.style.top = `${newY}px`;
if (onPositionChange) {
onPositionChange(newX, newY);
}
}
function handleMouseUp(): void {
isDragging = false;
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
}
// Suporte para touch (mobile)
function handleTouchStart(e: TouchEvent): void {
if (e.touches.length !== 1) return;
isDragging = true;
const touch = e.touches[0];
startX = touch.clientX;
startY = touch.clientY;
const rect = element.getBoundingClientRect();
initialX = rect.left;
initialY = rect.top;
document.addEventListener('touchmove', handleTouchMove, { passive: false });
document.addEventListener('touchend', handleTouchEnd);
e.preventDefault();
}
function handleTouchMove(e: TouchEvent): void {
if (!isDragging || e.touches.length !== 1) return;
const touch = e.touches[0];
const deltaX = touch.clientX - startX;
const deltaY = touch.clientY - startY;
let newX = initialX + deltaX;
let newY = initialY + deltaY;
if (typeof window === 'undefined') return;
const maxX = window.innerWidth - element.offsetWidth;
const maxY = window.innerHeight - element.offsetHeight;
newX = Math.max(0, Math.min(newX, maxX));
newY = Math.max(0, Math.min(newY, maxY));
element.style.left = `${newX}px`;
element.style.top = `${newY}px`;
if (onPositionChange) {
onPositionChange(newX, newY);
}
e.preventDefault();
}
function handleTouchEnd(): void {
isDragging = false;
document.removeEventListener('touchmove', handleTouchMove);
document.removeEventListener('touchend', handleTouchEnd);
}
handle.addEventListener('mousedown', handleMouseDown);
handle.addEventListener('touchstart', handleTouchStart, { passive: false });
// Retornar função de cleanup
return () => {
handle.removeEventListener('mousedown', handleMouseDown);
handle.removeEventListener('touchstart', handleTouchStart);
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('touchmove', handleTouchMove);
document.removeEventListener('touchend', handleTouchEnd);
};
}
/**
* Criar handler de redimensionar para janela
*/
export function criarResizeHandler(
element: HTMLElement,
handles: HTMLElement[],
limites: LimitesJanela = DEFAULT_LIMITS,
onSizeChange?: (width: number, height: number) => void
): () => void {
let isResizing = false;
let currentHandle: HTMLElement | null = null;
let startX = 0;
let startY = 0;
let startWidth = 0;
let startHeight = 0;
let startLeft = 0;
let startTop = 0;
function handleMouseDown(e: MouseEvent, handle: HTMLElement): void {
if (e.button !== 0) return;
isResizing = true;
currentHandle = handle;
startX = e.clientX;
startY = e.clientY;
const rect = element.getBoundingClientRect();
startWidth = rect.width;
startHeight = rect.height;
startLeft = rect.left;
startTop = rect.top;
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
e.preventDefault();
e.stopPropagation();
}
function handleMouseMove(e: MouseEvent): void {
if (!isResizing || !currentHandle) return;
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
let newWidth = startWidth;
let newHeight = startHeight;
let newLeft = startLeft;
let newTop = startTop;
// Determinar direção do resize baseado na classe do handle
const classes = currentHandle.className;
// Right
if (classes.includes('resize-right') || classes.includes('resize-e')) {
newWidth = startWidth + deltaX;
}
// Bottom
if (classes.includes('resize-bottom') || classes.includes('resize-s')) {
newHeight = startHeight + deltaY;
}
// Left
if (classes.includes('resize-left') || classes.includes('resize-w')) {
newWidth = startWidth - deltaX;
newLeft = startLeft + deltaX;
}
// Top
if (classes.includes('resize-top') || classes.includes('resize-n')) {
newHeight = startHeight - deltaY;
newTop = startTop + deltaY;
}
// Corner handles
if (classes.includes('resize-se')) {
newWidth = startWidth + deltaX;
newHeight = startHeight + deltaY;
}
if (classes.includes('resize-sw')) {
newWidth = startWidth - deltaX;
newHeight = startHeight + deltaY;
newLeft = startLeft + deltaX;
}
if (classes.includes('resize-ne')) {
newWidth = startWidth + deltaX;
newHeight = startHeight - deltaY;
newTop = startTop + deltaY;
}
if (classes.includes('resize-nw')) {
newWidth = startWidth - deltaX;
newHeight = startHeight - deltaY;
newLeft = startLeft + deltaX;
newTop = startTop + deltaY;
}
if (typeof window === 'undefined') return;
// Aplicar limites
const maxWidth = limites.maxWidth || window.innerWidth - newLeft;
const maxHeight = limites.maxHeight || window.innerHeight - newTop;
newWidth = Math.max(limites.minWidth, Math.min(newWidth, maxWidth));
newHeight = Math.max(limites.minHeight, Math.min(newHeight, maxHeight));
// Ajustar posição se necessário
if (newLeft + newWidth > window.innerWidth) {
newLeft = window.innerWidth - newWidth;
}
if (newTop + newHeight > window.innerHeight) {
newTop = window.innerHeight - newHeight;
}
if (newLeft < 0) {
newLeft = 0;
newWidth = Math.min(newWidth, window.innerWidth);
}
if (newTop < 0) {
newTop = 0;
newHeight = Math.min(newHeight, window.innerHeight);
}
element.style.width = `${newWidth}px`;
element.style.height = `${newHeight}px`;
element.style.left = `${newLeft}px`;
element.style.top = `${newTop}px`;
if (onSizeChange) {
onSizeChange(newWidth, newHeight);
}
}
function handleMouseUp(): void {
isResizing = false;
currentHandle = null;
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
}
const cleanupFunctions: (() => void)[] = [];
// Adicionar listeners para cada handle
for (const handle of handles) {
const handler = (e: MouseEvent) => handleMouseDown(e, handle);
handle.addEventListener('mousedown', handler);
cleanupFunctions.push(() => handle.removeEventListener('mousedown', handler));
}
// Retornar função de cleanup
return () => {
cleanupFunctions.forEach((cleanup) => cleanup());
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}