ちょっと何言ってるかわかんないと言われるであろうので補足すると、CLI 風の見た目 のGUIをCSSで 作ってみたので OSS として公開して使えるように してみた、ということになります。車輪の再発明的な遊び。
成果物
UI : https://cli-gui.firebaseapp.com
レポジトリ : https://github.com/kyoyababa/cli-gui
できること
基本的な機能として以下だけ実装してみた。
- clear : ログのクリア
- help : コマンド一覧の出力
- version : アプリバージョンの出力
それだけだとつまらないので、引数をとるサンプルとして以下を追加した。
- cats : 猫のデータベースから品種を一覧して出力(品種国のフィルタと名前のソートつき)
技術仕様
ターミナル風のUIを作るために Angular を使いたかったので Angular CLI で開発した。
HTML
HTMLはシンプルで、以下のみ。あとはコントローラ側で制御する。
<div id="jsi-cliLog" class="g-cli__wrapper" (click)="focus()">
<pre [innerHTML]="cliLog"></pre>
<form (submit)="addLog()" novalidate>
<span>$</span>
<input id="jsi-cliValue" type="text" name="cliValue" [(ngModel)]="cliValue" />
</form>
</div>
CSS
ターミナル風のUIは以下のCSSで再現した。以下に記載したのはおおまかな部分のみで細かい調整の部分は割愛。コントローラ側で生成したHTMLにスタイルを当てるためにAngularの /deep/
空間を使っている。 (全体は こちら から)
:host /deep/ .g-cli__wrapper {
overflow: auto;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: #000000;
font-family: monospace;
color: #FFFFFF;
> form {
> input {
width: calc(100vw - 1em * 3 - 0.5em);
border: 0;
outline: 0;
background-color: inherit;
color: inherit;
}
}
}
<input>
へのfocus
ターミナルライクにどこをクリックしても入力を開始できるように、 <div>
全体のどこをクリックしても <input>
にfocusできるようにした。ログ上の文字列をドラッグして選択している場合はfocusしたくないので、 window.getSelection()
をみて分岐。
public focus(): void {
if (window.getSelection().toString()) return;
document.getElementById('jsi-cliValue').focus();
}
入力値の出力
<input>
フィールドでEnterしたら、コントローラのモデル this.cliValue
を this.cliLog
に追加して、入力値を判定してコマンドになっていたら処理する。あとは、あとで上キーと下キーで過去の入力値を呼び出したいので this.history
に登録して、モデルをクリアする。
public addLog(): void {
this.cliLog += `
<span>-(cli-gui)</span> : ${this.generateDateString()}
<span>$</span> ${this.cliValue}`;
this.analyzeInput(this.cliValue);
this.history.push(this.cliValue);
this.lastAttachedHistoryNumber = null;
this.initializeValue();
}
コマンド入力値の判定と処理
入力した値をスペースごとに分割して配列にし、 cli-gui
で始まっていたらコマンドとして処理( this.do()
)する。
private analyzeInput(value: string): void {
const commands = value.trim().split(' ').filter(val => val !== '');
if (commands.length === 0) return;
if (commands[0] === 'cli-gui' && commands.length > 0) {
this.do(commands);
} else {
this.cliLog += `
<em>command ${commands[0]} is not found.</em>`;
}
}
こんな感じ。いちどに複数のコマンドが入力されることは考慮しない(ことにした)ので、 commands[1]
のみを見ている。
private do(commands: Array<string>): void {
switch (commands[1]) {
case 'clear' {
...
} break;
case 'help': {
...
} break;
case 'cats': {
...
コマンドの引数を見て処理を分岐
猫のデータベースから品種を一覧して出力(品種国のフィルタと名前のソートつき)
については引数をとって処理を分岐したいので、少し実装を追加して、コマンドの引数とその引数の次に入力されている値を見るようにした。
case 'cats':
case '-l': {
let targetCats = this.cats;
for (let i = 2; i < commands.length - 2; i++) {
switch (commands[i]) {
case '--country':
if (!commands[i + 1]) {
this.cliLog += `
<em>'country' filter must be supplied an argument.</em>`;
return;
}
targetCats = targetCats.filter(cat => cat.country.toLowerCase() === commands[i + 1].toLowerCase());
break;
case ...
上スクロール方式にするための強制スクロール
CLIの場合、ログがたまっていっても常に最下部に入力フィールドが表示されていないといけない。かつ、ユーザーが過去のログを溯ろうとして上にスクロールすることもできる(なので、CSSのposition属性で強制的に画面下部に入力フィールドを固定することはできない)。
そこで、(あまりいい解決策ではないようにも思うが)ユーザーが文字列をドラッグしているのでない限り、常に画面を最下部にスクロールすることにした。以下のメソッドはつねにテンプレートから呼び出されている。
public fetchingForScrolling(): void {
if (window.getSelection().toString()) return;
document.getElementById('jsi-cliLog').scrollTop = Number.MAX_SAFE_INTEGER;
}
このアプリの使い道
正直おもいつかないけど、いつか使いたくなる日がくるはず。たぶん。