Nel mio forum volevo dare agli utenti alcune opzioni di personalizzazione sul tema predefinito. Volevo dare loro la possibilità di impostare uno sfondo per il forum. Potrei creare temi diversi per questo, ma è un bel po’ di lavoro extra. E mentre i temi hanno impostazioni, queste sono per tutti e non specificamente per un utente. Ma c’è un modo per ottenere impostazioni configurabili dall’utente per i temi, ed è “abusando” dei campi utente.
Nel mio tema l’utente ha in realtà 3 impostazioni per lo sfondo (immagine, fusione con il colore di sfondo e traslucenza del contenuto), ma per questo post mi concentrerò solo su una singola impostazione, l’immagine di sfondo.
Fase 1: creazione del campo utente
L’utente ha la possibilità di selezionare una serie di sfondi diversi, o nessuno sfondo. Per il campo utente ho creato un campo a discesa opzionale con le seguenti opzioni:
- nessuno – nessuno sfondo, questo è importante
- Predefinito – se l’utente non ha fatto alcuna selezione
- Alternativa Uno
- Alternativa Due
Questo è un campo opzionale, quindi il valore predefinito sarà vuoto. Per un valore vuoto vorrei che venisse utilizzato lo sfondo predefinito.
Fase 2: CSS
In common/common.scss ho definito quanto segue:
:root {
--wallpaper-default: url(#{$img-wallpaper-default});
--wallpaper-alternative-one: url(#{$img-wallpaper-alternative-one});
--wallpaper-alternative-two: url(#{$img-wallpaper-alternative-two});
/* predefinito su nessuno per ridurre possibili cambi di immagine iniziali */
--wallpaper: none;
}
body {
background-image: var(--wallpaper);
}
Il :root contiene le variabili per le varie immagini di sfondo, che sono asset del tema. L’immagine di sfondo è impostata sulla variabile --wallpaper, che inizialmente è none. Quindi, per impostazione predefinita, quando viene caricato il CSS, non ci sarà alcuno sfondo. Questo viene fatto per rimuovere un possibile “sfarfallio” quando l’utente seleziona uno sfondo diverso da quello originale.
Ogni sfondo selezionabile ottiene una variabile --wallpaper-xx-yy-zz. I valori dei campi utente sono testo semplice. Basandomi su quel valore selezionerò la variabile CSS corretta da utilizzare come immagine di sfondo.
Fase 3: impostazioni del tema
Nel codice JavaScript devo conoscere il campo utente che contiene l’impostazione dello sfondo. Il modo più semplice per ottenerlo è fare riferimento direttamente all’ID del campo utente. Quindi è necessario creare un’impostazione del tema per configurare questo ID del campo utente.
wallpaper_user_field:
default: -1
type: integer
Fase 4: impostazione dello sfondo
Per questo è necessario scrivere del JavaScript. Poiché questo codice deve essere eseguito il prima possibile, si creerebbe un inizializzatore javascripts/discourse/initializer/your-theme-component.js.
Iniziamo con le basi:
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;
// recupera l'elemento `html`
this.styleRoot = document.body.parentElement.style;
this.loadWallpaperSettings();
}
// verrà aggiunta più logica in seguito
}
export default {
name: PLUGIN_ID,
initialize(owner) {
withPluginApi('1.34.0', (api) => {
this.instance = new YourThemeWallpaper(owner, api);
});
}
}
Questo è per il codice boilerplate di grandi dimensioni per aggiungere JavaScript a un componente del tema. La variabile styleRoot dovrebbe fare riferimento all’elemento html del documento, che utilizzeremo per modificare il CSS effettivo.
La funzione loadWallpaperSettings eseguirà il caricamento effettivo dell’impostazione dell’utente e assomiglierà a questo:
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('');
}
}
Per ottenere i valori dei campi utente, è necessario effettuare una chiamata API aggiuntiva, che potrebbe richiedere un po’ di tempo. Una volta che questa restituisce, otteniamo il valore del campo utente e chiamiamo setWallpaper con quel valore. settings.wallpaper_user_field è un riferimento all’impostazione del tema dal passaggio #3. Se l’impostazione non è stata configurata, il campo wp sarà vuoto per impostazione predefinita.
Se non c’è un utente corrente, chiamiamo la funzione setWallpaper con una stringa vuota per caricare lo sfondo predefinito.
La funzione setWallpaper aggiornerà effettivamente lo sfondo:
setWallpaper(wallpaper) {
if (!wallpaper || wallpaper === '') {
// usa il predefinito se non è stato impostato nulla
wallpaper = 'default';
}
// normalizza il valore per adattarlo alla variabile 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+')');
}
}
Se l’impostazione dello sfondo configurata era "none", rimuoviamo l’immagine di sfondo. Altrimenti, il valore viene normalizzato per assomigliare a una variabile CSS. Quindi “Alternativa Uno” si tradurrà nell’impostazione della variabile CSS --wallpaper su var(--wallpaper-alternative-one). Se il valore del campo utente non corrisponde a una variabile CSS, risulterà effettivamente in nessuno sfondo, il che è abbastanza buono.
Fase 5: caricamento più veloce
L’impostazione di cui sopra funziona benissimo. Tuttavia, la chiamata API aggiuntiva può comportare un caricamento piuttosto tardivo dello sfondo. Il che non produce un buon risultato. Per risolvere questo problema, utilizzo Local Storage del browser per memorizzare e recuperare l’impostazione.
Nel costruttore, prima di caricare le impostazioni dello sfondo dell’utente, prova prima a impostare lo sfondo in base ai dati di local storage. Durante il caricamento delle impostazioni dell’utente, aggiorna il local storage con ciò che è stato configurato.
const WALLPAPER_KEY = 'your_theme_wallpaper';
constructor(owner, api) {
setOwner(this, owner);
this.api = api;
// recupera l'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);
}
}
Questo migliora notevolmente l’impostazione dello sfondo all’inizio del caricamento di Discourse. Effettivamente la funzione setWallpaper verrebbe chiamata due volte. Ma poiché il valore cambia raramente, questo non sarà evidente. Solo se l’utente ha modificato lo sfondo, questo sfarfallerebbe durante il caricamento successivo. Questo è anche il motivo per cui lo sfondo iniziale nel CSS è impostato su none invece del predefinito.
Fase 6: anteprima dal vivo
Quando si cambia lo schema colori in Discourse, si ottiene un’anteprima dal vivo di questo nuovo schema colori, non è necessario salvare e ricaricare per vedere come apparirebbe. Questo sarebbe anche molto utile per le impostazioni dello sfondo, in modo che gli utenti possano verificare quale preferiscono.
Per realizzare questo, è necessaria un’ulteriore astuzia modificando i componenti dei campi utente.
Alla fine del costruttore, ho aggiunto una chiamata a this.setupLivePreview() che contiene quanto segue:
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);
}
}
});
}
Nella prima parte ascoltiamo gli eventi di cambio pagina. Se wallpaperPreviewed era vero, ricarichiamo le impostazioni originali configurate dall’utente. Se l’utente non ha salvato i nuovi valori dei campi utente, lo sfondo verrà ripristinato all’impostazione originale.
Successivamente c’è la vera magia. Modifichiamo il componente del campo utente a discesa per aggiungere del codice aggiuntivo alla funzione didUpdateAttrs. Questa verrebbe chiamata se il campo utente ricevesse un nuovo valore. Quando ciò accade, impostiamo la variabile wallpaperPreviewed su true, in modo che al cambio pagina vengano caricate le impostazioni salvate. Successivamente chiamiamo la funzione setWallpaper per mostrare invece lo sfondo attualmente selezionato.
La stessa cosa può essere fatta per gli altri tipi di campi utente. Ad esempio, per il campo di testo semplice si modificherebbe la classe component:user-fields/text.
Conclusione
Con questo, dai ai tuoi utenti un certo controllo su come i componenti del tema possono influenzare la loro esperienza tramite i campi utente.
L’unico problema è che i campi utente si trovano in Preferenze > Profilo invece che in Preferenze > Interfaccia, dove probabilmente li vorresti.