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.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Mic, MicOff, Video, VideoOff, Record, Square, Settings, PhoneOff, Circle } from 'lucide-svelte';
|
import { Mic, MicOff, Video, VideoOff, Radio, Square, Settings, PhoneOff, Circle } from 'lucide-svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
audioHabilitado: boolean;
|
audioHabilitado: boolean;
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
{#if gravando}
|
{#if gravando}
|
||||||
<Square class="h-4 w-4" />
|
<Square class="h-4 w-4" />
|
||||||
{:else}
|
{:else}
|
||||||
<Record class="h-4 w-4" />
|
<Radio class="h-4 w-4 fill-current" />
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -130,3 +130,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,70 @@
|
|||||||
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||||
import { X, GripVertical, GripHorizontal } from 'lucide-svelte';
|
import { X, GripVertical, GripHorizontal } from 'lucide-svelte';
|
||||||
|
|
||||||
|
// Tipos para Jitsi (evitando 'any')
|
||||||
|
interface JitsiConnection {
|
||||||
|
connect(): void;
|
||||||
|
disconnect(): void;
|
||||||
|
addEventListener(event: string, handler: (data?: unknown) => void): void;
|
||||||
|
initJitsiConference(roomName: string, options: Record<string, unknown>): JitsiConference;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JitsiConference {
|
||||||
|
join(): void;
|
||||||
|
leave(): void;
|
||||||
|
on(event: string, handler: (...args: unknown[]) => void): void;
|
||||||
|
removeEventListener(event: string, handler: (...args: unknown[]) => void): void;
|
||||||
|
muteAudio(): void;
|
||||||
|
unmuteAudio(): void;
|
||||||
|
muteVideo(): void;
|
||||||
|
unmuteVideo(): void;
|
||||||
|
getParticipants(): Map<string, unknown>;
|
||||||
|
getLocalTracks(): JitsiTrack[];
|
||||||
|
setDisplayName(name: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JitsiTrack {
|
||||||
|
getType(): 'audio' | 'video';
|
||||||
|
isMuted(): boolean;
|
||||||
|
mute(): Promise<void>;
|
||||||
|
unmute(): Promise<void>;
|
||||||
|
attach(element: HTMLElement): void;
|
||||||
|
detach(element: HTMLElement): void;
|
||||||
|
dispose(): Promise<void>;
|
||||||
|
getParticipantId(): string;
|
||||||
|
track: MediaStreamTrack;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JitsiMeetJSLib {
|
||||||
|
JitsiConnection: new (appId: string | null, token: string | null, options: Record<string, unknown>) => JitsiConnection;
|
||||||
|
constants: {
|
||||||
|
events: {
|
||||||
|
connection: {
|
||||||
|
CONNECTION_ESTABLISHED: string;
|
||||||
|
CONNECTION_FAILED: string;
|
||||||
|
CONNECTION_DISCONNECTED: string;
|
||||||
|
};
|
||||||
|
conference: {
|
||||||
|
USER_JOINED: string;
|
||||||
|
USER_LEFT: string;
|
||||||
|
TRACK_ADDED: string;
|
||||||
|
TRACK_REMOVED: string;
|
||||||
|
TRACK_MUTE_CHANGED: string;
|
||||||
|
CONFERENCE_JOINED: string;
|
||||||
|
CONFERENCE_LEFT: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
logLevels: {
|
||||||
|
ERROR: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
init(options: Record<string, unknown>): void;
|
||||||
|
setLogLevel(level: number): void;
|
||||||
|
createLocalTracks(options: Record<string, unknown>): Promise<JitsiTrack[]>;
|
||||||
|
}
|
||||||
|
|
||||||
// Importação dinâmica do Jitsi apenas no cliente
|
// Importação dinâmica do Jitsi apenas no cliente
|
||||||
let JitsiMeetJS: any = $state(null);
|
let JitsiMeetJS: JitsiMeetJSLib | null = $state(null);
|
||||||
|
|
||||||
import CallControls from './CallControls.svelte';
|
import CallControls from './CallControls.svelte';
|
||||||
import CallSettings from './CallSettings.svelte';
|
import CallSettings from './CallSettings.svelte';
|
||||||
@@ -28,10 +90,11 @@
|
|||||||
atualizarDispositivos,
|
atualizarDispositivos,
|
||||||
setJitsiApi,
|
setJitsiApi,
|
||||||
setStreamLocal,
|
setStreamLocal,
|
||||||
finalizarChamada as finalizarChamadaStore
|
finalizarChamada as finalizarChamadaStore,
|
||||||
|
inicializarChamada
|
||||||
} from '$lib/stores/callStore';
|
} from '$lib/stores/callStore';
|
||||||
|
|
||||||
import { obterConfiguracaoJitsi, gerarRoomName, obterUrlSala } from '$lib/utils/jitsi';
|
import { obterConfiguracaoJitsi } from '$lib/utils/jitsi';
|
||||||
import { GravadorMedia, gerarNomeArquivo, salvarGravacao } from '$lib/utils/mediaRecorder';
|
import { GravadorMedia, gerarNomeArquivo, salvarGravacao } from '$lib/utils/mediaRecorder';
|
||||||
import {
|
import {
|
||||||
criarDragHandler,
|
criarDragHandler,
|
||||||
@@ -73,12 +136,13 @@
|
|||||||
let duracaoTimer: ReturnType<typeof setInterval> | null = $state(null);
|
let duracaoTimer: ReturnType<typeof setInterval> | null = $state(null);
|
||||||
let gravador: GravadorMedia | null = $state(null);
|
let gravador: GravadorMedia | null = $state(null);
|
||||||
|
|
||||||
let jitsiConnection: any = $state(null);
|
let jitsiConnection: JitsiConnection | null = $state(null);
|
||||||
let jitsiConference: any = $state(null);
|
let jitsiConference: JitsiConference | null = $state(null);
|
||||||
|
|
||||||
// Queries
|
// Queries
|
||||||
const chamadaQuery = useQuery(api.chamadas.obterChamada, { chamadaId });
|
const chamadaQuery = useQuery(api.chamadas.obterChamada, { chamadaId });
|
||||||
const chamada = $derived(chamadaQuery?.data);
|
const chamada = $derived(chamadaQuery?.data);
|
||||||
|
const meuPerfil = useQuery(api.auth.getCurrentUser, {});
|
||||||
|
|
||||||
// Estado derivado do store
|
// Estado derivado do store
|
||||||
const estadoChamada = $derived(get(callState));
|
const estadoChamada = $derived(get(callState));
|
||||||
@@ -92,13 +156,41 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const module = await import('lib-jitsi-meet');
|
const module = await import('lib-jitsi-meet');
|
||||||
JitsiMeetJS = module.default;
|
JitsiMeetJS = module.default as unknown as JitsiMeetJSLib;
|
||||||
|
|
||||||
|
// Inicializar Jitsi
|
||||||
|
JitsiMeetJS.init({
|
||||||
|
disableAudioLevels: true,
|
||||||
|
disableSimulcast: false,
|
||||||
|
enableWindowOnErrorHandler: true
|
||||||
|
});
|
||||||
|
|
||||||
|
JitsiMeetJS.setLogLevel(JitsiMeetJS.constants.logLevels.ERROR);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao carregar lib-jitsi-meet:', error);
|
console.error('Erro ao carregar lib-jitsi-meet:', error);
|
||||||
alert('Erro ao carregar biblioteca de vídeo');
|
alert('Erro ao carregar biblioteca de vídeo');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inicializar store
|
||||||
|
function inicializarStore(): void {
|
||||||
|
if (chamada && meuPerfil?.data) {
|
||||||
|
inicializarChamada(
|
||||||
|
chamadaId,
|
||||||
|
conversaId,
|
||||||
|
tipo,
|
||||||
|
roomName,
|
||||||
|
ehAnfitriao,
|
||||||
|
chamada.participantes.map(pId => ({
|
||||||
|
usuarioId: pId,
|
||||||
|
nome: 'Participante', // Será atualizado depois
|
||||||
|
audioHabilitado: true,
|
||||||
|
videoHabilitado: tipo === 'video'
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Inicializar Jitsi
|
// Inicializar Jitsi
|
||||||
async function inicializarJitsi(): Promise<void> {
|
async function inicializarJitsi(): Promise<void> {
|
||||||
if (!browser || !JitsiMeetJS) {
|
if (!browser || !JitsiMeetJS) {
|
||||||
@@ -112,7 +204,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const config = configJitsi();
|
const config = configJitsi();
|
||||||
const options: any = {
|
const options: Record<string, unknown> = {
|
||||||
hosts: {
|
hosts: {
|
||||||
domain: config.domain,
|
domain: config.domain,
|
||||||
muc: `conference.${config.domain}`
|
muc: `conference.${config.domain}`
|
||||||
@@ -123,9 +215,10 @@
|
|||||||
|
|
||||||
const connection = new JitsiMeetJS.JitsiConnection(null, null, options);
|
const connection = new JitsiMeetJS.JitsiConnection(null, null, options);
|
||||||
jitsiConnection = connection;
|
jitsiConnection = connection;
|
||||||
|
setJitsiApi(connection);
|
||||||
|
|
||||||
// Eventos de conexão
|
// Eventos de conexão
|
||||||
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, () => {
|
connection.addEventListener(JitsiMeetJS.constants.events.connection.CONNECTION_ESTABLISHED, () => {
|
||||||
console.log('✅ Conexão estabelecida');
|
console.log('✅ Conexão estabelecida');
|
||||||
atualizarStatusConexao(true);
|
atualizarStatusConexao(true);
|
||||||
|
|
||||||
@@ -133,13 +226,15 @@
|
|||||||
client.mutation(api.chamadas.iniciarChamada, { chamadaId });
|
client.mutation(api.chamadas.iniciarChamada, { chamadaId });
|
||||||
|
|
||||||
// Criar conferência
|
// Criar conferência
|
||||||
const conferenceOptions: any = {
|
const estadoAtual = get(callState);
|
||||||
startAudioMuted: !estadoChamada.audioHabilitado,
|
const conferenceOptions: Record<string, unknown> = {
|
||||||
startVideoMuted: !estadoChamada.videoHabilitado
|
startAudioMuted: !estadoAtual.audioHabilitado,
|
||||||
|
startVideoMuted: !estadoAtual.videoHabilitado
|
||||||
};
|
};
|
||||||
|
|
||||||
const conference = connection.initJitsiConference(roomName, conferenceOptions);
|
const conference = connection.initJitsiConference(roomName, conferenceOptions);
|
||||||
jitsiConference = conference;
|
jitsiConference = conference;
|
||||||
|
setJitsiApi(conference);
|
||||||
|
|
||||||
// Eventos da conferência
|
// Eventos da conferência
|
||||||
configurarEventosConferencia(conference);
|
configurarEventosConferencia(conference);
|
||||||
@@ -148,13 +243,13 @@
|
|||||||
conference.join();
|
conference.join();
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, (error) => {
|
connection.addEventListener(JitsiMeetJS.constants.events.connection.CONNECTION_FAILED, (error: unknown) => {
|
||||||
console.error('❌ Falha na conexão:', error);
|
console.error('❌ Falha na conexão:', error);
|
||||||
atualizarStatusConexao(false);
|
atualizarStatusConexao(false);
|
||||||
alert('Erro ao conectar com o servidor de vídeo');
|
alert('Erro ao conectar com o servidor de vídeo');
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, () => {
|
connection.addEventListener(JitsiMeetJS.constants.events.connection.CONNECTION_DISCONNECTED, () => {
|
||||||
console.log('🔌 Conexão desconectada');
|
console.log('🔌 Conexão desconectada');
|
||||||
atualizarStatusConexao(false);
|
atualizarStatusConexao(false);
|
||||||
});
|
});
|
||||||
@@ -169,62 +264,64 @@
|
|||||||
|
|
||||||
// Configurar eventos da conferência
|
// Configurar eventos da conferência
|
||||||
function configurarEventosConferencia(
|
function configurarEventosConferencia(
|
||||||
conference: any
|
conference: JitsiConference
|
||||||
): void {
|
): void {
|
||||||
if (!JitsiMeetJS) return;
|
if (!JitsiMeetJS) return;
|
||||||
|
|
||||||
// Participante entrou
|
// Participante entrou
|
||||||
conference.on(JitsiMeetJS.events.conference.USER_JOINED, (id: string, user: any) => {
|
conference.on(JitsiMeetJS.constants.events.conference.USER_JOINED, (id: unknown, user: unknown) => {
|
||||||
console.log('👤 Participante entrou:', id, user);
|
console.log('👤 Participante entrou:', id, user);
|
||||||
// Atualizar lista de participantes
|
// Atualizar lista de participantes
|
||||||
atualizarListaParticipantes();
|
atualizarListaParticipantes();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Participante saiu
|
// Participante saiu
|
||||||
conference.on(JitsiMeetJS.events.conference.USER_LEFT, (id: string) => {
|
conference.on(JitsiMeetJS.constants.events.conference.USER_LEFT, (id: unknown) => {
|
||||||
console.log('👋 Participante saiu:', id);
|
console.log('👋 Participante saiu:', id);
|
||||||
atualizarListaParticipantes();
|
atualizarListaParticipantes();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Áudio mutado/desmutado
|
// Áudio mutado/desmutado
|
||||||
conference.on(JitsiMeetJS.events.conference.TRACK_MUTE_CHANGED, (track: any) => {
|
conference.on(JitsiMeetJS.constants.events.conference.TRACK_MUTE_CHANGED, (track: unknown) => {
|
||||||
console.log('🎤 Mute mudou:', track);
|
const jitsiTrack = track as JitsiTrack;
|
||||||
if (track.getType() === 'audio') {
|
console.log('🎤 Mute mudou:', jitsiTrack);
|
||||||
const participanteId = track.getParticipantId();
|
if (jitsiTrack.getType() === 'audio') {
|
||||||
const isMuted = track.isMuted();
|
|
||||||
// Atualizar estado do participante
|
// Atualizar estado do participante
|
||||||
atualizarListaParticipantes();
|
atualizarListaParticipantes();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Vídeo mutado/desmutado
|
// Vídeo mutado/desmutado
|
||||||
conference.on(JitsiMeetJS.events.conference.TRACK_MUTE_CHANGED, (track: any) => {
|
conference.on(JitsiMeetJS.constants.events.conference.TRACK_MUTE_CHANGED, (track: unknown) => {
|
||||||
if (track.getType() === 'video') {
|
const jitsiTrack = track as JitsiTrack;
|
||||||
|
if (jitsiTrack.getType() === 'video') {
|
||||||
atualizarListaParticipantes();
|
atualizarListaParticipantes();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Novo track remoto
|
// Novo track remoto
|
||||||
conference.on(
|
conference.on(
|
||||||
JitsiMeetJS.events.conference.TRACK_ADDED,
|
JitsiMeetJS.constants.events.conference.TRACK_ADDED,
|
||||||
(track: any) => {
|
(track: unknown) => {
|
||||||
console.log('📹 Track adicionado:', track);
|
const jitsiTrack = track as JitsiTrack;
|
||||||
adicionarTrackRemoto(track);
|
console.log('📹 Track adicionado:', jitsiTrack);
|
||||||
|
adicionarTrackRemoto(jitsiTrack);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Track removido
|
// Track removido
|
||||||
conference.on(
|
conference.on(
|
||||||
JitsiMeetJS.events.conference.TRACK_REMOVED,
|
JitsiMeetJS.constants.events.conference.TRACK_REMOVED,
|
||||||
(track: any) => {
|
(track: unknown) => {
|
||||||
console.log('📹 Track removido:', track);
|
const jitsiTrack = track as JitsiTrack;
|
||||||
removerTrackRemoto(track);
|
console.log('📹 Track removido:', jitsiTrack);
|
||||||
|
removerTrackRemoto(jitsiTrack);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adicionar track remoto ao container
|
// Adicionar track remoto ao container
|
||||||
function adicionarTrackRemoto(track: any): void {
|
function adicionarTrackRemoto(track: JitsiTrack): void {
|
||||||
if (!videoContainer || track.getType() !== 'video') return;
|
if (!videoContainer || track.getType() !== 'video') return;
|
||||||
|
|
||||||
const participantId = track.getParticipantId();
|
const participantId = track.getParticipantId();
|
||||||
@@ -241,7 +338,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remover track remoto do container
|
// Remover track remoto do container
|
||||||
function removerTrackRemoto(track: any): void {
|
function removerTrackRemoto(track: JitsiTrack): void {
|
||||||
if (!videoContainer) return;
|
if (!videoContainer) return;
|
||||||
const participantId = track.getParticipantId();
|
const participantId = track.getParticipantId();
|
||||||
const videoElement = document.getElementById(`remote-video-${participantId}`);
|
const videoElement = document.getElementById(`remote-video-${participantId}`);
|
||||||
@@ -257,12 +354,15 @@
|
|||||||
const participants = jitsiConference.getParticipants();
|
const participants = jitsiConference.getParticipants();
|
||||||
// Mapear participantes para o formato esperado
|
// Mapear participantes para o formato esperado
|
||||||
// Isso pode ser expandido para buscar informações do backend
|
// Isso pode ser expandido para buscar informações do backend
|
||||||
const participantesAtualizados = participants.map((p: any) => ({
|
const participantesAtualizados = Array.from(participants.values()).map((p: unknown) => {
|
||||||
usuarioId: p.getId() as Id<'usuarios'>,
|
const participant = p as { getId(): string; getDisplayName(): string; isAudioMuted(): boolean; isVideoMuted(): boolean };
|
||||||
nome: p.getDisplayName() || 'Participante',
|
return {
|
||||||
audioHabilitado: !p.isAudioMuted(),
|
usuarioId: participant.getId() as Id<'usuarios'>,
|
||||||
videoHabilitado: !p.isVideoMuted()
|
nome: participant.getDisplayName() || 'Participante',
|
||||||
}));
|
audioHabilitado: !participant.isAudioMuted(),
|
||||||
|
videoHabilitado: !participant.isVideoMuted()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
atualizarParticipantes(participantesAtualizados);
|
atualizarParticipantes(participantesAtualizados);
|
||||||
}
|
}
|
||||||
@@ -303,7 +403,7 @@
|
|||||||
|
|
||||||
// Criar MediaStream com todos os tracks
|
// Criar MediaStream com todos os tracks
|
||||||
const stream = new MediaStream();
|
const stream = new MediaStream();
|
||||||
localTracks.forEach((track: any) => {
|
localTracks.forEach((track: JitsiTrack) => {
|
||||||
stream.addTrack(track.track);
|
stream.addTrack(track.track);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -475,7 +575,10 @@
|
|||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (!browser) return;
|
if (!browser) return;
|
||||||
|
|
||||||
// Carregar Jitsi primeiro
|
// Inicializar store primeiro
|
||||||
|
inicializarStore();
|
||||||
|
|
||||||
|
// Carregar Jitsi
|
||||||
await carregarJitsi();
|
await carregarJitsi();
|
||||||
|
|
||||||
// Configurar janela flutuante
|
// Configurar janela flutuante
|
||||||
|
|||||||
@@ -110,3 +110,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
import UserAvatar from './UserAvatar.svelte';
|
import UserAvatar from './UserAvatar.svelte';
|
||||||
import ScheduleMessageModal from './ScheduleMessageModal.svelte';
|
import ScheduleMessageModal from './ScheduleMessageModal.svelte';
|
||||||
import SalaReuniaoManager from './SalaReuniaoManager.svelte';
|
import SalaReuniaoManager from './SalaReuniaoManager.svelte';
|
||||||
|
import CallWindow from '../call/CallWindow.svelte';
|
||||||
import { getAvatarUrl } from '$lib/utils/avatarGenerator';
|
import { getAvatarUrl } from '$lib/utils/avatarGenerator';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import {
|
import {
|
||||||
Bell,
|
Bell,
|
||||||
X,
|
X,
|
||||||
@@ -40,19 +40,17 @@
|
|||||||
let showAdminMenu = $state(false);
|
let showAdminMenu = $state(false);
|
||||||
let showNotificacaoModal = $state(false);
|
let showNotificacaoModal = $state(false);
|
||||||
let iniciandoChamada = $state(false);
|
let iniciandoChamada = $state(false);
|
||||||
|
let chamadaAtiva = $state<Id<'chamadas'> | null>(null);
|
||||||
|
|
||||||
// Importação dinâmica do CallWindow apenas no cliente
|
const chamadaAtivaQuery = useQuery(api.chamadas.obterChamadaAtiva, {
|
||||||
let CallWindowComponent: any = $state(null);
|
conversaId: conversaId as Id<'conversas'>
|
||||||
|
});
|
||||||
const chamadaAtual = $derived(chamadaAtivaQuery?.data);
|
const chamadaAtual = $derived(chamadaAtivaQuery?.data);
|
||||||
|
|
||||||
const conversas = useQuery(api.chat.listarConversas, {});
|
const conversas = useQuery(api.chat.listarConversas, {});
|
||||||
const isAdmin = useQuery(api.chat.verificarSeEhAdmin, {
|
const isAdmin = useQuery(api.chat.verificarSeEhAdmin, {
|
||||||
conversaId: conversaId as Id<'conversas'>
|
conversaId: conversaId as Id<'conversas'>
|
||||||
});
|
});
|
||||||
const chamadaAtivaQuery = useQuery(api.chamadas.obterChamadaAtiva, {
|
|
||||||
conversaId: conversaId as Id<'conversas'>
|
|
||||||
});
|
|
||||||
|
|
||||||
const conversa = $derived(() => {
|
const conversa = $derived(() => {
|
||||||
console.log('🔍 [ChatWindow] Buscando conversa ID:', conversaId);
|
console.log('🔍 [ChatWindow] Buscando conversa ID:', conversaId);
|
||||||
@@ -168,17 +166,6 @@
|
|||||||
chamadaAtiva = null;
|
chamadaAtiva = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carregar CallWindow dinamicamente apenas no cliente
|
|
||||||
onMount(async () => {
|
|
||||||
if (browser && !CallWindowComponent) {
|
|
||||||
try {
|
|
||||||
const module = await import('../call/CallWindow.svelte');
|
|
||||||
CallWindowComponent = module.default;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Erro ao carregar CallWindow:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verificar se usuário é anfitrião da chamada atual
|
// Verificar se usuário é anfitrião da chamada atual
|
||||||
const meuPerfil = useQuery(api.auth.getCurrentUser, {});
|
const meuPerfil = useQuery(api.auth.getCurrentUser, {});
|
||||||
@@ -501,10 +488,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Janela de Chamada -->
|
<!-- Janela de Chamada -->
|
||||||
{#if browser && chamadaAtiva && chamadaAtual && CallWindowComponent}
|
{#if browser && chamadaAtiva && chamadaAtual}
|
||||||
{@const Component = CallWindowComponent}
|
|
||||||
<div class="pointer-events-none fixed inset-0 z-[9999]">
|
<div class="pointer-events-none fixed inset-0 z-[9999]">
|
||||||
<Component
|
<CallWindow
|
||||||
chamadaId={chamadaAtiva}
|
chamadaId={chamadaAtiva}
|
||||||
conversaId={conversaId as Id<'conversas'>}
|
conversaId={conversaId as Id<'conversas'>}
|
||||||
tipo={chamadaAtual.tipo}
|
tipo={chamadaAtual.tipo}
|
||||||
|
|||||||
@@ -16,12 +16,24 @@ export interface LimitesJanela {
|
|||||||
maxHeight?: number;
|
maxHeight?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_LIMITS: LimitesJanela = {
|
function getDefaultLimits(): LimitesJanela {
|
||||||
minWidth: 400,
|
if (typeof window === 'undefined') {
|
||||||
minHeight: 300,
|
return {
|
||||||
maxWidth: window.innerWidth,
|
minWidth: 400,
|
||||||
maxHeight: window.innerHeight
|
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
|
* Salvar posição da janela no localStorage
|
||||||
@@ -30,6 +42,9 @@ export function salvarPosicaoJanela(
|
|||||||
id: string,
|
id: string,
|
||||||
posicao: PosicaoJanela
|
posicao: PosicaoJanela
|
||||||
): void {
|
): void {
|
||||||
|
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const key = `floating-window-${id}`;
|
const key = `floating-window-${id}`;
|
||||||
localStorage.setItem(key, JSON.stringify(posicao));
|
localStorage.setItem(key, JSON.stringify(posicao));
|
||||||
@@ -42,6 +57,9 @@ export function salvarPosicaoJanela(
|
|||||||
* Restaurar posição da janela do localStorage
|
* Restaurar posição da janela do localStorage
|
||||||
*/
|
*/
|
||||||
export function restaurarPosicaoJanela(id: string): PosicaoJanela | null {
|
export function restaurarPosicaoJanela(id: string): PosicaoJanela | null {
|
||||||
|
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const key = `floating-window-${id}`;
|
const key = `floating-window-${id}`;
|
||||||
const saved = localStorage.getItem(key);
|
const saved = localStorage.getItem(key);
|
||||||
@@ -75,6 +93,14 @@ export function obterPosicaoInicial(
|
|||||||
width: number = 800,
|
width: number = 800,
|
||||||
height: number = 600
|
height: number = 600
|
||||||
): PosicaoJanela {
|
): PosicaoJanela {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return {
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
x: (window.innerWidth - width) / 2,
|
x: (window.innerWidth - width) / 2,
|
||||||
y: (window.innerHeight - height) / 2,
|
y: (window.innerHeight - height) / 2,
|
||||||
@@ -124,6 +150,7 @@ export function criarDragHandler(
|
|||||||
let newY = initialY + deltaY;
|
let newY = initialY + deltaY;
|
||||||
|
|
||||||
// Limitar movimento dentro da tela
|
// Limitar movimento dentro da tela
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
const maxX = window.innerWidth - element.offsetWidth;
|
const maxX = window.innerWidth - element.offsetWidth;
|
||||||
const maxY = window.innerHeight - element.offsetHeight;
|
const maxY = window.innerHeight - element.offsetHeight;
|
||||||
|
|
||||||
@@ -173,6 +200,7 @@ export function criarDragHandler(
|
|||||||
let newX = initialX + deltaX;
|
let newX = initialX + deltaX;
|
||||||
let newY = initialY + deltaY;
|
let newY = initialY + deltaY;
|
||||||
|
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
const maxX = window.innerWidth - element.offsetWidth;
|
const maxX = window.innerWidth - element.offsetWidth;
|
||||||
const maxY = window.innerHeight - element.offsetHeight;
|
const maxY = window.innerHeight - element.offsetHeight;
|
||||||
|
|
||||||
@@ -306,6 +334,8 @@ export function criarResizeHandler(
|
|||||||
newTop = startTop + deltaY;
|
newTop = startTop + deltaY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
// Aplicar limites
|
// Aplicar limites
|
||||||
const maxWidth = limites.maxWidth || window.innerWidth - newLeft;
|
const maxWidth = limites.maxWidth || window.innerWidth - newLeft;
|
||||||
const maxHeight = limites.maxHeight || window.innerHeight - newTop;
|
const maxHeight = limites.maxHeight || window.innerHeight - newTop;
|
||||||
@@ -364,3 +394,4 @@ export function criarResizeHandler(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -329,3 +329,4 @@ export class GravadorMedia {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -576,3 +576,4 @@ export const obterChamada = query({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user