feat: implement theme customization and user preferences

- Added support for user-selected themes, allowing users to customize the appearance of the application.
- Introduced a new `temaPreferido` field in the user schema to store the preferred theme.
- Updated various components to apply the selected theme dynamically based on user preferences.
- Enhanced the UI to include a theme selection interface, enabling users to preview and save their theme choices.
- Implemented a polyfill for BlobBuilder to ensure compatibility across browsers, improving the functionality of the application.
This commit is contained in:
2025-11-22 22:05:52 -03:00
parent 58ac3a4f1b
commit 37d7318d5a
12 changed files with 1149 additions and 74 deletions

View File

@@ -147,6 +147,9 @@
if (result.error) {
console.error('Sign out error:', result.error);
}
// Resetar tema para padrão ao fazer logout
const { aplicarTemaPadrao } = await import('$lib/utils/temas');
aplicarTemaPadrao();
goto(resolve('/'));
}
</script>

View File

@@ -139,6 +139,60 @@
console.error(message, details);
}
// Garantir que BlobBuilder está disponível antes de importar lib-jitsi-meet
function garantirBlobBuilderPolyfill(): void {
if (!browser) return;
const windowWithBlobBuilder = window as WindowWithBlobBuilder;
// Verificar se já existe
if (
typeof windowWithBlobBuilder.BlobBuilder !== 'undefined' ||
typeof windowWithBlobBuilder.webkitBlobBuilder !== 'undefined' ||
typeof windowWithBlobBuilder.MozBlobBuilder !== 'undefined'
) {
return; // Já está disponível
}
// Criar polyfill inline se não estiver disponível
console.log('🔧 Criando polyfill BlobBuilder inline...');
function BlobBuilderPolyfill() {
if (!(this instanceof BlobBuilderPolyfill)) {
return new BlobBuilderPolyfill();
}
this.parts = [];
}
BlobBuilderPolyfill.prototype.append = function(data: Blob | string) {
if (data instanceof Blob) {
this.parts.push(data);
} else if (typeof data === 'string') {
this.parts.push(data);
} else {
this.parts.push(new Blob([data]));
}
};
BlobBuilderPolyfill.prototype.getBlob = function(contentType?: string) {
return new Blob(this.parts, contentType ? { type: contentType } : undefined);
};
// Aplicar em todos os locais possíveis
(window as unknown as Record<string, unknown>).BlobBuilder = BlobBuilderPolyfill;
(window as unknown as Record<string, unknown>).WebKitBlobBuilder = BlobBuilderPolyfill;
(window as unknown as Record<string, unknown>).MozBlobBuilder = BlobBuilderPolyfill;
(window as unknown as Record<string, unknown>).MSBlobBuilder = BlobBuilderPolyfill;
if (typeof globalThis !== 'undefined') {
(globalThis as unknown as Record<string, unknown>).BlobBuilder = BlobBuilderPolyfill;
(globalThis as unknown as Record<string, unknown>).WebKitBlobBuilder = BlobBuilderPolyfill;
(globalThis as unknown as Record<string, unknown>).MozBlobBuilder = BlobBuilderPolyfill;
}
console.log('✅ Polyfill BlobBuilder aplicado inline');
}
// Carregar Jitsi dinamicamente
async function carregarJitsi(): Promise<void> {
if (!browser || JitsiMeetJS) return;
@@ -146,16 +200,8 @@
try {
console.log('🔄 Tentando carregar lib-jitsi-meet...');
// Polyfill BlobBuilder já deve estar disponível via app.html
// Verificar se está disponível antes de carregar a biblioteca
const windowWithBlobBuilder = window as WindowWithBlobBuilder;
if (
typeof windowWithBlobBuilder.BlobBuilder === 'undefined' &&
typeof windowWithBlobBuilder.webkitBlobBuilder === 'undefined' &&
typeof windowWithBlobBuilder.MozBlobBuilder === 'undefined'
) {
console.warn('⚠️ Polyfill BlobBuilder não encontrado, pode causar erros');
}
// Garantir que BlobBuilder está disponível ANTES de importar
garantirBlobBuilderPolyfill();
// Tentar carregar o módulo lib-jitsi-meet dinamicamente
// Usar import dinâmico para evitar problemas de SSR e permitir carregamento apenas no browser
@@ -1165,16 +1211,8 @@
onMount(async () => {
if (!browser) return;
// Polyfill BlobBuilder já deve estar disponível via app.html
// Verificar se está disponível
const windowWithBlobBuilder = window as WindowWithBlobBuilder;
if (
typeof windowWithBlobBuilder.BlobBuilder === 'undefined' &&
typeof windowWithBlobBuilder.webkitBlobBuilder === 'undefined' &&
typeof windowWithBlobBuilder.MozBlobBuilder === 'undefined'
) {
console.warn('⚠️ Polyfill BlobBuilder não encontrado no onMount');
}
// Garantir que BlobBuilder está disponível antes de qualquer coisa
garantirBlobBuilderPolyfill();
// Inicializar store primeiro
inicializarStore();

View File

@@ -0,0 +1,194 @@
/**
* Utilitário para gerenciamento de temas personalizados do SGSE
*/
export type TemaId =
| 'purple'
| 'blue'
| 'green'
| 'orange'
| 'red'
| 'pink'
| 'teal'
| 'dark'
| 'light'
| 'corporate';
export interface Tema {
id: TemaId;
nome: string;
descricao: string;
corPrimaria: string;
corSecundaria: string;
corGradiente: string;
}
/**
* Lista de temas disponíveis
*/
export const temasDisponiveis: Tema[] = [
{
id: 'purple',
nome: 'Roxo',
descricao: 'Tema padrão com cores roxas e azuis',
corPrimaria: '#764ba2',
corSecundaria: '#667eea',
corGradiente: 'from-purple-600 via-blue-600 to-indigo-700'
},
{
id: 'blue',
nome: 'Azul',
descricao: 'Tema azul clássico e profissional',
corPrimaria: '#2563eb',
corSecundaria: '#3b82f6',
corGradiente: 'from-blue-500 via-blue-600 to-blue-700'
},
{
id: 'green',
nome: 'Verde',
descricao: 'Tema verde natural e harmonioso',
corPrimaria: '#10b981',
corSecundaria: '#059669',
corGradiente: 'from-green-500 via-emerald-600 to-teal-700'
},
{
id: 'orange',
nome: 'Laranja',
descricao: 'Tema laranja vibrante e energético',
corPrimaria: '#f97316',
corSecundaria: '#ea580c',
corGradiente: 'from-orange-500 via-amber-600 to-orange-700'
},
{
id: 'red',
nome: 'Vermelho',
descricao: 'Tema vermelho intenso e impactante',
corPrimaria: '#ef4444',
corSecundaria: '#dc2626',
corGradiente: 'from-red-500 via-rose-600 to-red-700'
},
{
id: 'pink',
nome: 'Rosa',
descricao: 'Tema rosa suave e elegante',
corPrimaria: '#ec4899',
corSecundaria: '#db2777',
corGradiente: 'from-pink-500 via-rose-600 to-fuchsia-700'
},
{
id: 'teal',
nome: 'Verde-água',
descricao: 'Tema verde-água refrescante',
corPrimaria: '#14b8a6',
corSecundaria: '#0d9488',
corGradiente: 'from-teal-500 via-cyan-600 to-teal-700'
},
{
id: 'dark',
nome: 'Escuro',
descricao: 'Tema escuro para uso noturno',
corPrimaria: '#1e293b',
corSecundaria: '#0f172a',
corGradiente: 'from-slate-800 via-gray-900 to-slate-900'
},
{
id: 'light',
nome: 'Claro',
descricao: 'Tema claro e minimalista',
corPrimaria: '#f8fafc',
corSecundaria: '#e2e8f0',
corGradiente: 'from-gray-100 via-slate-200 to-gray-300'
},
{
id: 'corporate',
nome: 'Corporativo',
descricao: 'Tema corporativo azul escuro',
corPrimaria: '#1e40af',
corSecundaria: '#1e3a8a',
corGradiente: 'from-blue-800 via-indigo-900 to-blue-900'
}
];
/**
* Mapeamento de temas para nomes do DaisyUI
* Usamos temas nativos do DaisyUI quando disponíveis, ou temas customizados SGSE
*/
export const temaParaDaisyUI: Record<TemaId, string> = {
purple: 'aqua', // Tema padrão atual (roxo/azul) - nativo DaisyUI
blue: 'sgse-blue', // Azul - customizado
green: 'sgse-green', // Verde - customizado
orange: 'sgse-orange', // Laranja - customizado
red: 'sgse-red', // Vermelho - customizado
pink: 'sgse-pink', // Rosa - customizado
teal: 'sgse-teal', // Verde-água - customizado
dark: 'dark', // Escuro - nativo DaisyUI
light: 'light', // Claro - nativo DaisyUI
corporate: 'sgse-corporate' // Corporativo - customizado
};
/**
* Obter tema por ID
*/
export function obterTema(id: TemaId | string | null | undefined): Tema | null {
if (!id) return null;
return temasDisponiveis.find((t) => t.id === id) || null;
}
/**
* Obter nome do tema DaisyUI correspondente
*/
export function obterNomeDaisyUI(id: TemaId | string | null | undefined): string {
if (!id) return 'aqua'; // Tema padrão
const tema = obterTema(id);
if (!tema) return 'aqua';
return temaParaDaisyUI[tema.id] || 'aqua';
}
/**
* Aplicar tema ao documento HTML
* NÃO salva no localStorage - apenas no banco de dados do usuário
*/
export function aplicarTema(temaId: TemaId | string | null | undefined): void {
if (typeof document === 'undefined') return;
const nomeDaisyUI = obterNomeDaisyUI(temaId || 'purple');
const htmlElement = document.documentElement;
const bodyElement = document.body;
if (htmlElement) {
// Remover todos os atributos data-theme existentes primeiro
htmlElement.removeAttribute('data-theme');
if (bodyElement) {
bodyElement.removeAttribute('data-theme');
}
// Aplicar o novo tema
htmlElement.setAttribute('data-theme', nomeDaisyUI);
if (bodyElement) {
bodyElement.setAttribute('data-theme', nomeDaisyUI);
}
// Forçar reflow para garantir que o CSS seja aplicado
void htmlElement.offsetHeight;
// Disparar evento customizado para notificar mudança de tema
if (typeof window !== 'undefined') {
window.dispatchEvent(new CustomEvent('themechange', { detail: { theme: nomeDaisyUI } }));
}
}
}
/**
* Aplicar tema padrão (roxo)
*/
export function aplicarTemaPadrao(): void {
aplicarTema('purple');
}
/**
* Obter tema padrão
*/
export function obterTemaPadrao(): Tema {
return temasDisponiveis[0]; // Purple
}