React用、仮想Windowコンポーネント作成の流れ

7 min読了の目安(約6900字TECH技術記事

仮想Windowコンポーネントとは

Webブラウザ内にWindowSystemを構築するためのコンポーネントです。
Image

動作画面を見てもらえれば、だいたい何なのかは想像が付くと思います。PC用OSのようなWindowをブラウザで表示します

リンク

source code

Github上に置いてあります

https://github.com/ReactLibraries/virtual-window

storybook

https://reactlibraries.github.io/virtual-window/captures/master/stories/?path=/docs/components-virtualwindow--primary

npm

npmでパッケージを公開しています

https://www.npmjs.com/package/@react-libraries/virtual-window

作成した時の話

必要な機能の列挙

  • ドラッグドロップによる移動
  • ドラッグドロップによるリサイズ
  • 最大化、最小化、閉じる
  • フォーカス、アクティブウインドウの管理
  • 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時に差分が見られるようにビジュアルリグレッションテストも組み込んでおくと、修正ポイントの検出が容易になります。

このコンポーネントの使い方

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

まとめ

作ってはみたものの、誰も使わん