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:
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
194
apps/web/src/lib/utils/temas.ts
Normal file
194
apps/web/src/lib/utils/temas.ts
Normal 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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user