OmniAICreator/Qiita-1.07M huggingface dataset:
EC2にログインしてdockerbuildまでをTeraTermマクロを使って自動化してみた

# 背景
社内にて開発しているアプリケーションがあります。
テスト環境と本番環境の2環境準備しているのですが、テスト環境については環境に反映する頻度が多いです。

毎回の手順として、
①TeraTermにログイン
②Dockerfileがあるディレクトリに移動
③dockerbuildする
という作業をしています。手作業での時間は約1分なので自動化するほどでもないのですが、ログが残らないのともっと簡単にやりたいというモチベーションのもと、上記手順を自動化してみました。

# 作ったコード
・7~10行目の接続情報
・31行目以降のコマンド
この2点をカスタマイズしていただくとご自身の環境でも動くと思います。

```Dockerbuild.ttl
;============================================= 
; Description : SSHAutologin and docker build 
; Author      : msymacromill
; Created     : 2022/12/16 
;============================================== 
;; 接続先ホスト／ユーザ名／パスワード設定／ログ設定  
USERNAME = 'ec2-user' 
HOSTNAME = 'xx.xxx.xx.xx'
KEY_FILE = 'xxxxxx.pem'
LOGDIR = 'C:\xxxxxxxxxxxxxxxxx'
;============================================== 

;; ①ログイン情報を構成する
msg = HOSTNAME
strconcat msg ':22 /ssh2 /auth=publickey /user='
strconcat msg USERNAME
strconcat msg ' /keyfile='
strconcat msg KEY_FILE
 
;; ②接続する
connect msg

; ログファイルを作成する
getdate DATE '%Y%m%d-%H%M%S.log'
LOGNAME = '\docker_build_'
strconcat LOGDIR LOGNAME
strconcat LOGDIR DATE
logopen LOGDIR 0 0

;; ③コマンドを実行する
;Dockerfile_appがあるフォルダに移動してbuildする
wait "$"
sendln "cd <Dockerfileがあるディレクトリ>"
wait "$"
sendln "docker build -f Dockerfile -t <image名> ."
wait "$"
sendln "docker images"
wait "$"
logclose
```

# 手順
1. 上記コードを書く
テキストファイルで上記コードを記入後、拡張子「.ttl」ファイルで保存します。

1. ttlファイルをttpmacro.exeで開く
拡張子「.ttl」ファイルはそのままでは開けません。
「C:\Program Files (x86)\teraterm」内に「ttpmacro.exe」があるのでそれで開きましょう。
「右クリック > プログラムから開く > 別のプログラムを選択 > このPCで別のアプリを探す」から「ttpmacro.exe」を選択すると開くようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414468/ed3245de-ffeb-192c-fb68-00a6c2a43cf9.png)


# 最後に
TeraTermにはマクロ機能があるようです。初めて知りました。
これでワンクリックでDockerimageを更新できるのでとても楽です。
また
・ログを残すように設定したので、どのタイミングでDockerimageを更新したのかを追えるようになった
・Windowsタスクスケジューラに乗せれば日時でDockerimage更新をしてくれるようになるのでテスト環境の反映がよりスピーディーにできる
というメリットも副次的に得ることができました。

# 参考

AWSのEC2にTeraTermマクロで自動ログインとログ取得する方法

https://it-mikeiken.com/awsec2-teratermmacro-autologin

TeraTermマクロでコードを実行する方法

https://nwengblog.com/teraterm-command/
[競プロ][Python]動的計画法

# 動的計画法とは

動的計画法（Dynamic Programming）は、対象となる問題を複数の部分問題に分割し、部分問題の計算結果を記録しながら解いていく手法です。

動的計画法は特定のアルゴリズムではなく、考え方です。

動的計画法を適用するには、以下の条件が成立する必要があります。

* 最適な部分構造（optimal substructure）
* 重複する部分問題（overlapping subproblems）
* 後付けなし

## 最適な部分構造（optimal substructure）

最適な部分構造は、部分問題と元の問題との関係を規定するものです。
動的計画法は問題の最適解を見つけること、つまり問題に対する多数の解の中から最適なものを見つけ出すことです。
問題の最適解は、その部分問題の最適解によって決定されます。

最適な部分構造は、以下の2条件が成立していることを指します。
* 部分問題も同じ最適化問題が成立している。
* 部分問題間が独立している。

## 重複する部分問題（overlapping subproblems）

重複する部分問題は、部分問題間の関係を規定するものです。
同一の部分問題が繰り返し出現する場合、重複する部分問題の計算結果を記録し再利用する事により計算量を削減します。

## 後付けなし

ある状態が決定されると、それ以降の状態から影響を受けないことです。
つまり現在の状態の処理は、現在の状態にのみ影響します。

# 線形DP

動的計画法は以下の重要ポイントがあります。
* 状態定義
* 状態遷移
* 初期化
* 境界条件

状態定義は、部分問題をどのように表現するかということです。例えば、一般的にサイズがn個ある部分問題はdp[n]と定義します。この場合dp[n-1]はn-1番目の部分問題の解になります。
状態遷移は、部分問題間の関係を表現するものです。

線形DPの特徴は、状態は問題サイズiの小->大の順で導き、大きい問題の解は小さい問題の解に依存します。
ここで問題サイズiは、最初のi個の要素[0...i]の解を考える場合です。

```text
状態定義：　dp[n]:=dp[0...n]上の問題の解
状態遷移：　dp[i] = f(dp[i-1], ..., dp[0])
```

上記状態定義と状態遷移から、大規模問題の状態は小規模問題にのみ関係し、問題の規模は変数iで完全に表されることがわかります。iの大きさは問題の大きさを表すので、iを小さいものから大きいものへとnに達するまで計算すると元の問題の解が得られ、これが線形DPとなります。

線形DPは問題の入力形式によって以下に分類できます。
* Single (dp[i])
* Double (dp[i][j])
* Matrix (dp[i][j])

## Single

入力は1つのリストとなる問題です。
Single問題の状態定義は、dp[n]:=[0...n]上の問題です。

### iより小さいO(1)個の部分問題に依存する
dp[i]は定数個の部分問題にのみ関係する問題です。

* [[leetcode]53. Maximum Subarray](https://leetcode.com/problems/maximum-subarray/)
dp[i] = nums[i] + max(dp[i - 1], 0)
* [[leetcode]70. Climbing Stairs](https://leetcode.com/problems/climbing-stairs/)
dp[i] = dp[i-1] + dp[i-2]
* [[leetcode]746. Min Cost Climbing Stairs](https://leetcode.com/problems/min-cost-climbing-stairs/) 
dp[i] = min(dp[i−1]+cost[i−1], dp[i−2]+cost[i−2])
* [[leetcode]790. Domino and Tromino Tiling](https://leetcode.com/problems/domino-and-tromino-tiling/)
dp[i][0] = dp[i−1][3]
dp[i][1] = dp[i−1][0]+dp[i−1][2]
dp[i][2] = dp[i−1][0]+dp[i−1][1]
dp[i][3] = dp[i−1][0]+dp[i−1][1]+dp[i−1][2]+dp[i−1][3]

### iより小さいO(N)個の部分問題に依存する
dp[i]は計算した全ての部分問題と関係する問題です。

* [[leetcode]300. Longest Increasing Subsequence](https://leetcode.com/problems/longest-increasing-subsequence/)
dp[i] = max(dp[0...j])+1, 0≤j<i & num[j]<num[i]
* [[leetcode]139. Word Break](https://leetcode.com/problems/word-break/)
dp[i] = dp[j] && check(s[j...i−1])

部分問題は位置iにのみ関係する場合、dp[i]となります。
部分問題は別の指標k（長さ、数、次数、色など）と関係する場合、dp[i][k]となります。
kには二分探索、貪欲法などのアルゴリズムを適用する場合があります。

* [[leetcode]813. Largest Sum of Averages](https://leetcode.com/problems/largest-sum-of-averages/)

## Double

入力は2つのリストとなる問題です。
サイズはそれぞれm,nの場合、2つの変数i,jで位置を表現します。

```text
状態定義：　dp[i][j]:=入力1[0...i],入力2[0...j]の時の解
状態遷移の例：　dp[i][j]=f(dp[i-1][j], dp[i-1][j-1], dp[i][j-1]) 
```

* [[leetcode]1143. Longest Common Subsequence](https://leetcode.com/problems/longest-common-subsequence/)

## Matrix

入力は2次元配列となる問題です。

* [[leetcode]64. Minimum Path Sum](https://leetcode.com/problems/minimum-path-sum/)

# prefix sum

# 区間DP

# 参考情報

* [wikipedia - 動的計画法](https://ja.wikipedia.org/wiki/%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95)
Vuetifyで膨大のリストの表示方。Vuetify Virtual Scrollerよりいいもの！

## はじめに
膨大のリストを表示するときには、データを取得しながら段々ブラウザーが遅くなって、フリーズしてしまいましたの問題にあったことありましたか？
なかったらよかったなんですが、もしかしたら未来にはその問題に会うかもしれない。

リストを表示するときには不可欠の技術を紹介していきたいです！

## Virtual Scroller簡単説明

Virtual Scrollerは名前の通り、実際のものをScrollすることもなく、Virtual空間でScrollingを行うのものです。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/914725/ad7a4264-cee8-337d-b266-62704286053e.png) ![virtual-scrolling-initial-state-change.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/914725/94b6b386-c8fc-19dc-b80c-ebc6abf4097e.gif)  
image credit: 参考のリンクまで

アイデアとしては全部のアイテムをDOMの中に実際作ることが要らなく、ユーザが見てるアイテムのみを作ります。
たまにScrollingをするときに快適になるようにバッファーを上と下にいくつか作ります。このバッファーがなければScrollingが早すぎるの時に空白スペースが見られてしまいました。


より深く知りたければ参考欄まで！

## すごい！導入したい！

Virtual Scrollerの実装はかなり複雑ですよ。参考の記事を見ると様々の概念、注意点などがあります。
しかし、こちらの実装は流行になるのでOSSで配っていて、ドキュメントを読めば使えますよ。

Vuetify 2を使ったら、 [Vuetify Virtual Scroller](https://vuetifyjs.com/en/components/virtual-scroller)というComponentがあります。

## Vuetify Virtual Scrollerが足りない

こちらではシンプルのリストだけを表示したいの場合では十分なんですが、たまにリストの表示方があまりにも簡単ではないところがあります。例えば
1. アイテムの最後でAPIロード中のマークを表示したい!
2. 表示されるアイテムのDOMサイズが異なってる！
3. 私のリストは水平式なんですが

そうなったら、まさか自分で導入?
確かに、書いたある問題ではいくつか作法によってVuetify Virtual Scrollerで解決できますが、より簡単な実装はいかがでしょうか？


## Vue Virtual Scroller
Github: https://github.com/Akryum/vue-virtual-scroller
Documents: https://github.com/Akryum/vue-virtual-scroller/blob/v1/packages/vue-virtual-scroller/README.md
Live Demo😍: https://vue-virtual-scroller-demo.netlify.app/


Vuetifyよりいいの理由
1. Version 2ではVue3を対応してるので、今のVue３Projectで待たなく利用できます
2. before and after slotにより、リストアイテム以外のもの設置できます。利用の例は
   - Slotでは[IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)設置ができていて、ぶつかったら次のDataをロードするように
   - リストについてのAlert
3. DynamicScrollerで不統一のアイテムサイズでも対応できる！
4. 水平式にも対応する！ 

確かに様々の機能があるだからこそこのLibraryを利用するにはドキュメントをしっかり読まなければならないですね！
もしその４つの中から必要の項目がなければVuetifyの方を使った方がいいかも！

## 参考

https://blog.logrocket.com/virtual-scrolling-core-principles-and-basic-implementation-in-react/
見やすいダッシュボードのために意識した７つのこと

:::note warn
当記事のグラフはすべてダミー値です。実際の数値とは関係ありません。
:::

結論、以下を意識しました。

1. 目的を明確にする
1. あれもこれも詰め込み過ぎない
1. 視覚的に判断しやすいグラフにする
1. いきなり完成系を目指さない
1. 小さくつくって他人にみてもらう
1. デザインの一貫性を保つ
1. 他のダッシュボードを参考にする

それぞれどういうことをやったか紹介させていただければと思います。

## 1. 目的を明確に

ダッシュボードから何を読み取りたいのか決めるところから始めました。
分析、レポーティング、監視などさまざまな用途があると思いますが、これを意識したことで、以下のことが自ずと定まってきました。

 - 見るべき指標はなにか
 - ダッシュボードの構成イメージ
 - 意思決定しやすいグラフ表現

どんな用途で使うのかを意識してみると、この後のダッシュボード作成がブレにくくなるかなと思いました。

## 2. あれもこれも詰め込み過ぎない

例えばメール配信における指標は、送信数、開封数、クリック数、CV数、開封率、CTR、CVRが挙げられます。

これを１個のグラフに詰め込むとこうなります（数値はダミーです）。

![スクリーンショット 2022-12-14 19.16.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107402/aefdf685-13d5-3420-6006-6e61037d672d.png)

当然CV数とかまったく読み取れないグラフになりました。

送信＞開封＞クリック＞CVのファネルでそれぞれ見たかったので、以下３つに分割しました。

1. 送信数、開封数、開封率
1. 開封数、クリック数、CTR
1. クリック数、CV数、CVR

こんな感じに。

![スクリーンショット 2022-12-14 19.18.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107402/d70cfc90-f497-722e-5a6e-9d6c9bd4f7f3.png)

意味のある単位で分割すると、グラフから読み取りたいものが１つに絞られて判断しやすくなりました。

## 3. 視覚的に判断しやすいグラフに

A/Bテストの分割が意図した比率になっているかを確認するためのグラフが欲しかったのですが、これをパターンごとに実数で色分けするとこうなります（数値はダミーです）。

![スクリーンショット 2022-12-15 15.47.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107402/6569faa7-fa01-6ce4-de44-dd6132f8389b.png)

わからなくはないんですが、直感的に判断しにくかったので100%積み上げ棒グラフにしてみました。

こんな感じ。

![スクリーンショット 2022-12-15 15.42.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107402/64c57993-d81c-2f91-1948-e68e7c259821.png)

３パターンでおよそ均等に分割されていることが視覚的にわかるようになりホクホクです。

## 4. いきなり完成系を目指さない

「そもそも欲しかったもの、見たかったものと違うんだよね」となることを避さけるため、いきなり完成系を目指さないスタイルで進めました。

具体的には、全部の指標・グラフを揃えてから公開するのではなく、一部の指標・グラフだけに絞って作り始めました。

いきなり全部作ると、手戻りの可能性が高く修正の手間がかかる可能性が高いので、これを避けるように進めてみました。

## 5. 小さくつくって他人にみてもらう

「4. いきなり完成系を目指さない」に関連しますが、まずは小さくつくったものを人にみてもらいながら進めました。

例えば一部の指標・グラフだけつくって見てもらうのもいいと思います。実際に見せてみると、イメージのすり合わせや改善意見ももらえたりするのでおすすめです。

## 6. デザインの一貫性

僕はデザイナーではないのですが、一貫性のないデザインにならないように意識しました。
具体的には以下を統一しました。

- フォント、カラー
- 色使いを最低限にし、同じ指標は同じ色になるように
- 階層化により上から下に向けてより詳細な粒度に

たとえば、以下のイメージになります（数値はダミーです）。

![スクリーンショット 2022-12-15 16.34.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107402/019f8fd6-f385-79ea-7672-027813273ba1.png)

フォント、カラーを統一しつつ、送信数、開封数、開封率は同じ色にし、全チャネル＞チャネル別と階層分けし、上から下にかけて詳細になっていくようにしました。

## 7. 他のダッシュボードを参考にする

他のダッシュボードを参考にすると勉強になります。僕の場合、Looker Studioのテンプレートギャラリーをいくつか見て、ノウハウ的なものをいくつかパクりました。

https://lookerstudio.google.com/gallery?category=marketing

![スクリーンショット 2022-12-15 16.47.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107402/b4dd2454-fd8d-95ce-0414-886544bca8b5.png)


以上になります。

それでは、良いダッシュボードライフを!!
Db2 : SQLで正規表現(2)正規表現とLIKEとの比較

# 正規表現とLIKEの比較

Db2 LUW（Linux,Unix and Windows版）ではV11.1以降、正規表現をSQL内で使うことができるようになっており、Oracle互換機能の一環として、Oracleと互換性のある[正規表現スカラー関数](https://www.ibm.com/docs/ja/db2/11.5?topic=sql-built-in-functions#r0011043__t11043-regexs)が使用できます。

正規表現とLIKEを比較してみます。

||LIKE|正規表現サポート|
|:---|:---|:---|
|使用方法|「述部」<br>主にWHERE句で特定のパターンを持つストリングを探索するための演算<br>WHERE column1 LIKE '資料%';　<br>(備考1)|「関数」<br>SELECTの列リストやWHERE句での条件判断式で使用できる<br>WHERE REGEXP_LIKE ( column1, '^資料.' ) = TRUE;<br>(備考2)
|対応パターン|•「%」任意のゼロ文字以上の文字列<br>•「_」任意の一文字 |•一般的な正規表現パターンをサポート<br>•マッチ文字列パターンと置換文字列パターン
|対応機能|•探索のみ|•探索結果（マッチの有無）<br>•マッチした回数<br>•マッチした箇所<br>•マッチ箇所を置換パターンで置換した文字列<br>•マッチ箇所の文字部分列





備考1：　以下のように、LIKE述部はSELECTの列リストでの利用も可能です。



```
SELECT strvalue , strvalue LIKE '%c_' AS "LIKEマッチ結果" 
	FROM (
		VALUES
			 'ABCアabca',
			 'ABCいabcb',
			 'ABCxabcc',
			 'abcアABCa',
			 'abcいABCb',
			 'abcxABCc'
	) AS dummytable (strvalue);


STRVALUE  LIKEマッチ結果
--------- -----------------
ABCアabca      true
ABCいabcb      true
ABCxabcc       true
abcアABCa      false
abcいABCb      false
abcxABCc       false

```


備考2：　正規表現関数（REGEXP_LIKE）のSELECT 列リストでの使用例



```
SELECT strvalue
	, REGEXP_LIKE (strvalue, '.*c.$', 'c') AS "大文字小文字区別あり"
	, REGEXP_LIKE (strvalue, '.*c.$', 'i') AS "大文字小文字区別なし"
	FROM (
		VALUES
			 'ABCアabca',
			 'ABCいabcb',
			 'ABCxabcc',
			 'abcアABCa',
			 'abcいABCb',
			 'abcxABCc'
	) AS dummytable (strvalue);


STRVALUE  大文字小文字区別あり    大文字小文字区別なし
--------- ------------------  ------------------
ABCアabca      true                true
ABCいabcb      true                true
ABCxabcc       true                true
abcアABCa      false               true
abcいABCb      false               true
abcxABCc       false               true      

```


# 参考資料


- [Db2 11.5 Knowledge Center](https://www.ibm.com/support/knowledgecenter/ja/SSEPGG_11.5.0/com.ibm.db2.luw.welcome.doc/doc/welcome.html)

    - [正規表現の制御文字](https://www.ibm.com/docs/ja/db2/11.5?topic=sql-regular-expression-control-characters)
    - [正規表現スカラー関数](https://www.ibm.com/docs/ja/db2/11.5?topic=sql-built-in-functions#r0011043__t11043-regexs)
PySpark Tips：Python と PySpark の名前衝突を避ける etc.

# Functions のインポートにエイリアスを使う
```python
from pyspark.sql.functions import *
```
上記のような import 文をたまに見ますが、やめた方がいいです。
これは、Python の組み込み関数である、`sum`, `count` 等と衝突します。また、`col` などのよく利用する変数名とも衝突しやすいです。

エラー例：
```python
from pyspark.sql.functions import *
sum([1,2,3])
> Invalid argument, not a string or column: [1, 2, 3] of type . For column literals, use 'lit', 'array', 'struct' or 'create_map' function.
```
回避策：エイリアスを使ってインポートし、`F.sum()`  等で利用しましょう。
```python
from pyspark.sql import functions as F
```

# df.col_name による直接参照は避ける
```python
df = df.select(F.lower(df1.colA), F.upper(df2.colB))
```
上記の場合、スペース等のサポートされていない文字が含まれるカラムの参照時にエラーが発生します。また、`df` 変数名が長い時にタイプミスが発生しやすくなります。

回避策：`F.col` による間接参照を利用します。全体の表記を統一できるため視認性も向上します。
```python
df = df.select(F.lower(F.col('colA')), F.upper(F.col('colB')))
```
Workato：Boxファイル名への年月日自動付与

# １．はじめに
Boxのファイル名に対し年月日および時間を付与するレシピをWorkatoで作成してみました。
以下で紹介する内容では、Boxの指定のフォルダにファイルがアップロード後にアップロードされたファイルのファイル名に対し自動で現在の年月日および時間を付与します。

# ２．処理の流れ
Boxの指定のフォルダにファイルがアップロード後にアップロードされたファイルのファイル名に対し自動で現在の年月日および時間を付与します。
![Image20221216_008.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266312/e5048f00-cb72-d383-e736-7d30b16df291.gif)

# ３．レシピ
## 3－１．コネクター
次のコネクターを使用してレシピを作成します。
|コネクター|用途|
|---|---|
|Box |・特定のフォルダにアップロードされたファイルを取得・ファイル名の変換|

## 3－２．レシピ作成
次のようなレシピを作成します。
![Image20221216_001.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266312/34d916c8-4f88-6898-e8bd-4f4766ad8c5d.gif)


①トリガー
Boxの特定のフォルダにファイルがアップロードされ次第、レシピを開始します。
![Image20221216_002.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266312/ce38936c-d442-e26b-3c65-8690f7606f68.gif)

②年月日時間を含むファイル名を作成
Phytonにてアップロードされたファイル名に年月日時間を付与したファイル名を出力します。
![Image20221216_003.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266312/2c89a89b-2d21-c491-8c0b-2ee7b80dc721.gif)
![Image20221216_004.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266312/60e891f4-f781-c759-8c31-cb970f66118c.gif)

「Execute Python code」アクションに以下内容を設定しています。

### Input fields

「Add field」ボタンを押下して、Add new field画面に以下内容を設定
　・Label：FileName
　・Data type：String　
　・Hint：なし

### Output fields

「Add field」ボタンを押下して、Add new field画面に以下内容を設定
　・Label：FileName
　・Data type：String
　・Optional：No
　・Hint：なし　

### Code
```
import os
import datetime
import pytz
  
def main(input):
    file, ext = os.path.splitext(input["FileName"])
    dt_now = datetime.datetime.now(pytz.timezone('Asia/Tokyo')).strftime('_%Y%m%d_%H%M%S')
    filename = file + dt_now + ext
    return {"FileName" : filename}
```

Pythonコネクタを利用すると簡潔なコードで対応可能です。
年月日時間を付与したファイル名作成においてWorkatoのformulaでも対応可能となりますが添付のように複雑化します。Pythonコネクタを利用すればより簡潔なコードで対応可能なためPythonコネクタを用いています。
![Image20221216_009.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266312/3436802e-a682-13a2-11e6-47fe9d2d664d.gif)

③ファイル名変更
Phytonで年月日時間を付与されたファイル名をBoxにアップロードされたファイルに付与（ファイル名変換）します。

![Image20221216_005.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266312/30e3715c-6526-d78b-23c7-ab5296d343bf.gif)

# ４．実行例
1）特定のフォルダにファイルをアップロードします。
![Image20221216_006.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266312/73d1bd01-e2bc-215c-5c23-88f0cc41c62f.gif)

2）WEBページをリロードします。ファイルに年月日時間が付与されていることを確認します。
![Image20221216_007.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266312/5e2eb222-f9b7-1351-b72e-ec96df64481e.gif)
LINEミニアプリ開発でのつまずきポイント

最近LINEミニアプリの開発・審査提出を行いました。
そこでつまずいたこと、知ったことについて書いていこうと思います。

※2022/11時点での情報のため、最新情報では異なる場合があります。

## LINEミニアプリとは
>LINEミニアプリは、LIFF（LINE Front-end Framework）上で実行されるウェブアプリです。LINEミニアプリを使えば、ユーザーはアプリをインストールしなくてもサービスを利用できます。
LINEミニアプリはウェブアプリですので、HTML5のほとんどの仕様が使用できます。詳しくは、「仕様」を参照してください。
https://developers.line.biz/ja/docs/line-mini-app/discover/introduction/

アプリと聞くとハードルが少し高く感じますが、LIFF化さえできていればミニアプリ化は簡単にできました。

## 開発編
### 外部サイトの遷移
外部サイトのリンクを特に何の捻りもなくこのように書いていました。
```html:
<a href="https://example.com">外部サイトはこちら</a>
```
ミニアプリ上でこの書き方のリンクに遷移すると、遷移先の画面上に「外部サイトに移動したため、機能が正常に動作しないか、セキュリティが保証されていない場合があります」と表示されてしまいました。
ミニアプリで外部サイトを開くのはあまりよろしくなさそうです。

そのため、以下のようにliff.openWindowを利用してLINE内ブラウザで開くようにして、ミニアプリで外部サイトを開かないように制御を行いました。

```html:
<a onClick={() => {liff.openWindow({url: "https://example.com", external: false});}}>
  外部サイトはこちら
</a>
```

### 友だち登録
ミニアプリでは友だち登録を強制することができません。
そのため、友だち登録しないと利用できないような実装はNGのようです。

ただし、アクセス許可要求画面を利用することで、友だちになってもらうタイミングを用意することは可能でした。
アクセス許可同意画面では、ボットリンク機能による友だち追加を表示できるので、ここでチェックがついたまま同意してもらえれば友だちになってもらえるという動きです。

なるほどと思い設定していたのですが、ミニアプリを立ち上げてもアクセス許可同意画面が表示されず……
どうやら、チャネル同意の簡略化が"ON"になっていると初回起動時の権限許可画面がスキップされ、アクセス許可同意画面も表示されないようです。
以下のような処理を書いてあげることでチャネル同意の簡略化が"ON"でも、任意のタイミングでアクセス許可要求画面を表示させることができました。

```typescript
liff.permission.query("profile").then((permissionStatus) => {
  if (permissionStatus.state === "prompt") {
    liff.permission.requestAll();
  }
});
```

処理など詳しいことはこちらを参照
https://developers.line.biz/ja/docs/line-mini-app/develop/channel-consent-simplification/#detailed-workflow

## 審査提出編
### 審査提出前に
LINEミニアプリの審査依頼前の確認事項というページが用意されています。
審査提出前に今一度確認しておくと良さそうです。

https://developers.line.biz/ja/docs/line-mini-app/submit/submission-guide/#review-request-checklist

### 審査環境と本番環境の用意が必要

審査提出時には、審査用のエンドポイントURLさえ設定していれば良いと思っていたのですが、本番用のエンドポイントURLの設定も必要でした。
審査提出は公開申請の立ち位置らしく、審査提出時に本番システムも稼働させておく必要があるようです。
審査通過後、30日後には自動的に公開されるようなので、そのせいもあるのかもしれませんね。

### 審査環境と本番環境に乖離は出さない

基本的には審査環境は本番環境の複製くらいに考えておいた方が良さそうです。
今回外部サイトへのリンクがあったのですが、その向き先をdev環境にしていたところツッコミをいただきました(リンク先にBASIC認証がかかっていたせいの可能性もありますが……)

LINEミニアプリの審査依頼前の確認事項でも、似たようなことが書かれていました。
>本番用のチャネルのLIFF URLと、審査用のチャネルのLIFF URLが同じサービスを反映しているかどうかを確認してください
> https://developers.line.biz/ja/docs/line-mini-app/submit/submission-guide/#review-request-checklist


## さいごに

LINEミニアプリの開発・審査提出時につまずいたこと、知ったことについて書きました。
開発の手助けになれば幸いです。
【MySQL】復号化

MySQLで暗号化されたデータを復号化する方法になります
```sql
SELECT
  CONVERT(AES_DECRYPT(UNHEX(last_name), 'base64:**********=') USING utf8)
FROM
  users
WHERE
  user_id = 10
```

UNHEX でバイナリに変換。
AES_DECRYPT で復号化。
CONVERT　指定した文字セットに変換
【Unity】スマホの傾きをGravitySensorで取得する

## 本記事について

スマホを車のハンドルのように回転させて遊びたい。
そこで、以下のようにスマホを回転させたときにどっち向きにどのくらい回転したのかを取得する方法を示す。
![iphone.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/c090e734-b94d-61f0-578a-06f575d9fe9c.png)

## 環境
環境
mac os 12.5 silicon
Unity 2021.3.7f1
Input System 1.3.0
iphone

## GravitySensorについて

[GravitySensor](https://docs.unity3d.com/Packages/com.unity.inputsystem@1.3/manual/Sensors.html#gravitysensor)

> This is useful to control content by device orientation

ドキュメントより、スマホの傾きを取得したコンテンツを作れる

[Compensate Orientation](https://docs.unity3d.com/Packages/com.unity.inputsystem@1.3/manual/Settings.html#compensate-orientation)

> rotation values reported by sensors are rotated around the Z axis as follows

本記事で扱うアプリはホームボタンが右にくるように固定している。

![スクリーンショット 2022-12-16 15.36.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/ccd0b145-6ac1-b412-967d-4d1acbb5dd14.png)

そのためドキュメントより `Values rotate by 270 degrees.`となる。

アプリの向きによってGravitySensorで取れる値は変わるはずなのでそこは注意。

## GravitySensorの値の取得

まずはInput Systemをインストールする。　[ドキュメント](https://docs.unity3d.com/Packages/com.unity.inputsystem@1.3/manual/Installation.html)

設定方法等に特筆すべきことは特にないので本記事では書かない。(必要そうであれば後から追記)

```初期化用クラス.cs
using UnityEngine.InputSystem;
...

void Init()
{
    if (GravitySensor.current != null)
    {
        InputSystem.EnableDevice(GravitySensor.current);
    }
}

```


```HogeController.cs
using UnityEngine.InputSystem;

...

void Update()
{
    var gravitySensor = GravitySensor.current;
    if (gravitySensor != null)
    {
        var enabled = gravitySensor.enabled;
        Vector3 gravity = gravitySensor.gravity.ReadValue();
        sensorLabel.text = $"enabled: {enabled}\n" +
            $"Gravity\nX={gravity.x:#0.00} Y={gravity.y:#0.00} Z={gravity.z:#0.00}";
    }
}
```

iphoneをおおむね水平にしたときの値が以下のように取れる。

![IMG_5111.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/5a773616-9163-7345-a3a1-aa80bd323fa8.png)

で、実験した結果gravity.xが以下の数値になることがわかった。
下が地面とする

|状況|ホームボタンが下|ホームボタンが右|ホームボタンが上|
|:--:|:--:|:--:|:--:|
|gravity.x|1.00|0.00|-1.00|
||![tate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/0d78e567-93c1-9d86-b362-76a6277a6a96.png)|![yoko.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/a889be78-9eaa-37c2-c909-33746bbb9385.png)|![gyaku.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/df4f5a91-9af2-2f69-4428-fe94106c003e.png)|

## gravity.xの値の考察

![スクリーンショット_2022-12-16_16_43_45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/d13f5067-4583-8547-3fdb-932d35463896.png)
ホームボタンの位置におけるgravity.xの値の図。

おそらく青矢印で示した重力の向きと、ピンクで示したスマホの向きのベクトルの内積を取っている。
単位ベクトルの内積は同じ向きなら最大、逆向きなら最小の性質をもつ。
すなわちホームボタンが下(重力と同じ向き)なら1で、逆なら-1ということになる。

## 結論

前提: スマホ傾けるのは180度未満とする！

gravity.xが正 -> ホームボタンが下 -> 右に回転させている
gravity.xが負 -> ホームボタンが上 -> 左に回転させている

つまり、例えばスマホの傾きによってキャラクターを横移動させたい場合には

`右への移動スピード * gravity.x`

とすればスマホの傾きによって横移動が可能。

## 参考
[【Unity】Input System（新しい入力システム）の使い方](https://kurokumasoft.com/2022/01/05/unity-input-system/)
[Unityでモーションセンサーを使ってモバイルゲームを作る](https://morioh.com/p/81492000e0ad)
結局レビューは怖い

## はじめに
もうこの季節がやってきたんですね。1年は早いです。
この業界に来て3年、レビューをする機会も増えてきましたので、その所感を3つほど書いていこうと思います。

## 一番大事なのは要件が満たされていること
以前の私は、レビューをとても崇高なものだと思っていました。

私なんかが指摘していいのだろうか、、何を見ればいいかわからない、、
などと考えれば考えるほど自信がなくなってゆくのです。

ですが、私が担保しなければならない最も大事なことは、<strong>画面が要件通りにきちんと動くこと</strong>、だと気づきました。

・ここのリンクを押したら〇〇のページに遷移する
・ボタンをクリックしたらXXが表示される

当たり前に実装されていると思いがちですが、結構漏れていることがありました。
ソースコードを見て、ちゃんとBEMで実装できてるかな〜〜なんて見る前に、まずは画面を触って期待通りに動くかどうかを確認することの方が1000000倍重要です。（自戒）

特別な知識がなくても確認できること、なおかつ一番大事なことなので、何を見たらいいかわからない、という方はまずここを確認しましょう。

## 自信がなく、判断に迷うなら思い切って聞いてみる
レビューをしていると、判断に迷うこと多々あると思います。

自分はこう思うけど、他の人は違う意見かもしれない。
自分が間違って認識しているのかもしれない。

今まで、私はこう思った時に指摘せずに流してしまっていることが何度かありました。
結果として自分の勘違いだったり、指摘しなくて問題がなかった部分もありましたが、
結局2次レビュアーが指摘していて、やっぱりそうか・・と自己嫌悪に陥ることもありました。

そんなモヤモヤを抱えないためにも、気になった部分は聞いてみることの大事さを最近は感じています。
みんな優しいです。本当に敵じゃないです。
人間なのでみんな間違えることはあります。許しあう世界を自分から作ってゆくのです。

## チェックリストはやはり有能
私が携わっているサービスは、巨大かつ歴史あるサービスなので、結構ガチガチに実装のルールが決まっていたりします。
もちろん先人たちが情報をまとめてくださっています。ですが、膨大すぎるのです。そして、漏れます。

ここ２年は人の入れ替わりも激しく、レビューの指摘事項の増加・レビュイー不足というのが課題に挙がっていました。

そこで、私たちのチームでは以下の方法でチェックリスト運用を始めました。

1. チームメンバーのプルリクエストから、指摘事項を全て洗い出す。
2. 頻出する項目をまとめてチェックリストを作成。
3. レビュー時には、レビュイーはチェックリストに全てチェックがついたことを確認したのちにプルリクエストを提出、レビュアーはチェックリストの観点でレビューを実施。

明文化できておらず個人の裁量によっていた部分もチェックリストに落とし込むことができ、レビュイーの立場としては、大分指摘が減ってきていると実感していますし、レビュアーの観点としても、メンバーのスキルによらないレビュー体制が整えられるようになったと思います。

### 形骸化しないために
最近ではレビュー会という、プルリクエストでの指摘事項を全員で共有する会を設けています。
自分以外のプルリクエストはなかなか見ることもなかったで、この時間を設けたことで、知らなかった実装ルールを知るきっかけになったり、実は曖昧に進めてしまっていた部分をこの場でサクッと認識を擦り合わせする場としても活用できています。

（そもそもみんなこんな案件やっていたのね、と知るきっかけになったり）

ここで上がった内容をチェックリストに反映し日々アップデートしていくことで、形骸化しないチェックリスト運用が実現できているので、今後も続けていきたい取り組みです！


## 最後に
2023年はこういう仕組みをもっと自動化できたらなあ。
それでは良いお年を！
vite でアセットを相対パスにする

vite で開発/本番環境でアセットを相対パスにしようとしたら、
思ったよりてこずってしまったのでその忘備録です。

## 一番シンプルなパターン

アセットを相対パスで記述する最も簡単なパターンは、`vite.config.js` に下記の設定を追加することです。
```vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  base: "./"
})
```
```style.scss
.box {
    background-image: url('/bg.png');
}
```
↓ ビルド後
```style.css
.box {
    background-image: url('../bg.png');
}
```

ただ、この設定でも相対パスにならないケースがありました。


## 相対パスにならないケース

エントリーポイントを持たず、ビルドするファイルを `rollupOptions` で指定しているケース。
下記のように設定を行っている場合です。

```vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
  build: {
    outDir: 'dist/assets',
    rollupOptions: {
      input: '/src/script/main.js',
      output: {
        entryFileNames: 'script/main.js',
        assetFileNames: `style/style.css`,
      }
    }
  }
})
```
このようなケースでは、ファイルのみが生成され、
読み込むアセットは HTML 側で指定することになります。
そのため、読み込むパスを相対で記述すればおおよそ問題ないのですが、
CSS の background-image の url() などが絶対パスのまま残ってしまいます。

```index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>title</title>
    <link rel="stylesheet" href="./assets/css/style.css">
  </head>
  <body>
    <div class="box"></div>
    <script src="./assets/script/main.ts"></script>
  </body>
</html>
```
```style.css
.box {
    background-image: url('/assets/images/bg.png'); /* 絶対パスのまま */
}
```


### CSS の url() を相対パスで記述したときの問題

CSS 内の url() を相対パスで記述すれば、パス自体は相対パスのまま出力されます。
しかし、上記の出力設定のままだと CSS の出力が開発中とビルドで異なるという問題が発生します。

具体的には、
- 開発中の CSS は `<style>` タグに変換され `<head>` 内に挿入される
- ビルド時の CSS は指定したディレクトリにファイルとして書き出される

そのため、url() を相対パスで記述すると開発中とビルド時で**参照がずれる**ことがあります。

### vite（rollup）ビルドのオプションで url() のパスを書き換える

この対策として、vite（rollup）ビルドのオプションで書き換えを行いました。
使うのは、`additionalData` オプションです。

```vite.dev.config.js
import { defineConfig } from 'vite'
export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `$base-dir: '/assets/';`,
      },
    },
  }, 
})
```
```vite.build.config.js
import { defineConfig } from 'vite'
export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `$base-dir: '../';`,
      },
    },
  }, 
})
```

開発用、ビルド用で vite.config.js を２種類用意しました。
これで、scss に `$base-dir` という変数を渡すことができるので、
scss 側で変数を使って url() を指定します。

```style.scss
.box {
  $src: $base-dir + 'images/bg.png';
  background-image: url($src);
}
```

力技になってしまったのですが、この手法で vite のアセットを相対パスにすることができました。
他にもっとスマートな方法がある気もするのですが。。
良い方法をご存知でしたら、コメントなどからご教示いただけますと幸いです:bow:
Dart Streamを使って素数を表示してみる

`Stream`内で計算した素数を`await for`で出力するコードをいくつか書いてみました。素数は`for(;;){}`で無限ループさせてその中で判定させ、素数だけを出力させるようにしたいと思います。

まずは、シンプルに`bool判定`でループの回数を半分の位置で判定して出してみます。`１`は必ず割れるので判定基準には入れません。

```dart
import 'dart:async';

// async関数内で await forかlisten()を使ってStreamの値を取り出す。async*内でyield(return)を使いStreamに値を返す。

Stream<int> primeNumberStream() async* {
  for(int i = 2;;i++){ 
    bool isPrime = true;
			for(int j = 2; j <= i ~/ 2; j++){
       if(i % j == 0){
         isPrime = false;
         break;
       }
      }
        if(isPrime){
          await Future.delayed(Duration(seconds: 1));
        yield i;
    }
  }
}

void main() async {   
  await for(int p in primeNumberStream()){
    print(p);
  }
}
```
次は高速化させて３から割るパターンです。`2`は予めプリントしておき、`count`を設けてインクリメントで判定させます。

```dart
import 'dart:async';

Stream<int> primeNumberStream() async* {
  print("2");
  for (int i = 3; ; i+=2) {
    int count = 0;
    for (int j = 1; j < i; j++) {
      if (i % j == 0) {
        count++;
      }
    }
    if (count == 1) {
      await Future.delayed(Duration(seconds: 1));
      yield i;
    }   
  }
}

main() async {
  await for(int p in primeNumberStream()){
    print(p);
  }
}
```
今度は`count`を取り除きfor文の中の条件式で判定させたパターンです。

```dart
import 'dart:async';

Stream<int> primeNumberStream() async* {  
  print("2");
  for(int i = 2; ;i++){ 
			for(int j = 3; (i % j != 0 && 
 j < i) || j == i; j += 2){
        if(j == i){
          await Future.delayed(Duration(seconds: 1));
        yield i;
        }
    }
  }
}

void main() async {   
  await for(int p in primeNumberStream()){
    print(p);
  }
}
```
VSCodeのスニペットで意図しない空白が入る件について

この記事は[NTTコムウェア Advent Calendar 2022](https://qiita.com/advent-calendar/2022/nttcomware) 22日目の記事です。

# はじめに
これは私がVSCodeのスニペット機能を使っていておかしな挙動に遭遇した内容とその対処法について記載しています。
同じような挙動に遭遇した方がこの記事を見て問題解決してもらえると幸いです。

# 動作環境
VSCodeのバージョンは1.74.2です。

# 結論
## As Is
=の右の空白が消える

![VSCode_snippet_bug.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335115/2ba8a80d-7f1f-ef76-0c8c-c7ba2bd05d3f.gif)

## To Be
=の右の空白が消えない

![VSCode_snippet_success.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335115/0498a7a0-bdcc-a1c8-93e9-3fe392b114b8.gif)

# 原因
prefixの値に半角スペースが入っていること
```json:python.json
{

	"input int":{
        "prefix":"input int",
		"body":"int(input())",
		"description":"input int",
	},
}
```

# 解決方法
prefixの値に半角スペースを入れない
```json:python.json
{

	"input int":{
        "prefix":"input_int",
		"body":"int(input())",
		"description":"input int",
	},
}
```

# おわりに
VSCodeのバグなのかなー。よくわかりませんが、これで解決です！
ALE で linter に rustc を指定していると良くない感じがしたので回避した

https://github.com/dense-analysis/ale/blob/0f55d371e92160812ff0a57b5f48f2010a420f0c/ale_linters/rust/rustc.vim#L4

https://github.com/rust-lang/rust/blob/872631d0f0fadffe3220ab1bd9c8f1f2342341e2/compiler/rustc_interface/src/util.rs#L542-L549

`/dev/null` を指定すると `/dev` にライトパーミッションを求めそうなので，適当に回避した

```shell-session
$ ln -s /dev/null ~/null
```

```vimscript
  let g:ale_rust_rustc_options = '--emit=mir -o ' . $HOME . '/null'
```

要るのかは，わからない……

(2022/12/23 追記) 結局は，`rustc` は抜きました．
`use` がうまくいかない？

## Refs

https://qiita.com/atm-snag/items/bac21759a85370dfdcb5
ロゴ製作 デザイン ヒアリング項目

* 会社やサービスの基本情報
* 事業内容
* どんな思い、きっかけでロゴを制作orリニューアルしようと思ったか
* 今後のビジョン
* 強みや特徴
* ロゴを見た人に、どんな印象を持ってもらいたいか
* ロゴデザインの要望
* どんな場面、用途で使用されることが想定されるか
実現方法がパッと思いつかなかった CSS の実装

## はじめに
本記事は Qiita Advent Calendar 2022 の 17 日目です！

自分が UI の実装を進めていく中で、実現方法に辿り着くのに少し時間がかかった CSS の実装を紹介します！

## ある要素の上方向に、高さが可変の小窓を余白を空けたうえで表示する

例えばボタンを押した際に、そのボタンの上方向に吹き出しのような小窓を表示させたいとします。
その小窓の高さが固定で余白も必要ない場合は、以下のような形で実装ができます。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="GRGbZeE" data-user="taka-k" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/taka-k/pen/GRGbZeE">
  Untitled</a> by taka-k (<a href="https://codepen.io/taka-k">@taka-k</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

- ボタンを div タグ等で囲み、そのタグに `position: relative;` を指定する
- その div タグ内に小窓のタグを入れ、小窓のタグに以下の CSS を指定する
    - `bottom: 100%;`
    - `position: absolute;`
    - `z-index: 1;`
        - 小窓を前面に出したい場合

要は、`bottom`に小窓の高さと同じ値を指定してあげれば良いわけです。

ただ、小窓内の要素の高さが決まっておらず、一定の距離を離して表示したい場合は上記の手段は使えません。
試行錯誤した結果、以下のように指定するとうまくいきました。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="BaVeJZN" data-user="taka-k" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/taka-k/pen/BaVeJZN">
  Untitled</a> by taka-k (<a href="https://codepen.io/taka-k">@taka-k</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

- 基本的には高さが固定の場合の指定方法のままでOK
- `bottom`のみ、`calc(100% + {余白の幅})`のように指定する

`calc` で高さを計算し、それを `bottom` に指定することで高さ分離して表示してくれるわけですね。
そして余白を空けたい場合は、`calc(100% + 8px)`のように余白分の幅をプラスしたうえで計算するとうまくいきます。
わかってしまえば単純ですが、閃くまでに結構時間がかかりました... 

## 文字列の左右に border を表示する

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="JjZQKxy" data-user="taka-k" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/taka-k/pen/JjZQKxy">
  文字列の左右に border を表示する</a> by taka-k (<a href="https://codepen.io/taka-k">@taka-k</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

- 文字列タグに以下の CSS を指定する
    - `align-items: center;`
    - `display: flex;`
- 文字列タグに`::before`、`::after`擬似要素で以下の CSS を指定する
    - `background-color: rgba(0, 0, 0, 0.12);`
        - 色はお好みで
    - `content: "";`
    - `flex-grow: 1;`
    - `height: 1px;`
        - 線の高さなのでお好みで

文字列と border の間に余白を空けたい場合は、それぞれの擬似要素に `margin-left`及び`margin-right` を指定してあげるとうまくいきます。
HIMITSU NO ANGOUをWebページ上で生成してみる

# はじめに
こんにちはあるいはこんばんは、エンジニアの皆さん。
メタップスアドベントカレンダー17日目の記事です。

何の記事を書くかずっと悩んでいたところ、ふと某アニメのワンシーンが頭を過りましたので
本日はタイトルの通り、秘密の暗号をWebページで生成してみようと思います。

# 画面
見た目はこちら
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595379/fa911f7b-93f9-e13d-3a5d-a209c0ae8c7a.png)

# 成果物
実際のコードや動作させてみたい方はこちらから閲覧できます。
https://codesandbox.io/s/himitsu-no-angou-f8jwz0

# 秘密の暗号の仕組み

```typescript
const shuffled = computed(() => shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'.split('')))
```
事前に、取りうる値をシャッフルした状態で配列として持っておきます。


```typescript
const v$ = useVuelidate(
    {
      plainText: {
        required,
        inputRegex,
        maxLength: maxLength(255),
      },
    },
    {plainText},
)
```
入力を受け付ける時、取りうる値以外のものを入力されても困りますので
ここではvuelidateを用いてバリデーションを定義しておきます。
半角英数字、（）と改行を受け付けます。

```typescript
const onClickGenerate = () => {
  cryptoText.value = plainText.value
      .split('')
      .map((e) => {
        if (e === '\n') {
          return '<br>'
        }
        if ([' ', '(', ')'].includes(e)) {
          return e
        }
        const index = shuffled.value.indexOf(e.toUpperCase())
        const x = (index % 6) + 1
        const y = Math.floor(index / 6) + 1
        return `${x}${y}`
      })
      .join(' ')
}
```
入力された文章を1文字ずつ取り出し、配列上のインデックスを検索します。
返ってきた位置からx座標とy座標を割り出します。
1文字ずつ割り出した座標を連結し、それ暗号文としています。

出てきた暗号文を生成した表と突き合わせて解読する。という感じです。

## 最後に
某アニメのワンシーンの暗号表を真似してつくってみただけなのですが、
この表ちゃんと元があって、「[ポリュビオスの暗号表](https://ja.wikipedia.org/wiki/%E3%83%9D%E3%83%AA%E3%83%A5%E3%83%93%E3%82%AA%E3%82%B9%E3%81%AE%E6%9A%97%E5%8F%B7%E8%A1%A8)」というらしいですね。知りませんでした。
もうちょっと拡張して、漢字やひらがなに対応したり、全部の文字を網羅するではなく入力された文章をカバーしながら
ランダムに他の漢字をピックアップした表を作るとかでも面白いかもしれません。

よかったら、皆さんも好きなキーワードを入れて友人に遊んでもらうなど楽しんでください。
それではごきげんよう。
TortoiseGitで右クリックメニューをカスタマイズ

# 概要

このずらーっと並んだメニューから選ぶのだるい…。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/8f7511dd-826f-c4f6-b561-871a197f2896.png)

よく使うやつだけリストアップしたり、逆に使わないやつは非表示にしたい。

# やり方

Settings 。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/2060b8ee-1687-a9fa-2a86-598eb2d28772.png)

## Context Menu

これは **『追加する』** 設定。

例えば ↓ こう設定すれば…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/1e7f638e-243b-3421-bd02-3c8f8df97cc0.png)
 ↓ こうなる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/bb0f85bb-78ce-9ecf-fbee-460e57e27bcb.png)

## Context Menu 2

これは **『除外する』** 設定。

元々こうなっていて…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/11720f4f-ffa8-b9eb-a6f7-d9fa64324bb2.png)
 ↓ こうすると…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/4b2c4ee9-b1be-e141-d34f-e4ced6281a5a.png)
 ↓ こうなる。（チェック入れたDiffがいなくなる）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/86b652da-4e80-069b-a1af-5b2182c51b77.png)

# バージョン

Windows 10 Pro 21H2 OSビルド 19045.2311
git version 2.39.0.windows.1
TortoiseGit 2.13.0.1
【Android】ダークモードのレイアウト色崩れを防ぐ

テーマで`android:forceDarkAllowed`を`false`に設定すればよい。

```themes.xml
<style name="Theme.TestApplication" parent="Theme.MaterialComponents.DayNight.NoActionBar">
...
	<item name="android:forceDarkAllowed">false</item>
</style>

```
ブラウザ上で3Dを表現できる Three.js を使ってみた

ブラウザ上で3D表現が可能な、[Three.js](https://threejs.org/) を使ってみました！
使いこなすと、かなりリッチなページが作れそうな所感です！

## インストール
使う手段は二通り
 1. CDNでもOK。 　
 2. npm install three

```javascript:main.js
<script src="https://unpkg.com/three@0.142.0/build/three.min.js"></script>
```

## 基本  
Three.js を使用する上で、必ず以下の3つが必要となります。

 1. シーン
 2. カメラ
 3. レンダラー

### シーン
ステージにあたるもの。
この上に3Dオブジェクトやライト、カメラを配置していきます。

### カメラ
カメラを追加しないと、オブジェクトをいくら追加しても、画面上には何も表示されません。

### レンダラー
全てを司るレンダラーを呼び出します。

## 3Dオブジェクト
そして、オブジェクトを追加していきます。

基本的には[**ジオメトリ**](https://threejs.org/docs/index.html#api/en/geometries/BoxGeometry)という立体モデルに
表面の素材[**マテリアル**](https://threejs.org/docs/index.html#api/en/materials/MeshPhysicalMaterial)を設定していく流れになります。
作ったオブジェクトに x, y, z 座標を指定して、位置を決めていきます。

多くのマテリアルは光源を必要とするのですが、ライトが無くても見える素材もあります。

### ライトの種類も豊富

**DirectionalLight**
一般的な太陽光みたいに平行に降り注ぐ光
![スクリーンショット 2022-12-16 18.00.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418521/c7ebebb8-d241-cadc-ac56-ea9065f62a29.png)


**RectAreaLight**
四角い範囲で照らす
![スクリーンショット 2022-12-16 18.00.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418521/2ee4d70f-0256-b923-6c6d-f828007cd7da.png)


他にも沢山あります。
アイデア次第で様々な表現ができそうです。

## 実践
今回は平面のジオメトリに、画像や動画を貼り付けてみました。
そうすると、こんな感じのギャラリーに。

![video01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418521/71a6eb0c-6a2d-4645-73a5-9c3308b15971.gif)
![video02.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418521/f906f8b4-7f0f-4c63-5098-755b29e72354.gif)

まだあまり時間を使えていませんが、突き詰めればモダンに仕上がりになりそうです。

###
ちなみに、上の実践ではカメラを回転させてはいませんが、
カメラを円周上に移動させて回ってるような映像にする場合は、次のように書いてカメラを動かします。

```javascript
camera.position.x = Math.cos(r / 180 * Math.PI) * 15
camera.position.z = Math.sin(r / 180 * Math.PI) * 15
```

x座標は cosθ * 半径、
z座標は sinθ * 半径、で求めることができます。
r を時間経過で値が増えていくようにすると、0〜360°の範囲で遷移します。
高さである y軸は変えていないので、平面の円状にカメラが動いていきます。

このように、何かを回転させる（円周上を移動させる）場合は、三角関数が使えます。
数学で習ったsin, cos が役に立つ日が来るとは...!


## おまけ
React 用で、かなり簡単に Three.js を利用できるライブラリを見つけました。

こちらの [Three Fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction)

少し使ってみた感想としては、簡素なコードでオブジェクトを追加できるので、手軽に3D要素を追加したい場合に良さそうです。


Vue/Nuxt 用は残念ながらなさそう。。。
ターミナルでスターウォーズを見るべ

この記事は[フラー株式会社](https://www.fuller-inc.com/) Advent Calendar 2022の 15 日目の記事です。
15 日目 の記事は　@is_hoku さんで「[Goa と sqlc, Atlas で快適な Web API サーバ開発](https://hokuishi.be/blog/develop_apiserver_with_goa)」でした。

![スクリーンショット 2022-12-16 17.45.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982464/14c1d79c-9922-2812-cd8d-3a9f635f07cf.png)

# はじめてのアドカレや〜ん
[フラー株式会社](https://www.fuller-inc.com/)さんでインターン生としてサーバーサイドエンジニアをやっています．
インターン始まってもう2ヶ月ですか．．．　
時が流れるのは早いもので、年内の勤務も残り1週間となりました．

今日は、はじめてアドベントカレンダーなるものを書きます．

さー、ネタに困りました！
でも、ネタはあるっちゃあるんです．

最近自分が知って、驚いた＆面白かった話題を取り上げようと思います．
既に知ってる人もいるかもしれませんが、まあ、私に書くチャンスをください（笑）

# ターミナルでスターウォーズ

さっそく本題なのですが、Quoraでネットサーフィンしていて、たまたま見つけたのですが、ターミナルで以下のコマンドを叩くとスターウォーズが見れるんです．

しかも、結構ちゃんと作り込まれていて、15分以上ある超大作です（笑）
```bash
$ nc towel.blinkenlights.nl 23
```

全部英語なんですけど、スターウォーズ好きな方は休憩の合間にでも見てみると面白いかもしれまへんで〜！
![スクリーンショット 2022-12-16 17.45.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982464/14c1d79c-9922-2812-cd8d-3a9f635f07cf.png)


17 日目は @furusax  さんで「Sign In with Apple REST APIをGoで扱うためのライブラリを作っている話」です！


### 参考文献とか
https://qr.ae/prItrF
https://orebibou.com/ja/home/201504/20150412_001/
Databricksノートブックのアウトプットと処理結果

[Notebook outputs and results \| Databricks on AWS](https://docs.databricks.com/notebooks/notebook-outputs.html) [2022/11/30時点]の翻訳です。

:::note warn
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

[ノートブックをクラスターにアタッチ](https://qiita.com/taka_yayoi/items/141e49b155f4cad1bb34#%E3%83%8E%E3%83%BC%E3%83%88%E3%83%96%E3%83%83%E3%82%AF%E3%82%92%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E3%83%BC%E3%81%AB%E3%82%A2%E3%82%BF%E3%83%83%E3%83%81)し、[1つ以上のセルを実行](https://qiita.com/taka_yayoi/items/68a31fd49203d2c9e96f)した後は、ノートブックは状態を持ち、アウトプットを表示します。ここでは、どのようにノートブックの状態とアウトプットを管理するのかを説明します。

# ノートブックの状態とアウトプットのクリア

ノートブックの状態とアウトプットのクリアするには、**Run**メニューの下部にある**Clear**オプションの一つを選択します。

| メニューオプション | 説明 |
|:--|:--|
|Clear all cell outputs| セルのアウトプットをクリアします。ノートブックを共有する際に結果を含めたくない際に役立ちます。  |
|Clear state   | 関数、変数定義、データ、インポートされたライブラリを含むノートブックの状態をクリアします。  |
| Clear state and outputs  | セルのアウトプットとノートブックの状態の両方をクリアします。  |
| Clear state and run all  | ノートブックの状態をクリアし、新規に処理を実行します。  |

# 結果の表示

セルが実行されると、Databricksは1000行のデータフレームを返却します。Databricksランタイム8.4以降では、1000行以上のデータが存在する場合には、最大10,000行まで表示するようにクエリーを再実行することができます。
![](https://docs.databricks.com/_images/results-limit-message.png)

## ネイティブにPythonを用いてPythonノートブックでSQLセルの結果を探索

SQLを用いてデータをロードし、Pythonを用いてデータを探索することができます。DatabricksのPythonノートブックで、SQL言語セルのテーブルの結果は自動でPythonデータフレームとして利用できるようになっています。詳細は、[PythonノートブックにおけるPythonをネイティブに用いたSQLセル結果の探索](https://qiita.com/taka_yayoi/items/dfb53f63aed2fbd344fc#python%E3%83%8E%E3%83%BC%E3%83%88%E3%83%96%E3%83%83%E3%82%AF%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8Bpython%E3%82%92%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%81%AB%E7%94%A8%E3%81%84%E3%81%9Fsql%E3%82%BB%E3%83%AB%E7%B5%90%E6%9E%9C%E3%81%AE%E6%8E%A2%E7%B4%A2)をご覧ください。

# 結果のダウンロード

デフォルトでは結果のダウンロードは有効化されています。この設定を切り替えるには、[Manage the ability to download results from notebooks](https://docs.databricks.com/administration-guide/workspace/notebooks.html#manage-download-results)をご覧ください。

ローカルマシンにテーブル形式のアウトプットを含むセルの処理結果をダウンロードすることができます。タブのタイトルの隣にある3点ボタンメニューをクリックします。結果の行数とDatabricksランタイムのバージョンに応じてメニューオプションが変化します。ダウンロードされた結果は`export.csv`というCSVファイルとしてローカルマシンに保存されます。
![](https://docs.databricks.com/_images/download-result.png)

# セルにおける複数のアウトプットの参照

Pythonノートブックと非Pythonノートブックの`%python`セルは、セルにおける複数アウトプットをサポートしています。例えば、以下のコードのアウトプットにはプロットとテーブルの両方が含まれます。

```py:Python
import pandas as pd
from sklearn.datasets import load_iris

data = load_iris()
iris = pd.DataFrame(data=data.data, columns=data.feature_names)
ax = iris.plot()
print("plot")
display(ax)
print("data")
display(iris)
```

Databricksランタイム7.3LTSでは、`spark.databricks.workspace.multipleResults.enabled true`を設定して、この機能を有効化しなくてはなりません。

### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)
t3-stack入門 (4.5) NextAuth.jsでJWTをセッションに使うとsession.idが取得できない

TodoにownerIdを設定しようとしましたが、なぜか、ログインしてもsession.idが設定されていません。
session.emailとsession.nameは設定されているのでsession自体はできているようです。
そういえば、CredentialProviderを使うように変更したとき、
session関数のuserパラメータが必ずundefineになるので意味ないと思って消したのでした。
ここら辺消してはまずかったのか？

```diff_typescript
 export const authOptions: NextAuthOptions = {
-  // Include user.id on session
-  callbacks: {
-    session({ session, user }) {
-      if (session.user) {
-        session.user.id = user.id;
-      }
-      return session;
-    },
-  },
-  // Configure one or more authentication providers
-  adapter: PrismaAdapter(prisma),
   providers: [
```

https://next-auth.js.org/configuration/callbacks#session-callback

ここを読むと、
* データベースセッションを利用している場合、userオブジェクトが引数として渡されます。
* JSON Web Tokensをセッションとして利用している場合、JWT payloadが代わりに渡されます。

とあるので、JWTを利用するときはuserではなくて、tokenプロパティを利用しなければならないみたいです。
```typescript
callbacks: {
  async session({ session, token, user }) {
    // Send properties to the client, like an access_token and user id from a provider.
    session.accessToken = token.accessToken
    session.user.id = token.id
    
    return session
  }
}
```
試しに、公式サイトにある通り、token.idをsession.user.idに代入する処理を行ってみましたが、
確かにtokenはわたってきますが、token.idはやっぱりundefineでした。
さっぱりわからない。tokenにはどんな内容が入ってくるんだろうと思ってデバックを有効にして、Next.jsを立ち上げてみたら
以下のようでした。

![token.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508186/ad4deeb5-1f91-3e1a-32f9-7843731807c1.png)

どうやら、idではなくsubプロパティにidが入っているようです。

```diff_typescript
 import { prisma } from "../../../server/db/client";
 
 export const authOptions: NextAuthOptions = {
+  secret: env.NEXTAUTH_SECRET,
+  // Include user.id on session
+  callbacks: {
+    session({ session, token }) {
+      if (session.user != null && token.sub != null) {
+        session.user.id = token.sub;
+      }
+      return session;
+    },
+  },
   providers: [
```

以上のようにしたところ無事、seesion.idにidが設定されるようになりました。
やはり、公式サイトはよく読まなければなりません。

ここまでの変更は以下の通りです。

https://github.com/bioflowy/t3-todo-example/commit/97f05149f303e813fb93c392f22030b258faea7c
leetcode141 を解いてみた

# 方針
自分で1回はプロセスを考える。解けなくてもOK。
解けなかったときは、振り返ることが可能な程度まで言語化する。
※考え方おかしくない？というのがあればご指摘くださいまし。
※解けなかった、苦戦したものを記事にする。

# 問題紹介

問題は以下URL参照

https://leetcode.com/problems/linked-list-cycle/description/
## 最初の考え
- こんなのリスト内をループさせる。配列に保存。次回ループ時に値が存在するかを判定すればいいんじゃね。

``` 初回.py
class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        mem = []
        while head != None:

            count = mem.count(head.val)
            mem.append(head.val)
            head = head.next
            if count > 0:
                return True

        return False
```

## 思考中
- 重複してるときのテストケースで落ちた...
    - link先の値が複数あるときは弾かないといけないのか...(´・ω・｀)
- input中の重複数値を洗い出すとかやれば大丈夫なんだけど、制限されてて確認できない(詰んだ)

## 結果
Solutionsを確認しました(´・ω・｀)

- 2つの速度でポインタを循環させて、一致した場合には循環しているのでTrue(ポインタの遭遇率が最近高い)
    - この手の問題はポインタを利用すればいいということを学んだよ。類似の使い方をする問題も多そうな気がする。
- 前提として、循環していない場合のデータ構造はhead.next＝Noneと考えた方が良さそう。
    - 値だけで判別すると繋ぎ先があるように思えるが、データ構造としてはそれは誤り。⇦ここの誤解が今回解けなかった原因。

そもそものアプローチがズレてたのが悔しいですね...(´・ω・｀)
ESP32にてGPSモジュールと接続するときにハマったこと(UARTの使い方)

# はじめに
ESP32にてUARTを利用する場合Arduinoとは若干使い方が異なります。GPSモジュールやそのほかモジュールと接続する場合に毎回調べてしまうので、備忘録的に記録しておきます。

# 使用モジュール
・ESP32 DevkitC
・GPS（ublox NEO-6M）TinyGPS++は予めインストールしておいてください。
![](https://i.imgur.com/mm0n7KC.png)


# ESP32での基本的なUART使い方


| Serial | RX | TX |
| -------- | -------- | -------- |
| Serial     | 3     | 1     |
| Serial1     | **※要宣言**     | **※要宣言**     |
| Serial2     | 16     | 17     |


## ESP32でUART1使用時の注意点
UART1についてはそのままでは使用することができません。

```
  Serial1.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);
```
このように宣言することでRX,TXをそれぞれ割り当てることができます。



## UART1(Serial1)を使用するコード

:::note info
ESP32で```Serial```をそのまま使用する場合は
```HardwareSerial```や```SoftwareSerial```の宣言は不要です。
:::

```cpp
#include <TinyGPS++.h>

TinyGPSPlus gps;

float gps_lat; //緯度
float gps_longt; //経度 

int RX_PIN = 23;
int TX_PIN = 22;

void setup()
{
  // シリアルポート開始
  Serial.begin(115200);
  Serial1.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);

}

void loop() {
  // Serial.println("test"); 
  while (Serial1.available() > 0) {
    char c = Serial1.read();
    gps.encode(c);
    if (gps.location.isUpdated()) {

      gps_lat = gps.location.lat();
      gps_longt = gps.location.lng();
      Serial.print("LAT:  "); Serial.println(gps_lat,9);
      Serial.print("LONG: "); Serial.println(gps_longt,9);
      delay(5000);
    }
  }
  delay(1000);
}

```


## UART2(Serial2)を使用するコード
こちらはTX,RXを指定ピンに接続することで宣言は不要です。

```cpp
#include <TinyGPS++.h>

TinyGPSPlus gps;

float gps_lat; //緯度
float gps_longt; //経度 

void setup()
{
  // シリアルポート開始
  Serial.begin(115200);
  Serial2.begin(9600); // NEO-7M デフォルト
}

void loop() {
  while (Serial2.available() > 0) {
    char c = Serial2.read();
    gps.encode(c);
    if (gps.location.isUpdated()) {
      gps_lat = gps.location.lat();
      gps_longt = gps.location.lng();
      Serial.print("LAT:  "); Serial.println(gps_lat,9);
      Serial.print("LONG: "); Serial.println(gps_longt,9);
      delay(5000);
    }
  }
}
```
SSL証明書についての個人的メモ (随時更新)

# ワイルドカード証明書

`*.example.jp` の証明書 → `www.example.jp`, `login.example.jp`, `mail.example.jp` などに対応

- NGケース
    - `*.example.jp` では `sub2.sub1.example.jp` のような2階層以上深いサブドメインは証明の対象外となる
        - その場合には、サブドメイン用の別証明書の発行が必要
    - `*.*.example.jp` のような複数階層にまたがる証明書は発行できない

## 参考リンク

[ワイルドカード証明書について - JPRS](https://jprs.jp/pubcert/about/wildcard/)
AndroidにおけるWebViewでのWebGLコンテンツの画面キャプチャについて

こんにちは。株式会社ZOZOでAndroidエンジニアをしております@zzt-osamuhanzawaです。[ZOZO Advent Calendar 2022](https://qiita.com/advent-calendar/2022/zozo)の17日目の担当をさせていただきます。
本記事ではAndroidのWebViewでのWebGLで描画されたコンテンツの画面キャプチャをする際の課題とどう解決したかのお話の紹介をしたいと思います。

## はじめに
まず、このWebViewの画面キャプチャというユースケース自体あまりないかもしれませんので軽くそこらへんの話をしたいと思います。現在、弊社では[ZOZOFIT](https://zozofit.com/)というフィットネスアプリを米国向けに展開しております。少し前にZOZOSUITを使って体の部位を計測するサービスがありましたが、その技術をさらに応用し、ZOZOSUIT2という計測用のハードを着用し、より身体部位の詳細な計測ができるようになりました。その新しいZOZOSUITとZOZOZFITアプリと併せて使い、ボディマネジメントを実現するといったサービスです。そして、このZOZOFITでは計測を行った身体のデータを以下のような3Dメッシュで表示させてます。
<img width="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218011/47444251-c8e2-fbf2-4522-bb3e51b17a50.png>
この3Dメッシュはアプリ内部でWebGLを使って表示させています。このWebGLで表示させている3Dデータの画面をキャプチャし、外部へシェアしたいというユースケースが発生しました。

## 課題
Androidで画面のキャプチャを処理すると以下のようなコードになるかと思います。
```kotlin
val bmp = Bitmap.createBitmap(webView.width, webView.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmp)
webView.draw(canvas)
```
`contaner`がWebGLで3Dメッシュを表示したWebViewのViewであり、通常であればこの処理でViewのキャプチャができるはずです。ただ、このままだとBitmapに肝心の3Dメッシュが入っていないという状態になりました。ちなみにiOSでは何も工夫なしにキャプチャができたとのことなのでAndroidのWebView特有の問題かと思っております。
<img width="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218011/7269ea0f-b82a-7deb-623b-2c6751e1b04c.png>

## どうしたか
##### WebGL(メッシュビューワー)の修正
結局、どうしたかというとアプリ内部で3Dメッシュを生成しているJavaScriptで実装されているビューワーの修正と一部WebViewの修正を行いました。
```javascript
    // 取得したイメージを表示するためのimg要素
    let captureImg = document.createElement('img');
    // containerは 下記 renderer に重ね合わせたHTML要素で、上記img要素を追加しておく
    container.appendChild(captureImg);
    // renderer は three.js の THREE.WebGLRenderer 
    renderer.domElement.toBlob(function(blob) {
    	// 要素をBlobデータとして取得し、URL文字列にする
        let imgUrl = URL.createObjectURL(blob);
        captureImg.onload = function() {
        	// URLオブジェクトの開放
            URL.revokeObjectURL(imgUrl);
        }
        captureImg.onerror = function() {
            URL.revokeObjectURL(imgUrl);
        }
        // 画像の読み込み
        captureImg.src = imgUrl;
        // rendererを非表示、img要素を表示に切り替える
        renderer.domElement.style.visibility = "hidden";
        captureImg.style.visibility = "visible";
    });
```
上記が3Dメッシュを表示させているビューワーの一部に追加したコードとなっております。やっていることは
 - DOM elementをJavaScriptでキャプチャ
 - blob情報にしてimgタグに描画

して画面表示をさせています。

##### アプリ側の修正
Android WebViewでビューワー側の上記に挙げた関数を呼び出す必要があるためアプリ側も修正します。
`evaluateJavascript` を実行し、先ほどのJSを呼び出します。そして2秒ぐらいまったあと、
```kotlin
webView.draw(canvas)
```
を実行することでWebGLの内容がキャプチャできます。
このようにすることでAndroid WebViewでのWebGLで描画されたコンテンツをキャプチャすることができるようになりました。
<img width="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218011/ea8fe344-0fbe-f573-f8d8-058cb79f35a5.png>

### まとめ
WebViewでWebGLコンテンツのキャプチャというユースケースはほぼほぼないと思います。が、ネット上にもこの際の問題に関しての情報はなかったため本記事として上げさせてもらいました。何かの参考になれば嬉しいです。