ぴこぴよの余白つき画像のサイズを変更しました
いつもぴこぴよをお使いいただきありがとうございます。(ぴこぴよ内に設置したアンケートで「つかってるよ~」とお声をいただき、制作の励みにしております。)
これまで、編集画面の「余白を付けずに画像を投稿」を設定しない場合、ドット絵の拡大率によらず、画像サイズを900 x 510px 固定としてツイートしていました。この画像サイズを 630 x 380px に変更しました。
これまでの画像サイズ 900 x 510px で投稿した場合、Twitterのサムネイル画像においてドット絵がきれいに表示されないことを確認しました。下図のように、投稿されたサムネイル画像を拡大してみると、入力画像とは異なる色味になっていることがわかります。
サイズ 900 x 510px の画像をアップロードしたとき、Twitterのサムネイル画像は 630 x 380px で投稿されているようなので、アップロードする画像も 630 x 380px としたところ、これまでよりきれいに投稿できることを確認しました。
実際のツイートのようすはこちら
投稿する画像をサムネと同じ大きさにしてみる pic.twitter.com/S7JYGks0jQ
— ぴよっぴ (@piyorinpa) October 19, 2021
ということで、きれいにドット絵を投稿できるようになったぴこぴよを今後ともよろしくお願いします。投稿ページにアンケートを設置してるので、よければ使い心地や不具合報告、励ましの投稿をいただけると開発のモチベーション向上につながります。
ではでは。姉妹サービスの「みんなでつくるダンジョン」のほうもあわせてよろしくお願いします。
GitHub Issue Timeline APIでProjectの状態にとどまった時間を取得する
GitHubにはIssueのタイムラインを取得できるTimeline APIがあります。Issueのコメントだけではなく、たとえば「いつ、どのラベルを付与したか」や「いつ、どのProject Columnに移動したか」などを取得できます。(GitHubドキュメント に記載の通り、GitHub Project に関する情報を Timeline APIで取得する機能は開発者プレビュー段階とのことで、 リクエストヘッダに Accept: application/vnd.github.starfox-preview+json
を付与することで取得できます)
たとえばこんなかんじのコマンドでリクエストできます。
curl \ -H "Accept: application/vnd.github.mockingbird-preview+json" \ -H "Accept: application/vnd.github.starfox-preview+json" \ "https://api.github.com/repos/{owner}/{repo}/issues/{issue_id}/timeline"
こんなレスポンスが返ってきます。(一部を抜粋しています)
[ { "id": 5272014742, "event": "converted_note_to_issue", "created_at": "2021-09-08T12:34:49Z", "project_card": { "id": 68365408, "url": "https://api.github.com/projects/columns/cards/68365408", "project_id": 13191782, "project_url": "https://api.github.com/projects/13191782", "column_name": "TODO" } }, { "id": 5272041752, "event": "moved_columns_in_project", "created_at": "2021-09-08T12:39:42Z", "project_card": { "id": 68365408, "url": "https://api.github.com/projects/columns/cards/68365408", "project_id": 13191782, "project_url": "https://api.github.com/projects/13191782", "column_name": "Doing", "previous_column_name": "TODO" } }, { "id": 915208714, "created_at": "2021-09-08T12:48:10Z", "updated_at": "2021-09-08T12:48:10Z", "author_association": "NONE", "body": "コメント", "performed_via_github_app": null, "event": "commented", }, { "id": 5287257709, "event": "labeled", "created_at": "2021-09-11T06:22:04Z", "label": { "name": "label1", "color": "EAA6C8" } }, { "id": 5287262270, "event": "closed", "created_at": "2021-09-11T06:29:45Z" } ]
event
パラメータによって「なにが行われたか」が分かります。たとえばこんなかんじ。
event | 何が行われたか |
---|---|
labeled | ラベルが付与された |
unlabeled | ラベルが外された |
added_to_project | プロジェクトに追加された |
converted_note_to_issue | プロジェクトカードをIssueにした |
moved_columns_in_project | プロジェクトカードを移動した |
また、各イベントのオブジェクトには created_at
パラメータが存在するので、eventパラメータと組み合わせると、ラベルが付与されていた時間や、プロジェクトのある状態にとどまった時間を集計することができます。
これらを行うGitHub Actionsを書いてみました。
たとえば、私は自身のGitHub Projectで趣味開発のタスク管理をしていたりしますが、このActionsを導入することで、なににどれくらい時間がかかっているかを簡単に確認できるようになります。
こんなかんじに使います。Issueが閉じられたタイミングで、ラベルが付与された時間やプロジェクトにかかった時間をコメントします。
name: issue-closed on: issues: types: - closed jobs: closed: runs-on: ubuntu-latest steps: - name: Report id: report uses: piyoppi/actions-labeled-duration@abf85a23b076409eb646c7319d72c3a9e7167e27 with: labels: label1,label2 # 集計対象のラベル(Optional) project_columns: TODO,Doing # 集計対象のプロジェクトカラム名(Optional) issue_comment: '⌛このIssueの作業にかかった時間は以下の通りです' # Issueのコメント(Optional)
実行するとこんなかんじ になります。
ちまちまと使いながら手を入れていこうと思っています。ではでは。
みんなでつくるダンジョンに「マップチップ」モードを追加しました
みんなでつくるダンジョンにマップチップモードを追加しました。いわゆる「マップチップ」を並べてマップが作れるようになりました。
こんなマップがつくれます。現状、「見下ろしビューモード 」 のみ対応しています(横スクロールマップも作れますが、衝突処理に難があります)。
機能の概要などについては、以前に公開した以下のブログ記事を参照してください。作れるマップの雰囲気がわかると思います。 garakuta-toolbox.hatenablog.com
使い方については、以下のドキュメントを参照してください。
一応「試験公開」ということにしています。こんな機能があったらいいな~などあれば、おきがるに @piyorinpa まで教えてください。みなさんがこの機能を使ってくれると、機能追加などをしよう~という気持ちになれます。
不具合報告は@piyorinpa あるいは 不具合報告フォーム までお願いします。
ではでは
LitでつくったWebComponentをJestでテストする
Lit(+TypeScript)でつくったWebComponentをJestでテストしたくなったので、行ったことをまとめておきます。
前提
- テストやコンポーネントはTypeScriptで記述
- ts-jestが導入済み
- Lit 2.0.0 / Jest 26系で動作確認
テストを書いてみる
以下のようなコンポーネントを定義したとします。
import { LitElement, html, css } from 'lit' import { property, customElement } from 'lit/decorators.js' @customElement('test-component') export class TestComponent extends LitElement { @property({type: String}) title = '' render() { return html` <div> <span>${title}</span> </div> `; } }
まずは JestのDOM操作に関するドキュメント を参考にテストを書いてみます。雰囲気はこんなかんじ。
// 以下のように読み込むことで @customElement decoratorによってコンポーネントが定義される import '~/src/TestComponent' // 型情報がほしいので別途TestComponentを読み込む import { TestComponent } from '~/src/TestComponent' test('Content should include a title', async () => { document.body.innerHTML = ` <test-component title="タイトル"></test-component> ` const component = document.getElementsByTagName('test-component')[0] as TestComponent // コンテンツが描画されるまで待つ await component.updateComplete // たとえばtitle attribute が正しくコンテンツに反映されていることを確認する expect(component.shadowRoot?.innerHTML.match(/タイトル/g)?.length).toEqual(1) })
Lit ドキュメントの Lifecycleの項 をみると、コンテンツの更新が完了すると updateComplete
PromiseがResolvedになるとあるので、コンテンツが更新されてからテスト項目を確認していきます(更新される前にテスト項目を確認すると、変更が反映されていない可能性があり、思うような実行結果が得られません)。
DOMの操作・取得の関数(たとえば Element.innerHTML
など)がテストでも使えるので、これを用いて actual な状態を確認していきます。
もし、 @customElement
decoratorを使わずにコンポーネントを定義している場合は、以下のようにテスト内で customElements.define()
を呼ぶことで同様にCustomElementを定義できます。
import { TestComponent } from '~/src/TestComponent' customElements.define('test-component', TestComponent) // 以下同じ
テスト中の document.getElementsByTagName
で取得した component
は TestComponent
をインスタンス化したものなので、メソッドやプロパティにアクセスすることもできます。TestComponent内の何かをモックしたい~みたいなこともできます。
テストを実行できるようにする
テストを実行してみると、以下のようなエラーが出てしまいます。
FAIL tests/TestComponent.test.ts ● Test suite failed to run Jest encountered an unexpected token This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript. By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules". Here's what you can do: • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it. • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config. • If you need a custom transformation specify a "transform" option in your config. • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option. You'll find more details and examples of these config options in the docs: https://jestjs.io/docs/en/configuration.html Details: /path/to/test-project/node_modules/lit/index.js:1 ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import"@lit/reactive-element";import"lit-html";export*from"lit-element/lit-element.js"; ^^^^^^ SyntaxError: Cannot use import statement outside a module > 1 | import { LitElement, html, css } from 'lit' | ^
Lit ライブラリ内に import
文があるので、これをJestが解釈できずにエラーになっているようです。Jestはデフォルトでは node_modules
配下のファイルをトランスパイルしないので、
エラーメッセージにある通り transformIgnorePatterns
に手を加え、Lit関連ライブラリがトランスパイルされるようにします。
トランスパイルの対象はエラーメッセージのとおりjsファイルなので、 babel-jest
を使ってトランスパイルされるようにします。(必要に応じて @babel/preset-env
をインストールします)
JestのGetting Started を参考に、babel.config.js
を以下のように記述します。
module.exports = { presets: [ [ '@babel/preset-env', { targets: { node: 'current', }, } ] ] }
続いて、jest.config.js
を以下のように設定します。
- Jestドキュメントの例を参考に、
transformIgnorePatterns
を以下のように記述します- transformIgnorePatterns は「指定した正規表現にマッチするファイルはトランスパイルされないようにする」プロパティ
- tsファイルの場合は ts-jest、jsファイルの場合は babel-jest を使いたいので、ts-node Presets を参考に、presetに
ts-jest/presets/js-with-babel
を設定します
module.exports = { preset: 'ts-jest/presets/js-with-babel', transformIgnorePatterns: [ "/node_modules/(?!(@lit|lit|lit-element|lit-html)/)" ] }
これでテストを実行できるようになりました。めでたしめでたし。
(おまけ)Canvas要素を含むコンポーネントのテスト実行
CustomElementにCanvasに関する処理が含まれていた場合は、テストの実行に失敗することがあります。JestのDOM周りには jsdom が用いられていますが、READMEをみると Cavnas Support の項があります。 これによると、 node-canvs をインストールすればよいとのことなので試してみると、インストールして特に設定を変えることなく実行することができました。
LitのTestingの項 によると、Web Test Runner を使うのがおすすめだよ!と書いてあるので、これもおいおいさわってみよう~となっていました。ではでは~
最近の開発の記録(20210802)
最近の開発のきろくをばをば。みんなでつくるダンジョン のマップエディタ機能がほぼほぼ固まってきたのでした。こんな感じのマップエディタができつつあります。 (ちなみにこのマップエディタは pico2map として公開しています。まだ安定版ではありませんが、そのうちいろいろちゃんとします)
※記事中のマップに含まれる素材画像は、墨cmさまから提供いただいたものと、ぴぽや様(https://pipoya.net)からお借りしたものを使っています。
これまでのマップエディタは、このようにパーツを配置して並べていくようにマップを作り上げる方式でした。
さらに、昨年には 見下ろしビュー のマップをつくれるようになったため、これまでのアクションゲーム形式の横スクロールマップのほか、RPGのようなマップも作れるようになりました(こんなマップがつくれます)。しかしながら、「パーツを配置して並べていく」形式のマップエディタだと、RPG向け素材画像などが活用しづらいなどの問題がありました。
ということで以下のように、マップチップを並べてマップをつくっていくタイプのマップエディタを開発しているのでした。マップチップをぺたぺたと貼り付けていくようにマップをつくれます。また、いわゆるオートタイルをサポートし、道や川などをつくるときに使われるマップチップを自動生成/配置するので、便利になっています。
レイヤーも4層つかうことができます。4枚目のレイヤーが「キャラクターより前側」に表示されるようになっています。
もちろん今まで通りの「パーツを並べる」方式でのマップ編集もサポートしており、マップチップとパーツを共存することができます。マップチップ編集ボタンを押すと、マップチップを配置できるようになります。
マップチップは(いまのところ)アニメーションをサポートしていませんが、これまでの「パーツ」はアニメーションや「プラグインの適用」ができるので、このようにねこや人を右往左往させたりできます。
マップチップはあくまでも「画像を並べる」だけなので、動きのあるものを作るときは引き続き「パーツを配置」していく形になります。
こんなかんじのマップエディタができつつあります。まだ最低限というかんじの機能ですが、遊んでいて楽しい感じになってきました。
いまはひたすら私自身が遊んでバグを見つけて直して...という作業を行って動作を安定させる方向で開発作業をしています。渋いバグなどは取れつつあるので、あともうちょっと~というかんじです。ではでは~
Amazon S3+CloudFront環境へGitHub Actionsをつかってデプロイする
静的サイトの配信のために、Amazon S3にファイルを設置してCloudFrontで配信する方法はよく行われていると思いますが、私の管理するアプリケーションでもこのような構成のものがあります。
というわけで、もっとかんたんにデプロイできるようにしたいな~ということで、GitHub Actionsでデプロイできるようにしたので、備忘録としてまとめました。
成果物のWorkflow
こんなかんじのWorkflowを定義します。私のアプリケーションの場合は事前にnode環境のセットアップ(actions/setup-node@v1
)をしたり、ビルド( npm run build
)してからS3にファイルを設置していますが、このへんはそれぞれのケースに応じて調整します。
Workflowのトリガは、今回は workflow_dispatch
として任意のタイミングで実行することとしましたが、このへんもそれぞれのケースに応じて調整するとよさそうです。
name: Deploy to s3 on: workflow_dispatch jobs: upload: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 # 成果物を生成する - uses: actions/setup-node@v1 with: node-version: '16.x' - name: Cache dependencies uses: actions/cache@v2 with: path: | **/node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - run: npm install - run: npm run build # S3にアップロードしてInvalidationリクエストを投げる - uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: (region) - run: aws s3 sync ./build s3://example-bucket - run: aws cloudfront create-invalidation --distribution-id (distribution-id) --paths "/*"
やっていることは概ねこんな感じです。
- aws-actions/configure-aws-credentials@v1 をつかって認証情報をセットアップする
- これを使うとawsコマンドを認証が済んだ状態+Regionをセットした状態で使うことができます。
(region)
を任意のRegionに設定します。
aws s3 sync
コマンドでビルドした成果物(など)をS3 Bucketに送信するaws cloudfront create-invalidation
でファイルの無効化を行い、CDNのサーバのコンテンツを差し替える(--paths "/*"
とすることですべてのファイルを無効化する)(distribution-id)
の部分は任意のDistribution IDに置き換えます。- Invalidationにはお金がかかります。 CloudFrontのドキュメント によると、1000件までの無効化パスまでは無料のようです。頻繁にこのActionsを実行する場合はこのことも考慮される必要がありそうです。
Workflowを動かすためのAWSアクセスキーの準備
各種awsコマンドが使えるように、アクセスキーを準備します。上記Actionsで用いるコマンド( aws s3 sync
, aws cloudfront create-invalidation
)には以下の権限が必要です。
(s3 sync
に必要な権限は、 こちらの記事 を参照しました)
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::(bucket-name)/*" ] }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": [ "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::(bucket-name)" ] }, { "Sid": "VisualEditor2", "Effect": "Allow", "Action": [ "cloudfront:CreateInvalidation" ], "Resource": [ "arn:aws:cloudfront::(aws-account-id):distribution/(distribution-id)" ] } ] }
(bucket-name)
はアップロード先のBucket名を設定します。(aws-account-id)
は、アカウントIDを設定します。- こちらのドキュメント に従ってアカウントIDを確認できます。
(distribution-id)
は、CloudFrontのDistribution ID を設定します。
これらのアクセス権限を持つIAM Userを作成し、アクセスキーとシークレットアクセスキーを取得します。これらを AWS_ACCESS_KEY_ID
と AWS_SECRET_ACCESS_KEY
として、それぞれGitHubリポジトリのSecretsに保存します
これで、さきほど定義したWorkflowが動作するようになります。
そのほか参考にしたもの
ビルドやアップロード周りを自動化することで、うっかりアップロードしちゃいけないファイルをアップロードするなどのミスが減るので、気軽にデプロイができるようになりますね。ではでは~
JSON Schemaでお手軽アンケートフォーム
こんにちは。お手軽に自分のサイトに埋め込むアンケート的なやつを作りたいけど、Google Formsなどでは若干要件を満たさないというお悩みがありました。
- ログインしているときだけアンケートに答えられるようにしたい
- 入力フォームに隠しパラメータを埋め込みたい(
<input type="hidden">
みたいなものを入れたい) - データの後加工をしたいので任意の場所にアンケート結果を保存したい
これらを解決するあれこれを作っていました(作っています)。
だいたいこんな感じのアプリケーションです。
保存したアンケートを集計したり、分析したりすることはこのアプリケーションではしないものとします。
このリポジトリはmonorepo構成で、いくつかのアプリケーションで構成されています。
アンケートフォーム
JSON Schemaに基づき、アンケートフォームを生成して表示する、Reactベースのアプリケーションです。
アンケートフォームの生成には react-jsonschema-form を使っています。JSON Schemaを与えることで、 適したInput要素を表示してくれます。このライブラリをベースに、アンケートフォームとして使いやすい形のデザインに調整しました。
このアプリケーションを起動し、 /surveys/:schemaId
にアクセスすると、指定したJSON Schemaを指定したアンケート定義JSON Schema配信サーバから取得してフォームを表示します。
たとえば、このアプリケーションをビルドする際の環境変数 REACT_APP_SCHEMA_BASE_URL="https://example.com"
として、以下のようなJSON Schemaを https://example.com/test-survey
に設置するとします。
{ "title": "アンケート", "description": "サンプルアンケートです", "type": "object", "required": ["useful"], "properties": { "useful": { "type": "string", "title": "満足度", "description": "このウェブサイトににどれくらい満足していますか?", "enum": [ "とても満足", "まあまあ満足", "あまり満足じゃない", "不満" ] }, "comment": { "title": "ご意見やご感想", "description": "ご意見やご感想、機能追加や改善のご要望などご自由にお書きください", "type": "string", "maxLength": 1000 } } }
/surveys/test-survey
にアクセスしたら GET https://example.com/test-survey
でJSON Schemaを取得し、以下のようなアンケートフォームを表示します。
送信ボタンを押すと、環境変数 REACT_APP_BASE_URL
に指定したアンケート受付APIにアンケートをPOSTします。
埋め込みアンケートフォームと署名付きパラメータ
このアンケートフォームをiframeを使ってサイトに埋め込んだとき、サイト側で生成した署名付きパラメータを受け取ることができます。アンケートと一緒に任意のパラメータを埋め込みたいときなどに使います。
アンケートを埋め込みたいページに以下のようにiframeを設置します。(アンケートフォームが http://localhost:3000
、埋め込み先ウェブサイトが http://localhost:3001
で起動しているとします。)
<!-- アンケートフォームが localhost:3000 で起動しているとする --> <!-- 埋め込んでいるウェブサイトは localhost:3001 で起動しているとする --> <iframe id="survey-form" src="http://localhost:3000/surveys/test-survey"></iframe>
たとえば、先ほどのアンケート test-survey
のJSON Schemaに以下のようなキー reportBoxOptions
を追加します。signedParameters
には署名付きパラメータの定義を、embedded
には埋め込み先ページのOriginを記述します。
{ "title": "アンケート", "description": "サンプルアンケートです", "type": "object", "properties": { ... } "reportBoxOptions": { "signedParameters": { "type": "object", "required": ["loggedIn"], "properties": { "loggedIn": { "type": "boolean" } }, "embedded": { "parentOrigin": "http://localhost:3001" } } } }
この signedParameters
に記載された形式のJSONをJWTに変換してアンケートフォームに渡します。
アンケートを埋め込みたいページのサーバサイド側でJWTを生成します(secret
は秘密鍵)
const jwt = require('jsonwebtoken'); const secret = 'test-secret'; // 今のところパラメータの暗号化を考慮していないので、秘密の情報は入れないこと const signedParameters= jwt.sign({ params: { loggedIn: true } }, secret);
アンケートを埋め込んだページに以下のような処理を追加します。
<script type="text/javascript"> const iframe = document.getElementById('survey-form'); const formOrigin = 'http://localhost:3000'; window.addEventListener('message', e => { // アンケートフォームから送られたメッセージでなければ受け取らない if (e.origin !== formOrigin) return; // アンケートフォームからSignedParameters受け取り準備完了のメッセージを受け取ったら // アンケートフォームへSignedParameters(をJWTエンコードした値)を送信する if (e.data.event === 'readyToReceiveSignedParameters') { // (signedParameters は埋め込み先のサーバ側で生成したJWT) iframe.contentWindow.postMessage(signedParameters, formOrigin); } }) </script>
アンケートフォーム側から signedParameters
の受け取り準備が完了したタイミングでメッセージが送出されるので、確認の上アンケートフォームへ signedParameters
の JWT を渡します。
アンケート送信時にこのJWTも一緒に送信され、アンケート受付APIでデコードされます。署名検証に成功したら、データストアに保存します。
JSON Schemaに signedParameters
の設定を記述した場合は、署名付きパラメータがアンケート受付APIへのリクエストボディに含まれないと保存されないので、たとえばログインしていない人にはアンケートを
送信してほしくないなどの用途にもつかえます。
アンケート回答後の遷移先の指定
JSON SchemaにreportBoxOptions.callbackUrl
パラメータを指定することで、アンケートを回答した後に任意のページに遷移させることができます。
完了画面を表示したり、アンケート回答後に何らかのアクションをしたいときに使います。
{ "title": "アンケート", "description": "サンプルアンケートです", "type": "object", "properties": { ... } "reportBoxOptions": { ... , "callbackUrl": "https://example.com/callback" } }
アンケート受付API
アンケートを受け取り、アンケート定義JSON Schemaサーバからアンケートに対応したJSON Schemaを取得したうえで、バリデーションをしてデータストアに保存します。 サーバ側のバリデーションには ajv をつかっています。
今回は、実装のひとつの例として、AWS環境(API Gateway と Lambda)にデプロイできるSAM TemplateとLambda Functionのサンプルを用意しました。
提出されたアンケートを検証(JSON Schemaを用いた入力値バリデーションと署名付きパラメータの署名検証)し、成功した場合はreportBoxMeta
というメタデータを付与したうえで、指定したS3 Bucketにデータを保存します。
例えば先ほどの test-survey
フォームを送信して検証が成功したとき、こんなデータが保存されます。
{ "useful":"まあまあ満足", "comment":"ここをあれこれするともっといいと思います。", "loggedIn":true, "reportBoxMeta":{ "metaDataVersion":1, "schemaId":"test-survey", "createdAtUtc":"2021-06-07T16:01:12.672Z" } }
まとめ
JSON Schemaでアンケートが作れるそれっぽいアプリケーション群をお試しで実装してみました。好きな場所にアンケート結果を収集できるようになったので、集計やデータの加工がしやすくなったかなと思っています。 私の自作アプリケーションに組み込むなどしながら引き続き手を加えていきたいなと思っています。あと、せっかく作ったのでちゃんと使い方とかをまとめなければ...。
ではでは~。