Mona Lisacoです。真夏に技術ブログを書くのは初めてかもしれません。
今回も懲りずに大爆笑アプリケーションを構築し、界隈で人気の絶頂を誇るあの名高い雑貨屋の業務改善に取り組んでいきましょう。
(※ この記事はchillSAP 夏の自由研究2020の8月21日分の記事として執筆しています)
あらすじ
雨にも風にも流行り病にも負けず、例の雑貨屋の麹町一丁目では今日もあの手この手の業務に大忙し。
しかし店舗スタッフのりさこには、これまでずっと誰にも言えずにきた秘密の悩みが……
思わず口をついて出たりさこの悩みを聞き、これまでの社員教育とは一体……!?と思わず打ちひしがれそうになる店長のzukahira。
これまでもこれからもドリフターズと共にあるであろうこの雑貨店の運命のことをりさこはきちんと理解しているのだろうか???
そう、でもこれはりさこだけの問題ではなく、この店全体の問題でもあるのです。
これまでこの雑貨店では、ジェネレーションギャップを考慮しない社員教育を行ってきたのではないでしょうか?
ドリフターズのことなら誰でも知っているのは当たり前だという前提で業務を任せてしまっていたのではないでしょうか?
シニアの社員が披露するネタを分かっているふりをして作り笑いばかりしている若いスタッフの本心を見て見ぬ振りをしてきたのではないでしょうか?
というわけで、今回は大人可愛い大爆笑わがままUIのドリフターズメンバー図鑑を、Fundamentals StylesとAngularを用いて構築していきます。
はじめに
作る機能の概要
今回の記事では、Fundamental StylesとAngularを用いてドリフターズメンバー図鑑のフロントエンド部分を構築していきます。
バックエンド部分の実装は省略し、モックデータを表示させることにします。
機能はざっくりと下記の5つです。
- 画面を表示したら大爆笑の音を再生し、画面遷移しても再生し続けること。
- ドリフターズのメンバーの名前をリスト表示すること。
- リストの名前をクリックしたら、各メンバーの詳細情報を画面表示すること。
- 編集モードにしたら、各メンバーの情報を編集できること。
- 編集モードを終了したら、データを更新しましたというダミーメッセージを表示すること。
また、下記のようなURLのルーティングを行います。
- URLの末尾
/members/[id]
のidに対応して、画面表示される詳細情報を変化させる。 -
/members/1
: いかりや長介 -
/members/2
: 高木ブー -
/members/3
: 仲本工事 -
/members/4
: 加藤茶 -
/members/5
: 志村けん
使用する技術について
Fundamental Styles
Fundamental Stylesとは、SAP Fiori風のUIをさまざまな画面に対して適用できるオープンソースのフレームワークです。
SAP FioriのUIが好きで好きでたまらないけれどSAPUI5を使用しないアプリを構築しなければならないときに使用することができます。
Fundamental Stylesは、現在のWebアプリ構築において主流のSPA(Single Page Application)フレームワークであるVue.js, React, Angularにそれぞれ特化させたフレームワークを用意しており、今回はAngularのものを使用します。
Angular
Angularとは、フルスタックでごりごりSPAを構築できるTypeScriptベースのフレームワークです(Googleおよびコミュニティによって提供)。
SPAとは、技術的には単一のページの中でコンテンツを切り替えたりURLを書き換えたりすることで、見た目の挙動としてはページ遷移をしているように見せることのできるWebアプリです。
何が嬉しいかというと、ページ遷移に関わるブラウザの挙動に縛られないUIの実装を行うことができ、また一旦初期表示をしてしまったあとは画面遷移後の読み込みが少なくて済むのでぬるぬると動かすことができます。
今回の実装で言えば、下記のような機能がSPAだからこそ実現しやすいものだと言えます。
- 大爆笑の音を再生し、画面遷移しても再生し続ける機能(SPAフレームワークを使わずに実装すると、ページ遷移時に音声ファイルをロードし直すため継続して再生させることが難しいはずです。)
- URLの変更とともに詳細エリア部分の画面表示内容を変更しても、一覧エリア部分はそのまま表示され続ける
他に、たとえば下記のようなWebアプリの機能はSPAだからこそ作りやすそうな機能であるように思えます。
- 画面の一部にチャットウィンドウを開いたまま画面遷移できるFacebookの機能
- メール作成ウィンドウを開いたまま画面遷移できるGmailやOffice 365の機能
- 画面の一部に動画を表示したまま画面遷移できるYoutubeの機能
このようにわかりやすく便利な機能以外にも、画面遷移にとらわれずぬるぬる動くさまざまなデザインのUIを構築することが可能です。
Webアプリの最先端UXを提供するならSPAで作るのが最適ということになります。
今回の記事でWebアプリの最先端UXを構築できるのかどうかわかりませんが、とりあえず作ってみましょう。
前提条件
開発環境
開発環境は以下の通りです。
- MacOS Mojave 10.14.6
- node 12.16.1
- npm 6.13.4
- Angular CLI 9.1.6
この記事の記載内容範囲
Angularの全貌は一つの記事では扱いきれません。
そこで今回の記事では、AngularのアプリにFundamental StylesのUIを適用するために必要なステップ(と、おまけで大爆笑無限連続再生の実装)に絞って扱います。
序盤の手順はこの記事で詳しめに記載しているので、前提知識なしで途中までは実装することができますが、
中盤以降の手順についてはAngularをちゃんと頑張らないといけないため記載を省略しています。
Angularはものすごく長大な公式チュートリアルが用意されており、こちらをやってみることでAngularのことがぼやっとわかるようになります。
今回作ったアプリで使用している機能はすべて「ツアー・オブ・ヒーローズ」のチュートリアル内で紹介されている実装を応用して作っています。興味のある方はぜひこちらの公式チュートリアルをお試しください。
実装手順
Angularプロジェクト生成
Angularプロジェクトを生成します。プロジェクト名称はfd-ngx-demo
としてみました。
Angular routingが必要か聞かれますがとりあえずデフォルト値のNoを選択します。
また、スタイルシートのフォーマットを聞かれるのでCSSではなくSCSSを選択しましょう。
(こうすることで、あとでFundamental Stylesのフォントを自動で取り込むことができます。)
$ ng new fd-ngx-demo
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? SCSS [ https://sass-lang.com/documentation/syntax#scss ]
生成されたプロジェクトフォルダの中に移動し、生成されたアプリを起動してみます。
$ ng serve --open
ソースを書き換えていくと画面が自動更新されるので、ブラウザは開きっぱなしにしておきましょう。
Fundamental Stylesの準備
※こちらに公式の手順が記載されています。
Fundamental Stylesを使用する準備をしていきます。
下記のコマンドで@fundamental-ngx/core
モジュールを追加します。
聞かれたオプションはすべてデフォルト値を選択します。
$ ng add @fundamental-ngx/core
? Set up browser animations for Fundamental Library for Angular? Yes
? Add default font imports into styles.scss? Yes
このときstyles.scss
にSAPのフォントが自動で追加されます。
また、package.jsonで管理されているnodeモジュールのリストなどが更新され、fundamentalsがアプリ内で使えるようになります。
それではこのモジュールをapp.module.tsにインポートしましょう。
//* ... *//
import { FundamentalNgxCoreModule } from '@fundamental-ngx/core'; //追記
//* ... *//
imports: [
FundamentalNgxCoreModule //追記
]
//* ... *//
これでfundamentalsを使っていく準備完了です。簡単!
新規コンポーネント追加 (1)
まずはcomponentを生成します。
Angularにおけるcomponentとは、画面の特定の部分を制御するかたまりのことです。
componentを生成すると、画面の特定の部分に対応させるためのTypeScript・HTML・CSSファイルなどが追加されます。
今回はShellbar with SideNavigationのUIを使ってみたいので、これに対応させるcomponentとしてfd-shellbar-side-nav
を生成することにします。
$ ng generate component fd-shellbar-side-nav
src/app/app.component.html
には最初に表示されたサンプルのhtmlがたくさん書き込まれているので、全て消して下記に変更します。
<app-fd-shellbar-side-nav></app-fd-shellbar-side-nav>
ちょっと特殊な感じがしますが、上記はコンポーネントセレクタと呼ばれるAngular特有の部品です。
これによってcomponentが画面表示されるようになります。
地味ですが、上記のようになれば成功です。
ちなみに自動生成されたfd-ngx-demo/src/app/fd-shellbar-side-nav/fd-shellbar-side-nav.component.html
はすでにサンプルとして下記が書かれており、これが画面表示されるので上記のような見た目になります。
<p>fd-shellbar-side-nav works!</p>
Fundamental StylesのShellbarModuleを使ってみる
次にFundamental Stylesの部品を使っていきます。
使いたい部品を見つけたら、まずfd-ngx-demo/src/app/app.module.ts
にそのモジュールをimportします。
どのモジュールをimportすればいいかは、各部品のドキュメントの冒頭部に書かれているのでそれをコピーすればOKです。
https://sap.github.io/fundamental-ngx/#/core/shellbar
//* ... *//
import { ShellbarModule } from '@fundamental-ngx/core'; //追記
//* ... *//
imports: [
ShellbarModule //追記
]
//* ... *//
さらに、importした部品を画面に表示させていくため、先ほど生成したcomponentにソースを追加していきましょう。
ここでも、とりあえず最初はドキュメントのサンプルとして紹介されているhtmlとtypescriptをコピーします。
https://sap.github.io/fundamental-ngx/#/core/shellbar#shellbarSideNav
<!-- 下記URLのサンプルhtmlをコピー
https://sap.github.io/fundamental-ngx/#/core/shellbar#shellbarSideNav -->
//* ... *//
export class FdShellbarSideNavComponent implements OnInit {
//* ... *//
condensed: boolean = false; //追記
}
さらにちょっとCSSを追記して見た目を調整します。
// ↓追記
body {
margin: 0;
padding: 0;
}
.shellbar-sidenav-content-example {
display: flex;
}
.fd-side-nav{
height: calc(100vh - 44px);
}
// ↑追記
すると……
え、もうこれだけでもうほぼほぼFioriでは…!? 瞬殺ですね。
リストにドリフターズのメンバーの名前を表示する
モックデータを用意
ここにドリフ情報をいろいろと表示していきたいので、大爆笑モックデータを作っていきます。
まずはデータの型定義用のファイルを作ります。
touch src/app/drifters-member.ts #ファイルを新規作成
作成したファイルにデータのオブジェクトの型を定義していきます。
名前のみならず、ニックネーム、本名、生没年日、身長、血液型、楽器、代表的なネタなどを従業員がサッと参照できるようにしておきたいので、下記のようにしておきましょう。
複数データが存在する場合はArrayとし、それ以外はstringとかnumberとかDateとか。
export interface DriftersMember {
id: number;
name: string;
nicknames: Array<object>;
realname: string;
from: Date;
to: Date;
hight: number;
bloodType: string;
instrument: string;
gag: Array<object>;
}
次にドリフ情報の中身を入れておくファイルを作ります。
touch src/app/mock-drifters-members.ts #ファイルを新規作成
中身はこんな感じで……
import { DriftersMember } from './drifters-member';
export const DRIFTERS_MEMBERS: DriftersMember[] = [
{ id: 1,
name: 'いかりや長介',
nicknames: [{name: '長さん'}, {name: 'ゴリラ'}, {name: '下唇'}],
realname: '碇矢 長一',
from: new Date('1931/11/1'),
to: new Date('2004/3/20'),
hight: 175.2,
bloodType: '不明',
instrument: 'ベース',
gag: [{name: 'オイッスー!'}, {name: '次、行ってみよう'}, {name: 'だめだこりゃ'}]
},
{ id: 2,
name: '高木ブー',
nicknames: [{name: 'ブーたん'}, {name: 'ロクさん'}, {name: '友ちゃん'}, {name: '大ちゃん'}],
realname: '高木 友之助',
from: new Date('1933/3/8'),
to: new Date('9999/12/31'),
hight: 160,
bloodType: 'O型',
instrument: 'リードギター',
gag: []
}, { id: 3,
name: '仲本工事',
nicknames: [{name: 'メガネ'}],
realname: '仲本 興喜',
from: new Date('1941/7/5'),
to: new Date('9999/12/31'),
hight: 160,
bloodType: 'A型',
instrument: 'ボーカル、ギター',
gag: [{name: 'コ・マ・オ・ク・リ・モ・デ・キ・マ・ス・ヨ'}, {name: '最初はグー'}, {name: '二度としません。三度します。'}]
},{ id: 4,
name: '加藤茶',
nicknames: [{name: '加ト茶'}, {name: '加トちゃん'}, {name: 'ヒデ坊'}, {name: 'チャー坊'}],
realname: '加藤 英文',
from: new Date('1943/3/1'),
to: new Date('9999/12/31'),
hight: 163,
bloodType: 'A型',
instrument: 'ドラム',
gag: [{name: '加トちゃんぺ'}, {name: 'ちょっとだけよ〜〜'}]
},{ id: 5,
name: '志村けん',
nicknames: [{name: 'けんちゃん'}, {name: 'シムケン'}],
realname: '志村 康徳',
from: new Date('1950/2/20'),
to: new Date('2020/3/29'),
hight: 168,
bloodType: 'A型',
instrument: 'キーボード、ギター、三味線',
gag: [{name: 'アイーン!'}, {name: '最初はグー'}, {name: 'だいじょうぶだぁー'}, {name: '何だ、チミはってか?'}, {name: 'そうです、私が変なおじさんです!'}]
}
];
アツくなってきました。
サービスの作成
ドリフ情報を取得して返却してあげるサービスを作ります。
下記のコマンドを叩くとfd-ngx-demo/src/app/drifters-member.service.ts
が生成されます。
ng generate service drifters-member
自動生成されたファイルに下記を追記して、ドリフ情報を返してあげるメソッドを作ります。
//* ... *//
import { DriftersMember } from './drifters-member'; //追記
import { DRIFTERS_MEMBERS } from './mock-drifters-members'; //追記
//* ... *//
export class DriftersMemberService {
// ↓追記
getDrifterMembers(): DriftersMember[] {
return DRIFTERS_MEMBERS;
}
// ↑追記
//* ... *//
データを取得
作成したドリフ情報取得サービスを、componentから呼び出して画面で読み込めるようにします。
//* ... *//
import { DriftersMember } from '../drifters-member'; // 追記
import { DriftersMemberService } from '../drifters-member.service'; // 追記
//* ... *//
export class FdShellbarSideNavComponent implements OnInit {
//* ... *//
// ↓追記
driftersMembers: DriftersMember[];
constructor(private driftersMemberService: DriftersMemberService) { }
ngOnInit(): void {
this.getDrifterMembers();
}
getDrifterMembers(): void {
this.driftersMembers = this.driftersMemberService.getDrifterMembers();
}
// ↑追記
}
データをリストに表示
読み込んだドリフ情報を画面表示できるように、HTMLにマッピングします。
下記のようにfd-ngx-demo/src/app/fd-shellbar-side-nav/fd-shellbar-side-nav.component.html
を書き換えてしまいましょう。
<!-- 下記に置き換え -->
<fd-shellbar>
<button fd-shellbar-side-nav fd-button [fdType]="'transparent'" [glyph]="'menu2'"
(click)="condensed = !condensed"></button>
<fd-shellbar-logo>
<a href="#" class="fd-shellbar__logo fd-shellbar__logo--image-replaced" aria-label="SAP"></a>
</fd-shellbar-logo>
<fd-shellbar-title>
ドリフターズのゆかいな仲間たち
</fd-shellbar-title>
</fd-shellbar>
<div class="shellbar-sidenav-content-example">
<fd-side-nav [condensed]="condensed">
<div fd-side-nav-main>
<ul fd-nested-list [textOnly]="false">
<li fd-nested-list-item *ngFor="let driftersMember of driftersMembers">
<a fd-nested-list-link>
<span fd-nested-list-icon [glyph]="'physical-activity'"></span>
<span fd-nested-list-title>{{driftersMember.name}}</span>
</a>
</li>
</ul>
</div>
</fd-side-nav>
</div>
こんな風に、プロパティ名をカッコ{{
}}
で囲むことでドリフ情報を画面にバインドできます。
HTMLのなかに簡単なfor文 *ngFor="let driftersMember of driftersMembers"
を書き込むだけでさらっと画面にドリフ情報のリストを表示することができます。
構造ディレクティブと呼ばれるこうしたAngularの部品はHTMLを便利な表記で操作することができ、他にもIf文やSwitch文などが用意されています。
画面はこんな感じ、画面の左側がいい感じになってきました!!!
新規コンポーネント追加 (2)
画面の右側もいい感じにしていきましょう。
画面の右側にはPanelのUIを使ってみたいので、これに対応させるcomponentとしてfd-panel
を生成することにします。
※必ずしもcomponentとFundamentalsの部品を1:1対応させなければいけないわけではないのですが、いまのところたまたまこういう作りになっています。
$ ng generate component fd-panel
そして、新しく作った画面は最初の画面の中に埋め込みたいので、
fd-ngx-demo/src/app/fd-shellbar-side-nav/fd-shellbar-side-nav.component.html
の中にコンポーネントセレクタを配置します。
<div>
//* ... *//
// ↓追記
<app-fd-panel style="width: 100%; height: calc(100vh - 44px);"></app-fd-panel>
// ↑追記
</div>
すると、サンプルのHTMLが右側に無事表示されました!
この右側部分のコンポーネントに、左のリストで選択されたメンバーのドリフ情報が表示されるようにしていきます。
Fundamental StylesのPanel Moduleを使ってみる
先ほどと同様に、下記のドキュメントを参考にしてPanel Moduleをインポートします。
https://sap.github.io/fundamental-ngx/#/core/panel
//* ... *//
import { PanelModule } from '@fundamental-ngx/core'; //追記
//* ... *//
imports: [
PanelModule //追記
]
//* ... *//
そしてとりあえずサンプルのHTMLを貼ってみます。
<!-- 下記URLのサンプルhtmlをコピー
https://sap.github.io/fundamental-ngx/#/core/panel#panelFixed
-->
すると……
すごい、コピペだけでそれっぽくなるのでやる気が湧いてきますね。
パネル部分にドリフメンバーの詳細情報を表示する
リスト部分で選択されたデータをパネル部分のComponentに連携する
リスト部分のcomponent内で選択されたメンバーの情報に応じて右側のパネルに異なるドリフ情報が表示されるようにしてみましょう。
左のリスト側の実装
まずはリスト部分のcomponent内のHTMLとTypeScriptを少し書き換え、
リスト部がクリックされたらonSelect
というメソッドが呼び出されて、選択した情報を連携できるようにしましょう。
まずHTML上でクリックイベントをメソッドにバインドします。
<!-- <li fd-nested-list-item *ngFor="let driftersMember of driftersMembers"> -->
<!-- この行↑をこれ↓に変更 -->
<li fd-nested-list-item *ngFor="let driftersMember of driftersMembers"
[class.selected]="driftersMember === selectedMember"
(click)="onSelect(driftersMember)">
そしてメソッド内で選択された値を取得するようTypeScriptファイルにメソッドを追記します。
//* ... *//
export class FdShellbarSideNavComponent implements OnInit {
//* ... *//
// ↓追記
selectedMember: DriftersMember;
onSelect(driftersMember: DriftersMember): void {
this.selectedMember = driftersMember;
}
// ↑追記
}
取得した値が右側のcomponentに渡されるようにHTMLのコンポーネントセレクタをさらに書き換えます。
<!-- <app-fd-panel [driftersMember]="selectedMember" style="width: 100%; height: calc(100vh - 44px);"></app-fd-panel> -->
<!-- この行↑をこれ↓に変更 -->
<app-fd-panel [driftersMember]="selectedMember" style="width: 100%; height: calc(100vh - 44px);"></app-fd-panel>
####右のパネル側の実装
こちら側ではちゃんと値を受け取れるようにします。
TypeScriptファイルに@Input
を追加して、ここで値の受け渡しに使う変数を定義します。
//* ... *//
import { Input } from '@angular/core'; // 追記
import { DriftersMember } from '../drifters-member'; // 追記
//* ... *//
export class FdPanelComponent implements OnInit {
@Input() driftersMember: DriftersMember; // 追記
//* ... *//
あとはHTMLをこんな風に変更しましょう。
<fd-panel [fixed]="true" *ngIf="driftersMember; else notSelected">
<h5 fd-panel-title> {{driftersMember.name}}</h5>
<div fd-panel-content [ariaLabel]="'Panel Content'" [id]="'panel-content-1'">
本名:{{driftersMember.realname}}
</div>
</fd-panel>
<ng-template #notSelected>
<fd-panel [fixed]="true">
<h5 fd-panel-title>no member selected</h5>
<div fd-panel-content [ariaLabel]="'Panel Content'" [id]="'panel-content-2'">
<fd-layout-panel-description>好きなメンバーを選んでね!</fd-layout-panel-description>
</div>
</fd-panel>
</ng-template>
上記では基本的にデータをバインドすることしかしていません(データが未選択の場合に表示する画面も簡単なのでついでに書いてしまいました)。
右側に志村けんの本名データが表示されました! 志村康徳さんというお名前だったのですね。
もっと頑張っていい感じにしていく
ここまででfundamentalsの基本的な使い方は伝わったかと思うので、これ以降の手順は概要だけ記載します。
Fundamental Stylesの部品をふんだんに使っていく
上記と同様の要領で、どんどん部品を使っていきます。
今回は下記の部品を試してみました。どれもぬるっとしていて大人可愛い部品です。
Multi Input
持ちネタやニックネームなど複数データの表示・編集に。
https://sap.github.io/fundamental-ngx/#/core/multi-input
####Date Picker
生没年日の表示・編集に。
https://sap.github.io/fundamental-ngx/#/core/datePicker
Combobox
特定の値から選択してデータを選ばせたいときに。
https://sap.github.io/fundamental-ngx/#/core/combobox
Switch
編集モードと照会モードの切り替えに。
https://sap.github.io/fundamental-ngx/#/core/switch
Dialog
完了メッセージの表示に。
https://sap.github.io/fundamental-ngx/#/core/dialog
などなど。。。
編集モードと照会モードを切り替える
fundamentalsの部品には画面内で使用するAPIがいろいろと用意されており、これによって例えば「編集スイッチがオンになった」というようなイベントを簡単に拾うことができます。
https://sap.github.io/fundamental-ngx/#/core/switch/api
Angularは画面モードの制御も簡単にできるので、Switchと組み合わせてさくっと編集モードと照会モードの切り替え機能を作ることができました。
照会モード
編集モード
routingをちゃんと実装すれば、onSelect()とかしなくていい
上記ではとりあえず簡単に動いている感じを示したかったのでonSelect()というメソッドでデータを受け渡ししていましたが、
URLのルーティングをきちんと実装すれば明示的にデータを受け渡しする必要はありません。
URLが書き変わるとそこから自動的にドリフメンバーのIDが決まるので、それを取得してデータ表示するだけでOKです。
ルーティングの実装は下記のチュートリアルを読んで頑張りましょう。
https://angular.jp/tutorial/toh-pt5
※チュートリアルのheroのところをdriftersMemberに読み替えれば、ほぼ似たようなことをやっています。
ルーティングを実装すると、下記のように、URLとデータが対応するようになります。
大爆笑無限連続再生機能を実装する
最後に、fundamentalsは全然関係ありませんが、せっかくAngularを使ったので大爆笑無限連続再生機能を実装しておきましょう。
下記のソースコードと音声ファイルを追加して、画面表示時に再生を開始するだけで、これ以上の実装はいりません。
URLが切り替わって右側の画面が変更されてもお、ページ全体が読み込み直されるわけではないのでずっと音声を再生し続けることができます。
//* ... *//
export class FdShellbarSideNavComponent implements OnInit {
//* ... *//
private SoundPlayer: HTMLAudioElement = new Audio('assets/daibakusho.mp3'); //追記
ngOnInit(): void {
// ↓追記
this.SoundPlayer.load();
this.SoundPlayer.loop = true; //音声ファイルの無限化
this.SoundPlayer.play();
// ↑追記
}
}
するとこんな感じになりました。
(!!!!音量注意!!!!)
Qiita記事「ドリフターズよ永遠に! ドリフのメンバー図鑑をFundamental Library for AngularでSAP Fioriっぽく作ってみた」のデモビデオを共有します。大人可愛い大爆笑わがままUIで業務改善を!#夏の自由研究2020 #chillSAP 8月21日分の投稿です!
— Mona Lisaco (@lisacomona) August 21, 2020
(!!!!音量注意!!!!) pic.twitter.com/x3bzev2dhT
これまでこのブログはいろいろな音を出してきたような気がしますが、
ただの大爆笑音声の無限再生は思った以上にドープなユーザーエクスペリエンスを生み出すことがわかりました。
(はからずもWebアプリの最先端UXを構築できたということになるのではないでしょうか。)
おわりに
というわけで。。。
どうやらこの雑貨店の業務課題は結果的に大人可愛く解決したようです。めでたしめでたし。