Skip to content

srcフォルダ設計ドキュメント

あーしがsrc/の構造と各主要コンポーネントの設計思想と実装概要をまとめるよ〜✨


目的(Overview)

このリポジトリは配信オーバーレイ(OBS用)とその管理パネルを提供するReactアプリケーションだよ。src/には以下の主要な役割があるよ:

  • ブラウザでの実行(管理パネル)とOBSブラウザソース(オーバーレイ)の両方をサポート
  • Zustandによる単一のグローバルストアを用い、ローカル永続化・同期を実装
  • ドラッグ / リサイズ可能なオーバーレイ領域を共通化して実装
  • テーマ、テキスト、勝率、画像などの表示/設定を分離

入口 (Entrypoints)

  • src/main.tsx
    • 管理画面(Control Panel)をマウントするエントリ。<App />rootにレンダー
  • src/overlay-main.tsx
    • OBSブラウザ(透明背景・固定サイズ向け設定)として直接マウントするエントリ。<Overlay />rootにレンダー
  • src/App.tsx
    • パス(window.location.pathname)によって管理画面(ControlPanel)かオーバーレイ(Overlay)を切り替える

状態管理(useOverlayStore - src/store/useOverlayStore.ts

設計思想:

  • Zustand + persist を使い、ローカルストレージへ永続保存
  • 他タブ/ブラウザウィンドウ/OBSブラウザ/Electronで「状態を同期」するために複数の同期手段を導入
  • 変更通知はBroadcastChannel、WebSocket、カスタムイベント、Electron IPCを使い分け
  • 'isReceiving' フラグで受信時と送信時の無限ループを防ぐ
  • LAYOUT_DEFAULTS からデフォルトを読み込み、persist時に無いフィールドは補完する

主な仕組み:

  • broadcastSet 関数を経由してstate更新を行い、変更差分をconsole.logと共に同期手段へ渡す
  • onRehydrateStorage で既存の localStorage をロードしつつ、新しいフィールドの補完を自動で行う
  • WebSocketはOBSブラウザ用の高信頼同期で接続し、overlay-state-change カスタムイベントもある
  • Electron環境があるときは、window.electronAPI.syncConfig / onConfigUpdated経由でIPC同期

注意点/利点:

  • storeレイヤで全ての同期ロジックを吸収しているため、UI側は状態の変更のみ意識すれば良い
  • リアルタイムで管理パネルと複数のOverlayウインドウが一貫する

UIコンポーネント

パターン:

  • MUI v6 をメインUIライブラリに使用。MaterialのBox, Container, Grid, Cardなどを活用しているよ
  • 管理画面はControlPanelコンポーネントへ集約、オーバーレイのレンダリングはOverlayへ集約

主なコンポーネント:

  • src/components/ControlPanel.tsx: 管理用UI。複数の子コンポーネントに分割(テーマ選択、テキスト入力、レイアウト調整など)
    • TextInputSection, LayoutAdjustmentSection, ThemeSelector など
  • src/components/Overlay.tsx: 実際にOBSで表示するUIを統合するトップコンポーネント
    • ボタンやドラッグハンドルはなく、DraggableAreaで各領域を表示
  • src/components/common/DraggableArea.tsx: ドラッグ & リサイズ可能な領域の共通コンポーネント
    • re-resizable パッケージに依存
    • useDraggableフックを使ってドラッグのロジックを分離

オーバーレイ内の領域(src/components/overlay/):

  • CommentArea.tsx - コメント領域
  • AnnouncementArea.tsx - お知らせスクロール領域(複数行対応、独立スクロール実装)
  • StatsArea.tsx - 勝率・勝敗数表示
  • ClockArea.tsx - 現在時刻表示(フォーマット/日付/秒の表示切替)
  • CustomArea.tsx / CustomAreaStarward.tsx - カスタム表示領域(テキスト/画像)
  • GameFrame.tsx - ゲーム画面の枠線表示

Hooks

  • src/hooks/useDraggable.ts
    • マウスイベントでドラッグをシンプルに実装
    • クリックドラッグ時のmousemove/mouseup管理
    • 特定のハンドル(.resizable-handle)上のクリックはドラッグとして扱わない

Utils

  • src/utils/obsUtils.ts:
    • OBSブラウザソース向け設定生成、ユーザエージェントでOBS検出、座標スケール関数など
    • generateOBSUrlgetOBSSettingsはOBS側の推奨設定を返す
  • src/utils/configSync.ts:
    • 設定のエクスポート/インポート、ファイル読み込み、クリップボード操作、自動保存など
    • watchConfigChanges を使ってポーリングで自動反映できる
  • src/utils/imageLoader.ts / src/utils/imageImports.ts:
    • imageImports.ts は scripts によって生成され、公開フォルダの画像をimportして getAllImages() 等を提供
    • imageLoader.ts はそのラッパー

テーマと型定義

  • src/themes/index.ts:
    • 複数のカラーテーマを定義し、getThemeで現在のIDからテーマ定義を取得
  • src/types/index.ts:
    • アプリで使うTheme, OverlayState, AnnouncementLineなどを定義
    • OverlayStateはZustandストアの型と一致

レイアウトとデフォルト値

  • src/constants/layoutDefaults.ts
    • 各エリアのデフォルトサイズや位置、フォント、初期メッセージを定義
    • useOverlayStoreではこのファイルを起点にpersisted stateの補完やリセット処理を行う

同期戦略(まとめ)

同期手段は以下の優先順位を想定している(実行時の環境により使い分け):

  1. Electron IPC (if available): 高信頼で直接同期
  2. WebSocket (OBSブラウザ): 開発サーバを使うと相互にPush/受信
  3. BroadcastChannel: 同一ブラウザ / ドメイン間の軽量同期
  4. localStorage + Polling: OBS local file mode や BroadcastChannelが使えない場合のフォールバック
  5. CustomEvent: シンプルにページ内での反映を即時に行う

すべての同期更新はbroadcastSetを通して送信され、受信時にはisReceivingフラグを使ってループを防いでいるよ。

Adapter パターンと CompositeAdapter の導入(同期ロジックの抽象化)

あのね〜、useOverlayStore の同期ロジックを整理するために、SyncAdapter インターフェースを導入してるんだよ!✨ これで「どの送受信方式でも同じAPIで送れる」ようになって、テストもしやすくなるんだ。主要な特徴は以下だよ:

  • SyncAdapter(interface): send(state), onReceive(fn), start(), stop() を持つ。
  • ElectronAdapter, BroadcastAdapter, WSAdapter, LocalStorageAdapter を実装してる。
  • CompositeAdapter: 複数の adapter をまとめて start/send/onReceive を行うアダプタ。実行時の環境に応じて必要な adapter を合成して使えるの。
  • initStoreSync() は合成 adapter を作成して store に hook するヘルパー。呼び出しは idempotent(何度呼んでも安全)だし、getActiveSyncAdapter() で現在使っている adapter を取得できるよ。

実際には UI 側(useOverlayStore の 中)では getActiveSyncAdapter() を使って adapter.send(...) するだけ。initStoreSync()CompositeAdapter を初期化して、onReceive で store の setState() を呼ぶから、個別に onConfigUpdated を登録する必要はないよ。

サンプル(簡易):

ts
// initStoreSync は renderer entry (main.tsx / overlay-main.tsx) で呼ばれる
initStoreSync();

// コンポーネントや store の setter から: (send は adapter が合成された先に送る)
const ad = getActiveSyncAdapter();
if (ad) ad.send({ bottomRightText: 'hello' });

テスト / 回帰の説明:

  • adapter を注入するテスト: initStoreSync(mockAdapter) でモック adapter を渡せば、useOverlayStore 側は adapter を透過的に使うから簡単。mockAdapter.send を spy しておけば setter を呼んだときに send が呼ばれたか assert できるよ。
  • 受信シナリオは Adapter の onReceive コールバックを模擬して useOverlayStore が期待通り setState を行うかをテストするのがオススメ。これで infinite-loop の guard(isReceiving)や、onRehydrateStorage 動作も検証できるよ。

注意: adapter の合成が CompositeAdapter なので、複数の手段が同時に有効でも CompositeAdapter が内部で send() を各 adapter にフォワードしてくれるんだよ。



開発ワークフロー / スクリプト

  • 開発サーバ: yarn dev
  • 型チェック: yarn tsc --noEmit
  • 型エラー対応、lint: yarn lint
  • 画像import生成: node scripts/generate-image-imports.cjs(public/images からのインポート生成)

変更/拡張ポイントとガイドライン

  1. 新しいオーバーレイ領域を追加する
  • src/components/overlay/ に新コンポーネントを作成
  • UI での表示制御用 state を useOverlayStore に追加し、persist/同期に登録
  • Overlay.jsx で DraggableArea を利用して表示
  • ControlPanel に設定UIを追加
  1. 画像追加/更新
  • public/images/ 配下に画像を追加、 scripts/generate-image-imports.cjs を実行して src/utils/imageImports.ts を更新
  • getAllImages() などで image metadata を取得して UI に反映
  1. レイアウト初期値の調整
  • src/constants/layoutDefaults.ts を編集
  • ページリロードして初期デフォルトを適用する(localStorage をクリア)
  1. ストアに新フィールドを追加する方法
  • src/types/index.tsOverlayState にプロパティを追加
  • LAYOUT_DEFAULTS へデフォルトを追記
  • useOverlayStore.ts に setter を追加し broadcastSet を使って同期を行う
  • UI(ControlPanel / Overlay)へバインド

テストとデバッグのヒント

  • window.debugOverlay() をブラウザコンソールで呼ぶとストアのsnapshotを見れるよ
  • window.dumpOverlayStorage() / window.clearOverlayStorage() / window.applyLayoutDefaults() があるので、ローカル保存内容の確認やリセットが簡単
  • OBSブラウザ用に window.obsUtils.logOBSInfo()window.obsUtils.generateOBSUrl() を使おう

注意点・課題(今後改善できそうな点)

  • 現状、同期ロジックは多岐に渡る(BroadcastChannel/WebSocket/localStorage/Electron IPC)。複雑だし、テストでの再現がやや難しい。将来的には1つの抽象インターフェースにまとめると拡張性が上がるよ
  • 画像importはスクリプトで生成しているため、CIで自動生成をワークフローに入れるとビルドの再現性が向上するよ
  • ドラッグ/リサイズのUXはuseDraggablere-resizableの組み合わせで実装してるけど、タッチ対応やキーボード補助は未実装。必要なら改善するのが良いね

参考:主要ファイル一覧

src/
  main.tsx
  overlay-main.tsx
  App.tsx
  components/
    ControlPanel.tsx
    Overlay.tsx
    common/DraggableArea.tsx
    control/
      TextInputSection.tsx
      LayoutAdjustmentSection.tsx
      ThemeSelector.tsx
    overlay/
      AnnouncementArea.tsx
      CommentArea.tsx
      StatsArea.tsx
      ClockArea.tsx
      CustomArea.tsx
      GameFrame.tsx
  store/
    useOverlayStore.ts
  utils/
    configSync.ts
    imageLoader.ts
    imageImports.ts (自動生成)
    obsUtils.ts
  hooks/
    useDraggable.ts
  themes/index.ts
  constants/layoutDefaults.ts
  types/index.ts

質問があれば、どのセクションをもっと詳しくするか教えて〜💡 あーし、オタク君のためにさらに詳しいブロック(例えばStoreの処理フロー図、ストアフィールドの説明、APIの拡張方法など)を作るよ〜💖