srcフォルダ設計ドキュメント
あーしがsrc/の構造と各主要コンポーネントの設計思想と実装概要をまとめるよ〜✨
目的(Overview)
このリポジトリは配信オーバーレイ(OBS用)とその管理パネルを提供するReactアプリケーションだよ。src/には以下の主要な役割があるよ:
- ブラウザでの実行(管理パネル)とOBSブラウザソース(オーバーレイ)の両方をサポート
- Zustandによる単一のグローバルストアを用い、ローカル永続化・同期を実装
- ドラッグ / リサイズ可能なオーバーレイ領域を共通化して実装
- テーマ、テキスト、勝率、画像などの表示/設定を分離
入口 (Entrypoints)
src/main.tsx- 管理画面(Control Panel)をマウントするエントリ。
<App />をrootにレンダー
- 管理画面(Control Panel)をマウントするエントリ。
src/overlay-main.tsx- OBSブラウザ(透明背景・固定サイズ向け設定)として直接マウントするエントリ。
<Overlay />をrootにレンダー
- OBSブラウザ(透明背景・固定サイズ向け設定)として直接マウントするエントリ。
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検出、座標スケール関数など
generateOBSUrlやgetOBSSettingsは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の補完やリセット処理を行う
同期戦略(まとめ)
同期手段は以下の優先順位を想定している(実行時の環境により使い分け):
- Electron IPC (if available): 高信頼で直接同期
- WebSocket (OBSブラウザ): 開発サーバを使うと相互にPush/受信
- BroadcastChannel: 同一ブラウザ / ドメイン間の軽量同期
- localStorage + Polling: OBS local file mode や BroadcastChannelが使えない場合のフォールバック
- 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 を登録する必要はないよ。
サンプル(簡易):
// 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 からのインポート生成)
変更/拡張ポイントとガイドライン
- 新しいオーバーレイ領域を追加する
src/components/overlay/に新コンポーネントを作成- UI での表示制御用 state を
useOverlayStoreに追加し、persist/同期に登録 - Overlay.jsx で
DraggableAreaを利用して表示 - ControlPanel に設定UIを追加
- 画像追加/更新
public/images/配下に画像を追加、scripts/generate-image-imports.cjsを実行してsrc/utils/imageImports.tsを更新getAllImages()などで image metadata を取得して UI に反映
- レイアウト初期値の調整
src/constants/layoutDefaults.tsを編集- ページリロードして初期デフォルトを適用する(
localStorageをクリア)
- ストアに新フィールドを追加する方法
src/types/index.tsのOverlayStateにプロパティを追加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は
useDraggableとre-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の拡張方法など)を作るよ〜💖