En mi foro quería dar a los usuarios algunas opciones de personalización en el tema predeterminado. Quería darles la opción de establecer un fondo para el foro. Podría crear diferentes temas para esto, pero eso es bastante trabajo adicional. Y aunque los temas tienen configuraciones, estas son para todos y no específicamente para un usuario. Pero hay una manera de lograr configuraciones configurables por el usuario para los temas, y es “abusando” de los campos de usuario.
En mi tema, el usuario tiene en realidad 3 configuraciones para el fondo de pantalla (imagen, mezcla con el color de fondo y translucidez del contenido), pero para esta publicación solo me centraré en una sola configuración, la imagen de fondo.
Paso 1: Creación del campo de usuario
El usuario tiene la opción de seleccionar una variedad de fondos de pantalla diferentes, o ninguno. Para el campo de usuario, creé un campo desplegable opcional con las siguientes opciones:
- ninguno: sin fondo de pantalla, esto es importante
- Predeterminado: si el usuario no hizo ninguna selección
- Alternativa uno
- Alternativa dos
Este es un campo opcional, por lo que el valor predeterminado estaría en blanco. Para un valor en blanco, me gustaría que se usara el fondo de pantalla predeterminado.
Paso 2: CSS
En common/common.scss definí lo siguiente:
:root {
--wallpaper-default: url(#{$img-wallpaper-default});
--wallpaper-alternative-one: url(#{$img-wallpaper-alternative-one});
--wallpaper-alternative-two: url(#{$img-wallpaper-alternative-two});
/* predeterminado a ninguno para reducir el posible cambio de imagen inicial */
--wallpaper: none;
}
body {
background-image: var(--wallpaper);
}
El :root contiene las variables para las diversas imágenes de fondo, que son recursos del tema. La imagen de fondo se establece en la variable --wallpaper, que inicialmente es none. Por lo tanto, por defecto, cuando se carga el CSS, no habrá fondo de pantalla. Esto se hace para eliminar un posible “parpadeo” cuando el usuario selecciona un fondo de pantalla diferente al original.
Cada fondo de pantalla seleccionable tiene una variable --wallpaper-xx-yy-zz. Los valores de los campos de usuario son texto plano. Basándome en ese valor, seleccionaré la variable CSS correcta para usar como imagen de fondo.
Paso 3: Configuraciones del tema
En el código JavaScript, necesito conocer el campo de usuario que contiene la configuración del fondo de pantalla. La forma más fácil de obtener esto es referirse directamente al ID del campo de usuario. Por lo tanto, necesita crear una configuración de tema para configurar este ID de campo de usuario.
wallpaper_user_field:
default: -1
type: integer
Paso 4: Configuración del fondo de pantalla
Para esto, necesitas escribir algo de JavaScript. Como este código necesita ejecutarse lo antes posible, crearías un inicializador javascripts/discourse/initializer/your-theme-component.js.
Empecemos con lo 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;
// buscar el elemento `html`
this.styleRoot = document.body.parentElement.style;
this.loadWallpaperSettings();
}
// se agregará más lógica más adelante
}
export default {
name: PLUGIN_ID,
initialize(owner) {
withPluginApi('1.34.0', (api) => {
this.instance = new YourThemeWallpaper(owner, api);
});
}
}
Esto es para el código de plantilla grande para agregar algo de JavaScript del componente de tema. La variable styleRoot debería hacer referencia al elemento html del documento, que utilizaremos para cambiar el CSS efectivo.
La función loadWallpaperSettings realizará la carga real de la configuración del usuario y se verá algo así:
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 obtener los valores de los campos de usuario, se debe realizar una llamada API adicional, que puede llevar un poco de tiempo. Una vez que esto regrese, obtenemos el valor del campo de usuario y llamamos a setWallpaper con ese valor. settings.wallpaper_user_field es una referencia a la configuración del tema del paso #3. Si la configuración no se configuró, el campo wp tendrá como valor predeterminado una cadena vacía.
Si no hay un usuario actual, llamamos a la función setWallpaper con una cadena vacía para cargar el fondo de pantalla predeterminado.
La función setWallpaper actualizará realmente el fondo de pantalla:
setWallpaper(wallpaper) {
if (!wallpaper || wallpaper === '') {
// usar el predeterminado si no se estableció ninguno
wallpaper = 'default';
}
// normalizar el valor para que se ajuste a la variable CSS
wallpaper = wallpaper.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-');
if (wallpaper === 'none') {
this.styleRoot.setProperty('--wallpaper', 'none');
} else {
this.styleRoot.setProperty('--wallpaper', 'var(--wallpaper-'+wallpaper+')');
}
}
Si la configuración del fondo de pantalla configurada fue "none", eliminamos la imagen de fondo. De lo contrario, el valor se normaliza para que se parezca a una variable CSS. Por lo tanto, “Alternativa uno” resultará en establecer la variable CSS --wallpaper en var(--wallpaper-alternative-one). Si el valor del campo de usuario no se corresponde con una variable CSS, efectivamente resultará en ningún fondo de pantalla, lo cual es suficiente.
Paso 5: Carga más rápida
La configuración anterior funciona muy bien. Sin embargo, la llamada API adicional puede hacer que el fondo de pantalla se establezca bastante tarde. Lo que no produce un buen resultado. Para resolver esto, utilizo el Almacenamiento Local del navegador para almacenar y recuperar la configuración.
En el constructor, antes de cargar la configuración del fondo de pantalla del usuario, primero intente establecer el fondo de pantalla basándose en los datos del almacenamiento local. Al cargar la configuración del usuario, actualice el almacenamiento local con lo que se configuró.
const WALLPAPER_KEY = 'your_theme_wallpaper';
constructor(owner, api) {
setOwner(this, owner);
this.api = api;
// buscar el elemento `html`
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);
}
}
Esto mejora enormemente la configuración del fondo de pantalla al principio de la carga de Discourse. Efectivamente, la función setWallpaper se llamaría dos veces. Pero como el valor rara vez cambia, esto no será perceptible. Solo si el usuario cambió el fondo de pantalla, parpadearía durante la próxima carga. Esta es también la razón por la que el fondo de pantalla inicial en el CSS se establece en none en lugar del predeterminado.
Paso 6: Vista previa en vivo
Al cambiar el esquema de color en Discourse, obtienes una vista previa en vivo de este nuevo esquema de color, no tienes que guardar y recargar para ver cómo se vería. Esto también sería muy útil para la configuración del fondo de pantalla, para que los usuarios puedan comprobar cuál les gusta más.
Para lograr esto, necesitas algunos trucos adicionales modificando los componentes de campos de usuario.
Al final del constructor, agregué una llamada a this.setupLivePreview() que contiene lo siguiente:
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);
}
}
});
}
En la primera parte, escuchamos eventos de cambio de página. Si wallpaperPreviewed era verdadero, recargamos la configuración original configurada por el usuario. Si el usuario no guardó los nuevos valores de los campos de usuario, el fondo de pantalla se restauraría a la configuración original.
Lo siguiente es la magia real. Modificamos el componente de campo de usuario desplegable para agregar código adicional a la función didUpdateAttrs. Esto se llamaría si el campo de usuario recibiera un nuevo valor. Cuando esto sucede, establecemos la variable wallpaperPreviewed en verdadero, para que en un cambio de página se carguen la configuración guardada. Luego llamamos a la función setWallpaper para mostrar el fondo de pantalla seleccionado actualmente en su lugar.
Lo mismo se puede hacer para otros tipos de campos de usuario. Por ejemplo, para el campo de texto plano, modificarías la clase component:user-fields/text.
Conclusión
Con esto, le das a tus usuarios cierto control sobre cómo los componentes de temas pueden afectar su experiencia mediante campos de usuario.
El único problema es que los campos de usuario están en Preferencias > Perfil en lugar de Preferencias > Interfaz, donde probablemente querrías tenerlos.