No meu fórum, eu queria dar aos usuários algumas opções de personalização no tema padrão. Eu queria dar a eles a opção de definir um plano de fundo para o fórum. Eu poderia criar temas diferentes para isso, mas isso seria um trabalho extra considerável. E embora os temas tenham configurações, elas são para todos e não especificamente para um usuário. Mas há uma maneira de obter configurações configuráveis pelo usuário para temas, e isso é “abusando” dos campos de usuário.
No meu tema, o usuário tem na verdade 3 configurações para o papel de parede (imagem, mesclagem com a cor de fundo e translucidez do conteúdo), mas para este post, vou focar apenas em uma configuração, a imagem do papel de parede.
Passo 1: criando o campo de usuário
O usuário tem a opção de selecionar um monte de papéis de parede diferentes, ou nenhum papel de parede. Para o campo de usuário, criei um campo de seleção opcional com as seguintes opções:
- none – sem papel de parede, isso é importante
- Default – se o usuário não fez nenhuma seleção
- Alternative One
- Alternative Two
Este é um campo opcional, então o valor padrão seria em branco. Para um valor em branco, eu gostaria que o papel de parede padrão fosse usado.
Passo 2: CSS
Em common/common.scss, defini o seguinte:
:root {
--wallpaper-default: url(#{$img-wallpaper-default});
--wallpaper-alternative-one: url(#{$img-wallpaper-alternative-one});
--wallpaper-alternative-two: url(#{$img-wallpaper-alternative-two});
/* default to none to reduce possible initial image switching */
--wallpaper: none;
}
body {
background-image: var(--wallpaper);
}
O :root contém as variáveis para as várias imagens de papel de parede, que são assets do tema. A imagem de fundo é definida para a variável --wallpaper, que é inicialmente none. Portanto, por padrão, quando o CSS é carregado, não haverá papel de parede. Isso é feito para remover um possível “piscar” quando o usuário seleciona um papel de parede diferente do original.
Cada papel de parede selecionável recebe uma variável --wallpaper-xx-yy-zz. Os valores dos campos de usuário são texto simples. Com base nesse valor, selecionarei a variável CSS correta para usar como imagem de fundo.
Passo 3: configurações do tema
No código JavaScript, preciso saber o campo de usuário que contém a configuração do papel de parede. A maneira mais fácil de obter isso é referenciar diretamente o ID do campo de usuário. Portanto, você precisa criar uma configuração de tema para configurar este ID de campo de usuário
wallpaper_user_field:
default: -1
type: integer
Passo 4: definindo o papel de parede
Para isso, você precisa escrever algum JavaScript. Como este código precisa ser executado o mais cedo possível, você criaria um inicializador javascripts/discourse/initializer/your-theme-component.js.
Vamos começar com o básico:
import { setOwner } from '@ember/owner';
import { withPluginApi } from 'discourse/lib/plugin-api';
const PLUGIN_ID = 'your-theme-component';
class YourThemeWallpaper {
api;
styleRoot;
constructor(owner, api) {
setOwner(this, owner);
this.api = api;
// fetch the `html` element
this.styleRoot = document.body.parentElement.style;
this.loadWallpaperSettings();
}
// more logic will be added later
}
export default {
name: PLUGIN_ID,
initialize(owner) {
withPluginApi('1.34.0', (api) => {
this.instance = new YourThemeWallpaper(owner, api);
});
}
}
Este é o código boilerplate para adicionar algum JavaScript de componente de tema. A variável styleRoot deve referenciar o elemento html do documento, que usaremos para alterar o CSS efetivo.
A função loadWallpaperSettings fará o carregamento real da configuração do usuário e se parece com isto:
loadWallpaperSettings() {
let user = this.api.getCurrentUser();
if (user) {
this.api.container.lookup('store:main').find('user', user.username).then((user) => {
let wp = user.user_fields[settings.wallpaper_user_field] || '';
this.setWallpaper(wp);
});
} else {
this.setWallpaper('');
}
}
Para obter os valores dos campos de usuário, uma chamada de API extra precisa ser feita, o que pode levar um pouco de tempo. Assim que isso retornar, obtemos o valor do campo de usuário e chamamos setWallpaper com esse valor. settings.wallpaper_user_field é uma referência à configuração do tema da etapa #3. Se a configuração não foi configurada, o campo wp será padrão para uma string vazia.
Se não houver usuário atual, chamamos a função setWallpaper com uma string vazia para carregar o papel de parede padrão.
A função setWallpaper realmente atualizará o papel de parede:
setWallpaper(wallpaper) {
if (!wallpaper || wallpaper === '') {
// use the default if none was set
wallpaper = 'default';
}
// normalize the value to fit the CSS variable
wallpaper = wallpaper.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-');
if (wallpaper === 'none') {
this.styleRoot.setProperty('--wallpaper', 'none');
} else {
this.styleRoot.setProperty('--wallpaper', 'var(--wallpaper-'+wallpaper+')');
}
}
Se a configuração de papel de parede configurada foi "none", removemos a imagem de fundo. Caso contrário, o valor é normalizado para se assemelhar a uma variável CSS. Assim, “Alternative One” resultará em definir a variável CSS --wallpaper para var(--wallpaper-alternative-one). Se o valor do campo de usuário não corresponder a uma variável CSS, efetivamente resultará em nenhum papel de parede, o que é bom o suficiente.
Passo 5: carregamento mais rápido
A configuração acima funciona bem. No entanto, a chamada de API extra pode resultar no papel de parede sendo definido bem tarde. O que não produz um bom resultado. Para resolver isso, utilizo o Local Storage do navegador para armazenar e recuperar a configuração.
No construtor, antes de carregar as configurações de papel de parede do usuário, primeiro tente definir o papel de parede com base nos dados do armazenamento local. Ao carregar as configurações do usuário, atualize o armazenamento local com o que foi configurado.
const WALLPAPER_KEY = 'your_theme_wallpaper';
constructor(owner, api) {
setOwner(this, owner);
this.api = api;
// fetch the `html` element
this.styleRoot = document.body.parentElement.style;
this.loadLocalStorage();
this.loadWallpaperSettings();
}
loadWallpaperSettings() {
let user = this.api.getCurrentUser();
if (user) {
this.api.container.lookup('store:main').find('user', user.username).then((user) => {
let wp = user.user_fields[settings.wallpaper_user_field] || '';
localStorage.setItem(WALLPAPER_KEY, wp);
this.setWallpaper(wp);
});
} else {
localStorage.removeItem(WALLPAPER_KEY);
this.setWallpaper('');
}
}
loadLocalStorage() {
let data = localStorage.getItem(WALLPAPER_KEY);
if (data) {
this.setWallpaper(data);
}
}
Isso melhora muito a definição do papel de parede no início do carregamento do Discourse. Efetivamente, a função setWallpaper seria chamada duas vezes. Mas como o valor raramente muda, isso não será perceptível. Somente se o usuário alterou o papel de parede, ele piscaria durante a próxima carga. Esta também é a razão pela qual o papel de parede inicial no CSS é definido como none em vez do padrão.
Passo 6: pré-visualização ao vivo
Ao alterar o esquema de cores no Discourse, você obtém uma pré-visualização ao vivo desse novo esquema de cores, você não precisa salvar e recarregar para ver como ficaria. Isso também seria muito bom para as configurações de papel de parede, para que os usuários possam verificar qual eles mais gostam.
Para conseguir isso, você precisa de alguns truques adicionais modificando os componentes de campo de usuário.
No final do construtor, adicionei uma chamada para this.setupLivePreview() que contém o seguinte:
setupLivePreview() {
api.onPageChange((url, title) => {
if (this.wallpaperPreviewed) {
this.wallpaperPreviewed = false;
this.loadWallpaperSettings();
}
});
let _this = this;
api.modifyClass('component:user-fields/dropdown', {
pluginId: PLUGIN_ID,
didUpdateAttrs() {
if (this.field.id == settings.wallpaper_user_field) {
_this.wallpaperPreviewed = true;
_this.setWallpaper(this.value);
}
}
});
}
Na primeira parte, ouvimos eventos de mudança de página. Se wallpaperPreviewed era verdadeiro, recarregamos as configurações originalmente configuradas pelo usuário. Se o usuário não salvou os novos valores dos campos de usuário, o papel de parede seria restaurado para a configuração original.
Em seguida, vem a mágica real. Modificamos o componente de campo de usuário de seleção suspensa para adicionar algum código adicional à função didUpdateAttrs. Isso seria chamado se o campo de usuário recebesse um novo valor. Quando isso acontece, definimos a variável wallpaperPreviewed como true, para que em uma mudança de página as configurações salvas sejam carregadas. Em seguida, chamamos a função setWallpaper para mostrar o papel de parede atualmente selecionado.
O mesmo pode ser feito para os outros tipos de campo de usuário. Por exemplo, para o campo de texto simples, você modificaria a classe component:user-fields/text.
Conclusão
Com isso, você dá aos seus usuários algum controle sobre como os componentes de tema podem afetar sua experiência usando campos de usuário.
O único problema é que os campos de usuário estão em Preferências > Perfil em vez de Preferências > Interface, onde você provavelmente gostaria de tê-los.