Sur mon forum, je voulais offrir aux utilisateurs quelques options de personnalisation sur le thème par défaut. Je voulais leur donner la possibilité de définir un arrière-plan pour le forum. Je pourrais créer différents thèmes pour cela, mais cela représente beaucoup de travail supplémentaire. Et bien que les thèmes aient des paramètres, ceux-ci s’appliquent à tout le monde et non spécifiquement à un utilisateur. Mais il existe un moyen d’obtenir des paramètres configurables par l’utilisateur pour les thèmes, et c’est en “abusant” des champs utilisateur.
Dans mon thème, l’utilisateur a en fait 3 paramètres pour le papier peint (image, mélange avec la couleur de fond et translucidité du contenu), mais pour ce post, je vais me concentrer uniquement sur un seul paramètre, l’image du papier peint.
Étape 1 : création du champ utilisateur
L’utilisateur a la possibilité de sélectionner un certain nombre de papiers peints différents, ou aucun papier peint. Pour le champ utilisateur, j’ai créé un champ déroulant facultatif avec les options suivantes :
- aucun – pas de papier peint du tout, c’est important
- Défaut – si l’utilisateur n’a fait aucune sélection
- Alternative Un
- Alternative Deux
Il s’agit d’un champ facultatif, donc la valeur par défaut serait vide. Pour une valeur vide, j’aimerais que le papier peint par défaut soit utilisé.
Étape 2 : CSS
Dans le fichier common/common.scss, j’ai défini ce qui suit :
:root {
--wallpaper-default: url(#{$img-wallpaper-default});
--wallpaper-alternative-one: url(#{$img-wallpaper-alternative-one});
--wallpaper-alternative-two: url(#{$img-wallpaper-alternative-two});
/* par défaut à none pour réduire les changements d'image initiaux possibles */
--wallpaper: none;
}
body {
background-image: var(--wallpaper);
}
Le :root contient les variables des différentes images de papier peint, qui sont des ressources de thème. L’image d’arrière-plan est définie sur la variable --wallpaper, qui est initialement none. Donc, par défaut, lorsque le CSS est chargé, il n’y aura pas de papier peint. Ceci est fait pour supprimer un possible “scintillement” lorsque l’utilisateur sélectionne un papier peint différent de l’original.
Chaque papier peint sélectionnable obtient une variable --wallpaper-xx-yy-zz. Les valeurs des champs utilisateur sont du texte brut. Sur la base de cette valeur, je sélectionnerai la variable CSS correcte à utiliser comme image d’arrière-plan.
Étape 3 : paramètres du thème
Dans le code JavaScript, j’ai besoin de connaître le champ utilisateur qui contient le paramètre du papier peint. Le moyen le plus simple d’y accéder est de faire référence directement à l’ID du champ utilisateur. Vous devez donc créer un paramètre de thème pour configurer cet ID de champ utilisateur.
wallpaper_user_field:
default: -1
type: integer
Étape 4 : définition du papier peint
Pour cela, vous devez écrire du JavaScript. Comme ce code doit être exécuté le plus tôt possible, vous créeriez un initialiseur javascripts/discourse/initializer/your-theme-component.js.
Commençons par les bases :
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;
// récupérer l'élément `html`
this.styleRoot = document.body.parentElement.style;
this.loadWallpaperSettings();
}
// plus de logique sera ajoutée plus tard
}
export default {
name: PLUGIN_ID,
initialize(owner) {
withPluginApi('1.34.0', (api) => {
this.instance = new YourThemeWallpaper(owner, api);
});
}
}
Ceci concerne le code de base pour ajouter du JavaScript au composant de thème. La variable styleRoot doit faire référence à l’élément html du document, que nous allons utiliser pour modifier le CSS effectif.
La fonction loadWallpaperSettings effectuera le chargement réel du paramètre utilisateur, et ressemble beaucoup à ceci :
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('');
}
}
Afin d’obtenir les valeurs des champs utilisateur, un appel API supplémentaire doit être effectué, ce qui peut prendre un peu de temps. Une fois que cela renvoie, nous obtenons la valeur du champ utilisateur et appelons setWallpaper avec cette valeur. settings.wallpaper_user_field est une référence au paramètre de thème de l’étape n° 3. Si le paramètre n’a pas été configuré, le champ wp sera par défaut une chaîne vide.
S’il n’y a pas d’utilisateur actuel, nous appelons la fonction setWallpaper avec une chaîne vide pour charger le papier peint par défaut.
La fonction setWallpaper mettra réellement à jour le papier peint :
setWallpaper(wallpaper) {
if (!wallpaper || wallpaper === '') {
// utiliser le défaut si aucun n'a été défini
wallpaper = 'default';
}
// normaliser la valeur pour qu'elle corresponde à 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 le paramètre de papier peint configuré était "none", nous supprimons l’image d’arrière-plan. Sinon, la valeur est normalisée pour ressembler à une variable CSS. Ainsi, “Alternative One” se traduira par la définition de la variable CSS --wallpaper sur var(--wallpaper-alternative-one). Si la valeur du champ utilisateur ne correspond pas à une variable CSS, cela se traduira effectivement par l’absence de papier peint, ce qui est suffisant.
Étape 5 : chargement plus rapide
La configuration ci-dessus fonctionne très bien. Cependant, l’appel API supplémentaire peut entraîner la définition du papier peint assez tardivement. Ce qui ne produit pas un bon résultat. Afin de résoudre ce problème, j’utilise le Stockage Local du navigateur pour stocker et récupérer le paramètre.
Dans le constructeur, avant de charger les paramètres du papier peint de l’utilisateur, essayez d’abord de définir le papier peint en vous basant sur les données du stockage local. Lors du chargement des paramètres utilisateur, mettez à jour le stockage local avec ce qui a été configuré.
const WALLPAPER_KEY = 'your_theme_wallpaper';
constructor(owner, api) {
setOwner(this, owner);
this.api = api;
// récupérer l'élément `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);
}
}
Cela améliore considérablement la définition précoce du papier peint lors du chargement de Discourse. Effectivement, la fonction setWallpaper serait appelée deux fois. Mais comme la valeur change rarement, cela ne sera pas perceptible. Ce n’est que si l’utilisateur a changé le papier peint qu’il scintillerait lors du prochain chargement. C’est aussi la raison pour laquelle le papier peint initial en CSS est défini sur none au lieu de la valeur par défaut.
Étape 6 : aperçu en direct
Lorsque vous changez le schéma de couleurs dans Discourse, vous obtenez un aperçu en direct de ce nouveau schéma de couleurs, vous n’avez pas à sauvegarder et recharger pour voir à quoi il ressemblerait. Ce serait également très agréable pour les paramètres du papier peint afin que les utilisateurs puissent vérifier celui qu’ils préfèrent.
Pour y parvenir, vous avez besoin d’une astuce supplémentaire en modifiant les composants des champs utilisateur.
À la fin du constructeur, j’ai ajouté un appel à this.setupLivePreview() qui contient ce qui suit :
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);
}
}
});
}
Dans la première partie, nous écoutons les événements de changement de page. Si wallpaperPreviewed était vrai, nous rechargeons les paramètres d’origine configurés par l’utilisateur. Si l’utilisateur n’a pas sauvegardé les nouvelles valeurs des champs utilisateur, le papier peint sera restauré au paramètre d’origine.
Ensuite, c’est la vraie magie. Nous modifions le composant de champ utilisateur déroulant pour ajouter du code supplémentaire à la fonction didUpdateAttrs. Celle-ci serait appelée si le champ utilisateur recevait une nouvelle valeur. Lorsque cela se produit, nous définissons la variable wallpaperPreviewed sur true, de sorte que lors d’un changement de page, les paramètres sauvegardés soient chargés. Ensuite, nous appelons la fonction setWallpaper pour afficher le papier peint actuellement sélectionné à la place.
La même chose peut être faite pour les autres types de champs utilisateur. Par exemple, pour le champ texte brut, vous modifieriez la classe component:user-fields/text.
Conclusion
Avec cela, vous donnez à vos utilisateurs un certain contrôle sur la façon dont les composants de thème peuvent affecter leur expérience grâce aux champs utilisateur.
Le seul problème est que les champs utilisateur se trouvent sous Préférences > Profil au lieu de Préférences > Interface, où vous voudriez probablement les avoir.