All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 55s
- Agregar componente MsnAvatar con marco SVG de grosor variable - Implementar degradados radiales estilo Frutiger Aero - Agregar composable usePresence con detección de inactividad - Incluir selector de estados: Online, Away, Busy, Offline - Actualizar UserHeader para usar el nuevo avatar
125 lines
3.1 KiB
TypeScript
125 lines
3.1 KiB
TypeScript
/**
|
|
* Composable para manejar el estado de presencia del usuario
|
|
* Inspirado en Windows Live Messenger
|
|
*/
|
|
|
|
export type PresenceStatus = 'online' | 'away' | 'busy' | 'offline'
|
|
|
|
interface PresenceConfig {
|
|
light: string // Color para el highlight (top-left)
|
|
medium: string // Color medio
|
|
dark: string // Color para la sombra (bottom-right)
|
|
}
|
|
|
|
export const PRESENCE_COLORS: Record<PresenceStatus, PresenceConfig> = {
|
|
online: {
|
|
light: '#CCFF99',
|
|
medium: '#66CC33',
|
|
dark: '#339900'
|
|
},
|
|
away: {
|
|
light: '#FFE699',
|
|
medium: '#FFCC33',
|
|
dark: '#FF9900'
|
|
},
|
|
busy: {
|
|
light: '#FF9999',
|
|
medium: '#FF3333',
|
|
dark: '#CC0000'
|
|
},
|
|
offline: {
|
|
light: '#CCCCCC',
|
|
medium: '#999999',
|
|
dark: '#666666'
|
|
}
|
|
}
|
|
|
|
export const usePresence = () => {
|
|
const status = useState<PresenceStatus>('userPresenceStatus', () => 'online')
|
|
const lastActivity = useState<number>('lastActivity', () => Date.now())
|
|
|
|
// Configuración de tiempos (en milisegundos)
|
|
const AWAY_TIMEOUT = 5 * 60 * 1000 // 5 minutos de inactividad = Away
|
|
|
|
let inactivityTimer: NodeJS.Timeout | null = null
|
|
|
|
// Actualizar última actividad
|
|
const updateActivity = () => {
|
|
lastActivity.value = Date.now()
|
|
|
|
// Si estaba away y el usuario vuelve a estar activo, ponerlo online
|
|
if (status.value === 'away') {
|
|
status.value = 'online'
|
|
}
|
|
|
|
resetInactivityTimer()
|
|
}
|
|
|
|
// Resetear el timer de inactividad
|
|
const resetInactivityTimer = () => {
|
|
if (inactivityTimer) {
|
|
clearTimeout(inactivityTimer)
|
|
}
|
|
|
|
// Solo en el cliente
|
|
if (import.meta.client) {
|
|
inactivityTimer = setTimeout(() => {
|
|
// Solo cambiar a away si está online
|
|
if (status.value === 'online') {
|
|
status.value = 'away'
|
|
}
|
|
}, AWAY_TIMEOUT)
|
|
}
|
|
}
|
|
|
|
// Cambiar estado manualmente
|
|
const setStatus = (newStatus: PresenceStatus) => {
|
|
status.value = newStatus
|
|
|
|
// Si se cambia manualmente, resetear el timer
|
|
if (newStatus === 'online') {
|
|
resetInactivityTimer()
|
|
} else if (inactivityTimer) {
|
|
// Si se pone busy o offline manualmente, cancelar el timer de away
|
|
clearTimeout(inactivityTimer)
|
|
inactivityTimer = null
|
|
}
|
|
}
|
|
|
|
// Obtener los colores del estado actual
|
|
const colors = computed(() => PRESENCE_COLORS[status.value])
|
|
|
|
// Inicializar listeners de actividad (solo en cliente)
|
|
const initActivityListeners = () => {
|
|
if (import.meta.client) {
|
|
const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click']
|
|
|
|
events.forEach(event => {
|
|
document.addEventListener(event, updateActivity, { passive: true })
|
|
})
|
|
|
|
// Iniciar el timer
|
|
resetInactivityTimer()
|
|
|
|
// Cleanup cuando se desmonte
|
|
onUnmounted(() => {
|
|
events.forEach(event => {
|
|
document.removeEventListener(event, updateActivity)
|
|
})
|
|
if (inactivityTimer) {
|
|
clearTimeout(inactivityTimer)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
return {
|
|
status: readonly(status),
|
|
colors,
|
|
setStatus,
|
|
updateActivity,
|
|
initActivityListeners,
|
|
PRESENCE_COLORS
|
|
}
|
|
}
|