React用、仮想Windowコンポーネント作成の流れ
仮想Windowコンポーネントとは
Webブラウザ内にWindowSystemを構築するためのコンポーネントです。
動作画面を見てもらえれば、だいたい何なのかは想像が付くと思います。PC用OSのようなWindowをブラウザで表示します
リンク
source code
Github上に置いてあります
storybook
npm
npmでパッケージを公開しています
作成した時の話
必要な機能の列挙
- ドラッグドロップによる移動
- ドラッグドロップによるリサイズ
- 最大化、最小化、閉じる
- フォーカス、アクティブウインドウの管理
- Windowの親子関係
- オーバーラップ(親Windowの外に出る)とチャイルド(親ウインドウから出られない)スタイル
以上の機能を最低限として作成していきます
必要コンポーネントの確定
Parts | Description |
---|---|
Icon | タイトルで使用するアイコン |
TitleBar | Windowのタイトルバーの部分 |
ResizeFrame | サイズ変更用の不可視フレーム |
Client | クライアント領域(オーバーラップ制御に必要) |
VirtualWindow | 全てを統合した仮想Windowコンポーネント |
Windowロジックを制御するためのHookの作成
useWindow
という座標やステータス管理を行うHookを作成します。基本的には表示以外の全てをここにぶっ込みVirtualWindowコンポーネントから呼び出します。これで表示制御とロジックを分離します。
関数コンポーネントを作る際は極力、フックとカスタムフック、そして仮想DOMのみしか残らないように記述していきます。
作成中にあると便利なので使った物
Storybook
パーツコンポーネントを作っていく過程で重宝します。ソース中にTSDocを書いておけば、ドキュメントの確認にも使えます。
Scaffold
コンポーネントを作るときにスタイル定義ファイルやStorybookのファイルをコマンド一発で自動生成するのに使います。これを用意しておくだけで、新規コンポーネントを作る際に初期ファイルの用意で気力が萎えるという問題が生じにくくなります。
Lint
文法を統一するための物ですが、自動修正で楽をするためのものでもあります。これを設定しておけば、コード自体は適当に書いて自動修正という流れで進められます。
CI/CD
文法チェックとビルドチェック、さらにStorybookの静的ビルドでgh-pagesを作成を行います。ブランチごとに生成する形にしておくと、動作確認が楽になります。また、PR時に差分が見られるようにビジュアルリグレッションテストも組み込んでおくと、修正ポイントの検出が容易になります。
- ビジュアルリグレッションテストの出力結果は以下のようにしています
https://reactlibraries.github.io/virtual-window/?branch=feature-fix-text--master&prefix=Diff&target=master
このコンポーネントの使い方
<>
<VirtualWindow title="Top Window1" x={10} y={10}>
contents
<VirtualWindow
title="Child Window(Client)"
width={400}
height={200}
overlapped={false}
>
Inside only
</VirtualWindow>
<VirtualWindow
title="Child Window(Overlap)"
x={160}
y={250}
width={400}
height={200}
active={true}
>
Can go outside
</VirtualWindow>
</VirtualWindow>
<VirtualWindow title="TopWindow2" x={350} y={350} width={300} height={200}>
contents
</VirtualWindow>
</>
<VirtualWindow>
にパラメータを記述してサイズなどを設定することが出来ます。また、ウインドウの中にウインドウを入れたりも可能です。子ウインドウに関しては親ウインドウの内部のみに表示するか、外側に出られるかの属性を設定できます。
仮想ウインドウなので、ドラッグドロップによるサイズの変更や移動に対応しています。重ね合わせの計算も行っているので、アクティブウインドウが先頭に来るように切り替えが行われます。
細かい使い方
パラメータの他にrefでdispatchを受け取って、プログラム側からウインドウを閉じたり、サイズの変更をしたりというもの出来るようになっています。
<VirtualWindow> parameters
Name | Type | Default | Description |
---|---|---|---|
title | ReactNode | "" | Window title |
overlapped | boolean | true | Whether to set position to fixed |
titleEnable | boolean | true | Whether to display the title |
titleSize | number | 32 | Title bar size |
titleButtons | {} | {max:true,min:true,close:true} | Presence or absence of a button attached to the title |
active | boolean | false | Whether to activate in the initial state |
baseX | start | center | end | start | Placement criteria in the X direction |
baseY | start | center | end | start | Placement criteria in the Y direction |
x | number | 0 | Initial X position |
y | number | 0 | Initial Y position |
width | number | 640 | Initial width |
height | number | 480 | Initial height |
state | normal | max | min | close | normal | Window state |
frameSize | number | 1 | Frame size |
resize | boolean | true | Whether to allow resizing |
resizeBold | number | 8 | Invisible frame size for resizing |
clientStyle | React.CSSProperties | undefined | Client style |
clientClass | string | undefined | Client class name |
clientMovable | boolean | false | Whether the client can be dragged and dropped |
dispatch | Ref | undefined | Dispatch for parameter setting |
onUpdate | (params: WindowParams) => void | undefined | State change event |
WindowParams
Name | Type | Description |
---|---|---|
active | boolean | Window active |
x | number | Current x |
y | number | Current y |
width | number | Current width |
height | number | Current height |
state | normal |max|min |close | Window state |
init | boolean | Initial display |
dispatch
dispatch({type:"state", payload:'normal'|'max'|'min'|'close'});
dispatch({type:"position", payload:{x:number,y:number}});
dispatch({type:"size", payload:{width:number,height:number}});
まとめ
作ってはみたものの、誰も使わん
Discussion