ユーザーがカスタマイズできるテーマコンポーネント(使い方)

私のフォーラムで、ユーザーにデフォルトテーマのカスタマイズオプションを提供したいと考えていました。フォーラムの背景を設定するオプションを提供したかったのです。これのために異なるテーマを作成することもできましたが、それはかなりの追加作業になります。テーマには設定がありますが、それらは全員のためのものであり、特定のユーザーのためではありません。しかし、テーマの設定をユーザーが構成できるようにする方法があり、それはユーザーフィールドを「悪用」することです。

私のテーマでは、ユーザーは実際に壁紙(画像、背景色とのブレンド、コンテンツの半透明度)に対して3つの設定を持っていますが、この投稿では壁紙画像という単一の設定に焦点を当てます。

ステップ1:ユーザーフィールドの作成

ユーザーは、壁紙なし、またはさまざまな壁紙を選択するオプションがあります。ユーザーフィールドには、次のオプションを持つオプションのドロップダウンフィールドを作成しました。

  1. なし – 壁紙なし、これは重要です
  2. デフォルト – ユーザーが選択しなかった場合
  3. 代替1
  4. 代替2

これはオプションフィールドなので、デフォルト値は空白になります。空白の値の場合、デフォルトの壁紙を使用したいと思います。

ステップ2:CSS

common/common.scssで以下を定義しました。

:root {
  --wallpaper-default: url(#{$img-wallpaper-default});
  --wallpaper-alternative-one: url(#{$img-wallpaper-alternative-one});
  --wallpaper-alternative-two: url(#{$img-wallpaper-alternative-two});
  /* 初期画像の切り替えの可能性を減らすために、デフォルトはnone */
  --wallpaper: none;
}

body {
  background-image: var(--wallpaper);
}

:rootにはさまざまな壁紙画像の変数(テーマアセット)が含まれています。背景画像は--wallpaper変数に設定されており、これは最初はnoneです。したがって、CSSがロードされると、デフォルトで壁紙は表示されません。これは、ユーザーが元の壁紙とは異なる壁紙を選択した場合に発生する可能性のある「ちらつき」を削除するために行われます。

選択可能な各壁紙には、--wallpaper-xx-yy-zzという変数が割り当てられます。ユーザーフィールドの値はプレーンテキストです。その値に基づいて、背景画像として使用する正しいCSS変数を選択します。

ステップ3:テーマ設定

JavaScriptコードでは、壁紙設定を含むユーザーフィールドを知る必要があります。これを取得する最も簡単な方法は、ユーザーフィールドIDを直接参照することです。したがって、このユーザーフィールドIDを構成するためのテーマ設定を作成する必要があります。

wallpaper_user_field:
  default: -1
  type: integer

ステップ4:壁紙の設定

これにはJavaScriptを記述する必要があります。このコードはできるだけ早く実行される必要があるため、初期化子 javascripts/discourse/initializer/your-theme-component.js を作成します。

基本的なことから始めましょう。

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;
    // `html`要素を取得
    this.styleRoot = document.body.parentElement.style;
    this.loadWallpaperSettings();
  }

  // 後でさらにロジックが追加されます
}

export default {
  name: PLUGIN_ID,
  initialize(owner) {
    withPluginApi('1.34.0', (api) => {
      this.instance = new YourThemeWallpaper(owner, api);
    });
  }
}

これは、テーマコンポーネントJavaScriptを追加するための大量の定型コードです。styleRoot変数は、CSSの効果を変更するために使用するドキュメントのhtml要素を参照する必要があります。

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] || '';
      this.setWallpaper(wp);
    });
  } else {
    this.setWallpaper('');
  }
}

ユーザーフィールドの値を取得するには、追加のAPI呼び出しを行う必要があり、これには少し時間がかかる場合があります。これが返されたら、ユーザーフィールドの値を取得し、setWallpaperを呼び出してその値を渡します。settings.wallpaper_user_fieldは、ステップ3のテーマ設定への参照です。設定が構成されていない場合、wpフィールドは空文字列にデフォルト設定されます。

現在のユーザーがいない場合は、空文字列でsetWallpaper関数を呼び出してデフォルトの壁紙を読み込みます。

setWallpaper関数は、実際に壁紙を更新します。

setWallpaper(wallpaper) {
  if (!wallpaper || wallpaper === '') {
    // 設定されていない場合はデフォルトを使用
    wallpaper = 'default';
  }
  // 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+')');
  }
}

構成された壁紙設定が"none"だった場合は、背景画像を削除します。それ以外の場合、値はCSS変数に似たものになるように正規化されます。「代替1」は、CSS変数--wallpapervar(--wallpaper-alternative-one)に設定することになります。ユーザーフィールドの値がCSS変数にマッピングされない場合、実質的に壁紙なしになりますが、これは十分です。

ステップ5:高速読み込み

上記のセットアップはうまく機能します。しかし、追加のAPI呼び出しにより、壁紙の設定がかなり遅くなる可能性があります。これは良い結果をもたらしません。これを解決するために、ブラウザのローカルストレージを使用して設定を保存および取得します。

コンストラクタで、ユーザーの壁紙設定を読み込む前に、まずローカルストレージからのデータに基づいて壁紙を設定しようとします。ユーザー設定を読み込むときに、ローカルストレージを構成されたもので更新します。

const WALLPAPER_KEY = 'your_theme_wallpaper';

constructor(owner, api) {
  setOwner(this, owner);
  this.api = api;
  // `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);
  }
}

これにより、Discourseの読み込みの早い段階で壁紙が設定されるようになります。実質的にsetWallpaper関数は2回呼び出されます。しかし、値が変更されることはめったにないため、これは目立ちません。ユーザーが壁紙を変更した場合にのみ、次回の読み込み時にちらつきが発生します。これも、CSSの初期壁紙がデフォルトではなくnoneに設定されている理由です。

ステップ6:ライブプレビュー

Discourseで配色を変更すると、新しい配色が表示され、どのように見えるかを確認するために保存して再読み込みする必要はありません。壁紙設定でも同様に便利で、ユーザーは最も気に入ったものを確認できます。

これを実現するには、ユーザーフィールドコンポーネントを変更するという追加の工夫が必要です。

コンストラクタの最後に、this.setupLivePreview()への呼び出しを追加しました。これには以下が含まれます。

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);
      }
    }
  });
}

最初の部分では、ページ変更イベントをリッスンしています。wallpaperPreviewedがtrueの場合、ユーザーが元々構成した設定を再読み込みします。ユーザーがユーザーフィールドの新しい値を保存しなかった場合、壁紙は元の設定に戻されます。

次に、実際の魔法です。ドロップダウンユーザーフィールドコンポーネントを変更して、didUpdateAttrs関数に追加のコードを追加します。これは、ユーザーフィールドに新しい値が割り当てられた場合に呼び出されます。この場合、wallpaperPreviewed変数をtrueに設定して、ページ変更時に保存された設定が読み込まれるようにします。次に、setWallpaper関数を呼び出して、現在選択されている壁紙を表示します。

他のユーザーフィールドタイプでも同様のことを行うことができます。たとえば、プレーンテキストフィールドの場合は、component:user-fields/textクラスを変更します。

結論

これにより、ユーザーフィールドを使用して、テーマコンポーネントがユーザーエクスペリエンスにどのように影響するかをユーザーに制御させることができます。

唯一の問題は、ユーザーフィールドが_Preferences > Profile_の下にあり、おそらく配置したい_Preferences > Interface_の下にないことです。

「いいね!」 9