tag:crieit.net,2005:https://crieit.net/boards/baseball-score-management/feed 「野球リーグスコア管理システムの開発」の投稿 - Crieit Crieitで「野球リーグスコア管理システムの開発」ボードに投稿された最近の投稿 2020-10-12T13:35:04+09:00 https://crieit.net/boards/baseball-score-management/feed tag:crieit.net,2005:PublicArticle/react-planet-safari12 2020-10-12T13:28:19+09:00 2020-10-12T13:35:04+09:00 https://crieit.net/boards/baseball-score-management/react-planet-safari12 react-planetがsafari12系で動かない問題 <h1>react-planet</h1> <p><a href="https://crieit.now.sh/upload_images/b4a4698dfd42a33222225ae8c87679a75f83d7b5ea6e5.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b4a4698dfd42a33222225ae8c87679a75f83d7b5ea6e5.png?mw=700" alt="concept.png" /></a><br /> 手軽に円形メニュー(Circular Menu)を提供できるライブラリ。</p> <h2>ResizeObserver is not defined</h2> <p>react-planetではリサイズイベントをResizeObseverによって検知しています。</p> <p><strong>ただし、ResizeObserverはsafari 13.1以降に対応しています。</strong><br /> 手元にあるiPhone5Sはsafari12系でした。</p> <p>react-planet<br /> ┗ <a target="_blank" rel="nofollow noopener" href="https://github.com/ZeeCoder/use-resize-observer">use-resize-observer</a><br />   ┗ <a target="_blank" rel="nofollow noopener" href="https://github.com/que-etc/resize-observer-polyfill">resize-observer-polyfill</a></p> <p>今回、おそらく動いていないのはresize-observer-polyfillのようです。<br /> きちんとメンテされてそうなのはこれ。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/juggle/resize-observer">resize-observer</a></p> <p>ただ、use-resize-observerのソースを書き換えて、<br /> react-planetのdependencyも変えないといけないので一旦調査してエラー回避する方向で終了。</p> <p>TopMenuコンポーネントの中でreact-planetを使っています。</p> <pre><code class="javascript"><br /> {typeof ResizeObserver !== 'undefined' ? <div id="topMenu"> <TopMenu/> </div> :<></> } </code></pre> ckoshien tag:crieit.net,2005:PublicArticle/CI-Netlify 2020-10-01T23:31:59+09:00 2020-10-01T23:31:59+09:00 https://crieit.net/boards/baseball-score-management/CI-Netlify CIを設定していないのにNetlifyのビルドが通らなくなった! <h1>Treating warnings as errors because of process.env.CI = true</h1> <p>2020/6/15にNetlifyの仕様が変わったのが原因でソースの警告があるとビルドが通らなくなる現象が発生している模様。</p> <h2>解決方法</h2> <p>(元のビルドコマンドがnpm run buildの場合)<br /> Build Commandを「CI = npm run build」に変更する。</p> <p><a href="https://crieit.now.sh/upload_images/c757f121bd19245f1a30d36c3424b0625f75e71168f55.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c757f121bd19245f1a30d36c3424b0625f75e71168f55.jpg?mw=700" alt="" /></a></p> <p>その他コンフィグの設定方法が変わってきているようなのでリリースなどがある場合は要注意かも。</p> ckoshien tag:crieit.net,2005:PublicArticle/NextJS-PWA 2020-09-21T23:48:25+09:00 2020-09-21T23:48:25+09:00 https://crieit.net/boards/baseball-score-management/NextJS-PWA NextJSをPWA化 <h1>NextJSをPWA化しました</h1> <p>「草野球放送局」という草野球のライブ配信補助ツールを作ったのですが、ブラウザにカメラ映像を映してスコアボードと合成する手法だと、どうしても動画の表示部分が狭くなってしまう問題点がありました。</p> <h2>草野球放送局</h2> <ul> <li><a href="https://crieit.net/posts/9607755c4800bec007d1a912ce8f42dc">草野球のライブ配信を補助するツールを作った話</a></li> </ul> <h2>ReactJSをPWA化する方法</h2> <ul> <li><a href="https://crieit.net/boards/baseball-score-management/PWA">PWA対応</a></li> </ul> <h2>NextJSをPWA化するには</h2> <h3>next-pwa</h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/shadowwalker/next-pwa">next-pwa</a></li> </ul> <p>詳しくはREADMEを読んでいただければと思うのですが、</p> <ol> <li>next.config.jsを書き換える</li> <li>serviceWorker.jsを設置する(SW.jsにリネーム)</li> </ol> <p>NextJSをcreate-next-appで作るとserviceWorker.jsがないのでCRAから拝借しました。</p> <h2>PWAでカメラを使うには</h2> <p>あれ、iOSでカメラ起動しないな...と思ってたのですが、iOS13以前ではPWAの表示モードが"browser"でしかカメラを起動できません。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/1pp0/items/a3f0ea14910e25f49878">PWAでカメラを使うためiOSとAndroidで異なるmanifestを読み込む</a></p> <p>なので、<code>react-device-detect</code>を使ってmanifest.jsonへのリンクをユーザエージェント別に書き換えます。</p> <pre><code class="javascript">import * as rdd from 'react-device-detect'; ...中略 {rdd.isAndroid && rdd.isChrome ? <link rel="manifest" href="/manifest.json" /> :'' } {rdd.isIOS && rdd.isSafari ? <link rel="manifest" href="/manifest.ios.json" /> :'' } </code></pre> <h2>オチ</h2> <p>iOSは"browser"モードだから表示領域増えないじゃん!!!<br /> androidは(ほぼ)全画面使えますが。</p> ckoshien tag:crieit.net,2005:PublicArticle/54e64fb3977c5dc1c6b7a736b952385d 2020-08-26T17:06:25+09:00 2020-08-26T21:45:07+09:00 https://crieit.net/boards/baseball-score-management/54e64fb3977c5dc1c6b7a736b952385d 蓋速計測器をリリースしました <h2>蓋速計測器をリリースしました</h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://cap-baseball.com/calcspeed">ブラウザから蓋速が出せる蓋速計測器</a></li> </ul> <p><a href="https://crieit.now.sh/upload_images/bd19742064e38749c419e028c2aeee115f461623e171c.jpeg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/bd19742064e38749c419e028c2aeee115f461623e171c.jpeg?mw=700" alt="EgULFyPVoAEFJi6.jpeg" /></a></p> <p>基本的に動画から蓋速を算出する場合はコマ送りできるソフト(aviutlなど)を使ってフレーム数から到達時間を計算しているのですが、aviutlはwindows専用なので、誰でも同じように簡単に速度を算出できる仕組みがあれば便利だと思い、reactから動画を扱えるコンポーネントを使って、コマ送り機能と、リリースの瞬間とキャッチの瞬間の時間を記録すれば誰でも速度を計算できる機能を実装しました。</p> <h3>実装</h3> <p>video-reactはtypescriptの型がないようだったので少し面倒でした。</p> <p><code>9.22*3.6/(cTime-rTime)</code><br /> 9.22mを(「キャッチした時間」-「リリースした時間」)の差で割って3.6(秒速から時速への変換)をかけます。</p> <pre><code class="javascript">import React, { createRef, useEffect, useState, useMemo } from "react"; import { Player, ControlBar, ForwardControl } from "video-react"; const calcSpeed = () => { const playerRef = createRef(); const [fileURL, setFileURL] = useState(null); const [rTime, setRTime] = useState(null); const [cTime, setCTime] = useState(null); return ( <> <Player fluid={false} height={400} width={'95vw'} ref={playerRef} autoPlay src={fileURL} ></Player> <input onChange={(e) => { if (e.target.files.length > 0) { const url = URL.createObjectURL(e.target.files[0]); setFileURL(url); } <span>}</span><span>}</span> type="file" multiple={false} accept=".mp4" /> <button onClick={() => { const player = playerRef.current.getState(); playerRef.current.seek(player.player.currentTime + 0.03); console.log(player.player.currentTime); <span>}</span><span>}</span> > +0.03s </button> <button onClick={() => { const player = playerRef.current.getState(); playerRef.current.seek(player.player.currentTime - 0.03); console.log(player.player.currentTime); <span>}</span><span>}</span> > -0.03s </button> <button onClick={()=>{ const player = playerRef.current.getState(); setRTime(player.player.currentTime); <span>}</span><span>}</span> >リリース</button> <button onClick={()=>{ const player = playerRef.current.getState(); setCTime(player.player.currentTime); <span>}</span><span>}</span> >キャッチ</button> <br/>リリース:{rTime} <br/>キャッチ:{cTime} <br/>蓋速:{cTime && rTime && (cTime - rTime > 0) ? (9.22*3.6/(cTime-rTime)).toFixed(2)+'km/h':''} </> ); }; export default calcSpeed; </code></pre> ckoshien tag:crieit.net,2005:PublicArticle/7a0a7e6b1b3cef872fc0d95aba5e1513 2020-08-26T17:00:19+09:00 2020-08-26T21:44:03+09:00 https://crieit.net/boards/baseball-score-management/7a0a7e6b1b3cef872fc0d95aba5e1513 情報局のチームページレイアウトの刷新 <h2>チームページのレイアウトを新しくしました</h2> <p>各チームのトップページに新たに大きな写真を採用し、チーム名の縦置きレイアウトを廃止しました。テーマカラーの表示は維持しつつ、横置きに変更しました。<br /> レスポンシブで最大横幅を1000pxにし、基本2カラム、スマホは1カラムにしています。<br /> チーム紹介とメンバー紹介はだいぶteamsの影響を受けました。</p> <h3>チーム紹介</h3> <p><a href="https://crieit.now.sh/upload_images/375b4816178c76a2def3573ab6c9df785f460ed7160dd.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/375b4816178c76a2def3573ab6c9df785f460ed7160dd.png?mw=700" alt="" /></a></p> <h3>メンバー紹介</h3> <p>自分で入力した自己紹介の一部を表示するようにしました。<br /> <a href="https://crieit.now.sh/upload_images/6ceaf5db4d119105d794e2581d42dbeb5f460f1b68138.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6ceaf5db4d119105d794e2581d42dbeb5f460f1b68138.png?mw=700" alt="" /></a></p> <h3>戦績</h3> <p>試合結果一覧のデザインを取り込みました。<br /> 若干デザイン崩れなので再考の余地ありというところです。<br /> <a href="https://crieit.now.sh/upload_images/411c410f8c97e2f2718f3d01ad4d1e325f460f3a109ba.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/411c410f8c97e2f2718f3d01ad4d1e325f460f3a109ba.png?mw=700" alt="" /></a></p> <h3>選手成績</h3> <p>元々打撃・投手成績タブに分けていたものを統合しました。<br /> <a href="https://crieit.now.sh/upload_images/3d9262bc4745636759a8e35744c8965a5f460f46c49c9.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3d9262bc4745636759a8e35744c8965a5f460f46c49c9.png?mw=700" alt="" /></a></p> ckoshien tag:crieit.net,2005:PublicArticle/OGP-vercel 2020-08-08T22:04:03+09:00 2020-08-08T22:04:03+09:00 https://crieit.net/boards/baseball-score-management/OGP-vercel OGPサーバをvercelに移設した <p><a target="_blank" rel="nofollow noopener" href="https://github.com/ckoshien/ogp-vercel">ソース</a></p> <p>前回、puppeteer-clusterで同時起動の制御をしたはずだったのだが、<br /> 書き方が悪いのか同時アクセスすると暴走してVPSの応答がなくなってしまうので、vercelに分離した。</p> <h2>vercelとは</h2> <p><a target="_blank" rel="nofollow noopener" href="https://vercel.com/">vercel</a></p> <p>herokuと同じようにサーバサイドのプログラムをデプロイできるサービス。<br /> netlifyはフロントエンド特化。</p> <h2>OGPサーバの用途</h2> <ul> <li><a href="https://crieit.net/posts/1bbf25a7cba4095f0fd1afe817b553dd">パワプロ風画面ジェネレータを作ってみた</a></li> <li><a href="https://crieit.net/boards/baseball-score-management/2a852991709339b8e3fabd03a921fa8b">バえない機能のつくりかた</a></li> </ul> <h2>ルーティングに苦戦</h2> <p>vercelは<code>now.json</code>でルーティングを記述している。<br /> 正規表現で各メソッドに<code>req.query.id</code>を渡す場合の記述の仕方はこんな感じ。</p> <pre><code class="javascript"> "routes": [ { "src": "/cap-baseball/(?<id>[^/]*)", "dest": "/screenshotCap.js?id=$1" }, { "src": "/(?<id>[^/]*)", "dest": "/screenshot.js?id=$1" } ] </code></pre> <h2>puppeteerでスクリーンショット</h2> <p>このあたりは前回の記事で説明しているので不要かなと思う。<br /> cluster使わない書き方に戻してます。</p> <pre><code class="javascript">const browser = await puppeteer.launch({ args: chrome.args, executablePath: await chrome.executablePath, headless: chrome.headless, }); const page = await browser.newPage(); await page.goto("https://pawapro-gen.netlify.app/view/" + id); await page.setCacheEnabled(false); await page.waitForSelector("#main"); const fullPage = await page.$("#main"); const fullPageSize = await fullPage.boundingBox(); const VIEWPORT = { width: 600, height: 450, deviceScaleFactor: 1 }; await page.setViewport( Object.assign({}, VIEWPORT, { height: fullPageSize.height }) ); await page.waitFor(5000); const elements = await page.$$("#main"); let img; for (const [index, element] of elements.entries()) { img = await element.screenshot(); } await page.close(); await console.log("screenshot done."); return img; </code></pre> ckoshien tag:crieit.net,2005:PublicArticle/puppeteer-cluster-puppeteer 2020-07-19T17:51:39+09:00 2020-07-19T17:51:39+09:00 https://crieit.net/boards/baseball-score-management/puppeteer-cluster-puppeteer puppeteer-clusterでpuppeteerの同時起動数制御 <h2>経緯</h2> <p>OGP生成用のpuppeteerアプリケーションに複数アクセスがあった場合、同時にpuppeteerが複数起動してしまってサーバのリソースを食い尽くす憂き目に遭ったのでpuppeteer-clusterを使って同時起動数を制御することにした。</p> <h2>puppeteer-cluster</h2> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/thomasdondorf/puppeteer-cluster">github</a></p> <h3>maxConcurrency</h3> <p>同時に起動する最大プロセス数です。</p> <h3>puppeteerOptions</h3> <p>puppeteerに渡していたオプションがclusterでも使えます。</p> <pre><code class="javascript"> headless: true, executablePath: "/usr/bin/chromium-browser", args: ["--no-sandbox"], </code></pre> <h3>cluster.execute()</h3> <p>結果を取得するためにqueueではなくexecuteを使います。</p> <h2>実装</h2> <pre><code class="javascript">try { const cluster = await Cluster.launch({ concurrency: Cluster.CONCURRENCY_CONTEXT, //同時最大起動数1 maxConcurrency: 1, //puppeteerの起動オプション puppeteerOptions: { headless: true, executablePath: "/usr/bin/chromium-browser", args: ["--no-sandbox"], }, }); await cluster.task(async ({ page, data: id }) => { await page.goto("https://cap-baseball.com/player/" + id); await page.setCacheEnabled(false); await page.waitForSelector("#main"); const fullPage = await page.$("#main"); const fullPageSize = await fullPage.boundingBox(); const VIEWPORT = { width: 920, height: 700, deviceScaleFactor: 1 }; await page.setViewport( Object.assign({}, VIEWPORT, { height: fullPageSize.height }) ); await page.waitFor(3000); const elements = await page.$$("#main"); let img; for (const [index, element] of elements.entries()) { img = await element.screenshot(); } await page.close(); await console.log("screenshot done."); //撮ったスクリーンショットを返却する return img; }); //結果を取得するためにqueueではなくexecuteを使う const result = await cluster.execute(capId); await cluster.idle(); await cluster.close(); return result; } catch (err) { console.error(err); throw new Error(err); } </code></pre> ckoshien tag:crieit.net,2005:PublicArticle/2a852991709339b8e3fabd03a921fa8b 2020-07-11T00:45:20+09:00 2020-07-11T00:45:20+09:00 https://crieit.net/boards/baseball-score-management/2a852991709339b8e3fabd03a921fa8b バえない機能のつくりかた <p>割と思い付きで機能を作ってしまう方なのですが、<br /> (旧バージョンの<a target="_blank" rel="nofollow noopener" href="https://cap-baseball.com">情報局</a>でムダとなった機能をリニューアル時に捨てた)<br /> 今回はブラウザで作れる野球選手カードについて。</p> <h1>CSSで画像を装飾する</h1> <p><a href="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55f088b6a27027.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55f088b6a27027.jpg?mw=700" alt="" /></a></p> <h2>フレームを作る</h2> <p><a href="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55f088ba3aa9f0.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55f088ba3aa9f0.jpg?mw=700" alt="" /></a><br /> 画像を見てやり方に気づいた方はご名答。<br /> 角のフレームは四角形を傾けて重ねてるだけなのです。<br /> <strong>transform 15deg</strong>で15度傾けて表示しています。<br /> 親要素をrelativeにし、この要素自体をabsoluteにすると<br /> 位置調整ができます。</p> <pre><code class="jsx"><div style=<span>{</span><span>{</span> position: "absolute", transform: "rotate(15deg)", bottom: -40, right: 5, width: 350, height: 100, backgroundColor: "色を指定", <span>}</span><span>}</span> /> </code></pre> <h2>はみ出した部分をカットする</h2> <p>親要素の大きさを指定して、<strong>overflow:hidden</strong><br /> とすると、はみ出した要素を非表示にできます。</p> <pre><code class="jsx"><div style=<span>{</span><span>{</span> width: 340, height:450, backgroundColor:'#e6e6e6', overflow: "hidden", position: "relative", <span>}</span><span>}</span> /> </code></pre> <h2>写真に丸アイコンを載せる</h2> <p>div要素のbackgroundImageに画像を指定して、<br /> <strong>border-radius 50%</strong>で丸にします。<br /> div要素の高さはwidth,heightで指定し、<br /> 画像の大きさはbackground-sizeで調整します。</p> <p>親要素をrelativeにし、この要素自体をabsoluteにすると<br /> 位置調整ができます。</p> <pre><code class="jsx"><div style=<span>{</span><span>{</span> width: 80, height: 80, border:'2px solid white', backgroundSize: "80px 80px", backgroundImage: `url('画像のURL')`, backgroundRepeat: "no-repeat", backgroundPosition: "center center", position: "absolute", display: "inline-block", borderRadius: "50%", top: 3, left: 3, <span>}</span><span>}</span> /> </code></pre> <h1>CSSで遊ぶって楽しい!</h1> <p>これまでも色々CSSで遊んできたので比較的サクッと実装できました。(ただし不評)</p> <ul> <li><a href="https://crieit.net/boards/web1week-202005/WEB-UI">WEB技術で複雑なUIの再現に挑戦しました</a></li> <li><a href="https://crieit.net/posts/CSS">CSSアニメーションでトランザム!</a></li> </ul> ckoshien tag:crieit.net,2005:PublicArticle/v2-1 2020-06-17T23:35:54+09:00 2020-06-26T12:58:00+09:00 https://crieit.net/boards/baseball-score-management/v2-1 キャップ野球情報局v2.0リリースしました。 <p>「<a target="_blank" rel="nofollow noopener" href="https://cap-baseball.com">キャップ野球情報局</a>」というサイトを作っています。</p> <p><a href="https://crieit.now.sh/upload_images/1013f9b2e47bbcbb5748de4b9eb9dc6f5eea2aabad3ee.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1013f9b2e47bbcbb5748de4b9eb9dc6f5eea2aabad3ee.jpg?mw=700" alt="" /></a></p> <h2>アップデート履歴</h2> <ul> <li>2019年12月 <ul> <li><a href="https://crieit.net/boards/baseball-score-management/CMS">初版(v1.0)リリース </a></li> </ul></li> <li>2020年1月 <ul> <li><a href="https://crieit.net/boards/baseball-score-management/1-2">マイナーアップデート(ver1.1)</a></li> </ul></li> </ul> <p>ちまちまとマイナーアップデートを重ねていましたが、<br /> 5/17からNextJSへ移植するのと同時に、次のメジャーアップデート(v2.0)を7月リリースを目標に作っています。</p> <h2>次バージョン新機能</h2> <p>twitterアカウントを持っていれば誰でもログインできます。</p> <h3>マイページ</h3> <p><a href="https://crieit.now.sh/upload_images/c757f121bd19245f1a30d36c3424b0625eea261eed30c.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c757f121bd19245f1a30d36c3424b0625eea261eed30c.jpg?mw=700" alt="" /></a></p> <h3>ユーザページ編集</h3> <p>別サービス「みんなのSCORE」のデータに対応する形で選手ページを持っています。</p> <h3>選手へのコメント機能</h3> <p>選手間で「この選手はどういう選手です」という他己紹介をする機能です。</p> <p><a href="https://crieit.now.sh/upload_images/c3dfcfa14baac17d4d2396730359de445eea26dde42ce.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c3dfcfa14baac17d4d2396730359de445eea26dde42ce.jpg?mw=700" alt="" /></a></p> <h3>チームページ編集</h3> <p>別サービス「みんなのSCORE」のデータに対応する形でチームページを持っています。<br /> チームの紹介のほか、チームのテーマカラーが設定できます。</p> <p><a href="https://crieit.now.sh/upload_images/99df024aaf85b0a3ce2f9748714eaef45eea271e6e978.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/99df024aaf85b0a3ce2f9748714eaef45eea271e6e978.jpg?mw=700" alt="" /></a></p> <h3>イベント登録・編集</h3> <p>キャップ野球には、主なイベントとして</p> <ul> <li>大会</li> <li>リーグ</li> <li>練習会</li> </ul> <p>がありますが、それらの情報を登録・編集することができます。</p> <p><a href="https://crieit.now.sh/upload_images/2eb69756734b1809ecdae1cbc88308c45eea276e18bd3.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2eb69756734b1809ecdae1cbc88308c45eea276e18bd3.jpg?mw=700" alt="" /></a></p> <h2>次バージョンに採用している技術</h2> <ul> <li>フロント <ul> <li>NextJS</li> <li>ReactJS</li> <li>Netlify</li> <li>TypeScript</li> </ul></li> <li>サーバサイド <ul> <li>NodeJS</li> <li>MySQL</li> <li>Docker(-compose)</li> <li>TypeScript</li> </ul></li> <li>ミドルウェアなど <ul> <li>slack</li> <li>firebase</li> <li>cloudinary</li> </ul></li> </ul> <h2>imgurではなくcloudinaryを採用した理由</h2> <p><strong>運営者ギルド</strong>では画像ストレージとしてimgurを薦められていたのですが、imgurとcloudinary両方を実装して、使いやすさの観点からcloudinaryを採用することにしました。</p> <h3>ログイン</h3> <p>imgurはOAuth認証すればログイン状態でアップロードできるのですが、ブラウザでPINが必要など使い勝手と実装に難がある印象です。</p> <h3>匿名アップロード</h3> <p>比較的実装が簡単ですが、imgurでは匿名アップロードした画像をGUIでは管理できません。</p> <p>工数的にはcloudinaryのログイン状態アップロード≒imgurの匿名アップロードという印象だったので、GUIで全体管理ができるcloudinaryを選びました。用途としては無料枠で足りると思います。</p> ckoshien tag:crieit.net,2005:PublicArticle/2-5e9dbe598bd28 2020-04-21T00:23:05+09:00 2020-04-21T00:23:05+09:00 https://crieit.net/boards/baseball-score-management/2-5e9dbe598bd28 2つのアプリケーションに共通の項目はどこに保持するのがよいか <h2>考え方の変遷</h2> <p>以前、こういう記事を書いたのですが、</p> <ul> <li><a href="https://crieit.net/posts/1626f2bc2824aa0dbd4d3bee680dc2fe">共通の設定をリポジトリ化する</a></li> </ul> <p>設定を変えるたびにnpm installしないと反映されないのが不便だったのもあり、同じDB、同じアプリケーションサーバを使っているならば、即座に反映されるDBに保持するのがよいという考えに変わりました。</p> <p>2つのアプリケーションで共通していてテキストファイルで保持していたデータは以下の2つです。</p> <ul> <li>チームの公式twitter <ul> <li>チームテーブルにカラム追加</li> </ul></li> <li>試合ごとのyoutube 動画URL <ul> <li>試合テーブルにカラム追加</li> </ul></li> </ul> <p>試合ごとのyoutube URLは自分でDB設計を変えたときに用意していたみたいですが、完全に存在を失念していました。</p> <p>特段新しい技術の導入はしてないです。</p> ckoshien tag:crieit.net,2005:PublicArticle/SCORE-ver4 2020-04-19T00:47:03+09:00 2020-04-19T00:47:03+09:00 https://crieit.net/boards/baseball-score-management/SCORE-ver4 みんなのSCOREがver4αになりました。 <p><a href="https://crieit.now.sh/upload_images/ca22bca3023945a7b3b641c937c6b8115e9b2060f01ad.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ca22bca3023945a7b3b641c937c6b8115e9b2060f01ad.jpg?mw=700" alt="" /></a></p> <h1>NodeJS側</h1> <h2>続・Twitterログイン</h2> <p>以下のパッケージを使う。<br /> - passport-twitter<br /> - twitterログインを行うライブラリ<br /> - express-session<br /> - ログイン情報をセッション(メモリ)に格納するライブラリ。<br /> - express-mysql-session<br /> - 揮発するメモリのセッション情報をmysqlに格納するライブラリ。</p> <p>reactJSからAPI(NodeJS/Express)へはcredentialsを要求するようにする。</p> <h2>データ保全の強化</h2> <p>ユーザが書き換えるテーブルに履歴テーブルを用意した。<br /> これでこのプロジェクトの合計テーブル数は21となった。</p> <h2>ユーザの権限周りの実装</h2> <p>これまで権限レベルが単一だったが、今回権限を制限したユーザを想定しているので権限周りの実装を行った。</p> <h1>ReactJS側</h1> <h2>自動入力コンポーネントの移行</h2> <p>スコア入力で大人数の中から選手を選ぶのに自動入力コンポーネントを使用している。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/moroshko/react-autosuggest">react-autosuggest</a></li> </ul> <p>react-autocompleteはどうもモバイルフレンドリーではなかったようで、モバイルでサジェストが見えなくなってしまう欠点があったので使用するコンポーネントを移行した。</p> <h2>SCSS化</h2> <p>他プロジェクトではすでに導入しているSCSSをこのプロジェクトでも導入。最も古参プロジェクトだったのでCRA(react-scripts)のバージョンを上げるなど。</p> <h1>その他</h1> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">オートサジェスト修正した <a target="_blank" rel="nofollow noopener" href="https://t.co/ZMB7IBKccB">pic.twitter.com/ZMB7IBKccB</a></p>— ckoshien/みんなのSCORE ver4α (@ckoshien_tech) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/ckoshien_tech/status/1251466561199599619?ref_src=twsrc%5Etfw">April 18, 2020</a></blockquote> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">アカウントの申請をいただくと、「チーム管理者」として招待します。チーム管理者は・新規試合登録・自チーム試合結果編集・自チームメンバー登録ができます。 <a target="_blank" rel="nofollow noopener" href="https://t.co/3TCUtyyRCz">pic.twitter.com/3TCUtyyRCz</a></p>— ckoshien/みんなのSCORE ver4α (@ckoshien_tech) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/ckoshien_tech/status/1251421645908013057?ref_src=twsrc%5Etfw">April 18, 2020</a></blockquote> ckoshien tag:crieit.net,2005:PublicArticle/SCORE-version4 2020-04-15T23:51:47+09:00 2020-04-16T13:04:41+09:00 https://crieit.net/boards/baseball-score-management/SCORE-version4 みんなのSCORE、version4開発開始 <p>長らくボードを放置していましたが、<br /> <a target="_blank" rel="nofollow noopener" href="https://jcbl-score.com">みんなのSCORE</a>のversion4に向けて開発を開始しました。</p> <h1>4/15修正内容</h1> <h2>三塁打対応</h2> <ul> <li>OPS</li> <li>長打率</li> </ul> <p>の計算を三塁打を加味したものに修正。</p> <h2>セイバーメトリクス</h2> <p>メンテナンスの都合上、セイバーメトリクスの計算をSQLからTypescript側で行うように修正。</p> <h3>修正前(SQL)</h3> <pre><code class="sql"> CASE WHEN ((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) is null THEN 0 WHEN ((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) is not null THEN ((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) END as slg, CASE WHEN ((sum(hit)+sum(four_ball))/sum(tpa))+((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) is null THEN 0 WHEN ((sum(hit)+sum(four_ball))/sum(tpa))+((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) is not null THEN ((sum(hit)+sum(four_ball))/sum(tpa))+((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) END as ops, (sum(hit)+sum(four_ball))/sum(tpa) as obp, ((((sum(hit)+sum(twobase)*1+sum(homerun)*2)+0.26*sum(four_ball)-0.03*sum(strike_out)+3*sum(tpa))*(sum(hit)+sum(four_ball)+2.4*sum(tpa)))/(9*sum(tpa))-0.9*sum(tpa))*27/(sum(at_bats)-sum(hit)) as RC27, CASE WHEN sum(at_bats)/sum(strike_out) is null THEN 0 WHEN sum(at_bats)/sum(strike_out) is not null THEN sum(at_bats)/sum(strike_out) END as not_strike_out, CASE WHEN sum(at_bats)/sum(homerun) is null THEN 0 WHEN sum(at_bats)/sum(homerun) is not null THEN sum(at_bats)/sum(homerun) END as avg_homerun, CASE WHEN sum(rbi)/sum(at_bats) is null THEN 0 WHEN sum(rbi)/sum(at_bats) is not null THEN sum(rbi)/sum(at_bats) END as avgRbi, </code></pre> <h3>修正後</h3> <pre><code class="javascript">public calcSABR = (players) => { for(let i = 0; i < players.length; i++){ players[i].average = players[i].hit/players[i].at_bats; players[i].slg = (players[i].hit + players[i].twobase*1 + players[i].three_base*2 + players[i].homerun*3 )/players[i].at_bats; players[i].obp = (players[i].hit + players[i].four_ball)/players[i].tpa; players[i].ops = players[i].slg + players[i].obp; const rc27b = players[i].slg * players[i].at_bats + 0.26 * players[i].four_ball - 0.03 * players[i].strike_out; const rc27a = players[i].slg * players[i].at_bats + players[i].four_ball; const rc27c = players[i].at_bats + players[i].four_ball; players[i].rc27 = ((rc27a + 2.4*rc27c)*(rc27b + 3*rc27c)/(9*rc27c))-(0.9 + rc27c); players[i].not_strike_out = players[i].at_bats/players[i].strike_out; players[i].avg_homerun = players[i].at_bats/players[i].homerun; players[i].avgRbi = players[i].rbi/players[i].at_bats; } return players; } </code></pre> <h1>残りのversion4 実装予定機能</h1> <h2>スコア入力一般開放</h2> <ul> <li>twitterログイン <ul> <li>先日の蓋々交換で技術調査・実装済み</li> </ul></li> <li>スコア入力画面レスポンシブ対応</li> </ul> <h3>要件</h3> <p>チーム管理者は</p> <ul> <li>自分のチームの成績のみ入力できる</li> <li>チームメンバーを追加できる</li> <li>メンバーのサジェストを自チームに限定する</li> </ul> <h2>その他バグ修正</h2> <h2>リファクタリング</h2> <h2>テストコード実装</h2> <h2>意見募集</h2> <p>みんなのSCORE、キャップ野球情報局はユーザの皆さんのご意見を募集しています。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://ikens.net/ckoshien_tech/jcbl-score?v=3">iken(みんなのSCORE)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://ikens.net/ckoshien_tech/cap-baseball-info?v=1">iken(キャップ野球情報局)</a></li> </ul> ckoshien tag:crieit.net,2005:PublicArticle/1-9 2020-01-10T23:53:59+09:00 2020-01-10T23:57:48+09:00 https://crieit.net/boards/baseball-score-management/1-9 1/9進捗 <h1>サービスURL</h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://cap-baseball.com">https://cap-baseball.com</a></li> </ul> <h1>イベント登録フォーム設置</h1> <p><a href="https://crieit.now.sh/upload_images/05f2f1dc5b343e1c8a42410b564454345e188b440a98b.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/05f2f1dc5b343e1c8a42410b564454345e188b440a98b.jpg?mw=700" alt="" /></a></p> <p>DBに下書き状態でイベント情報を登録するフォームです。<br /> bot対策にrecaptchaを採用しました。</p> <h2>reactJSでgoogle recaptchaを使う</h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/dozoisch/react-google-recaptcha">react-google-recaptcha</a></li> </ul> <h3>site keyを取得する</h3> <h3>コンポーネントを設置する</h3> <pre><code class="html"><ReCAPTCHA sitekey="Your client site key" onChange={onChange} /> </code></pre> <h1>レスポンシブ対応</h1> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">【アップデート】レスポンシブ対応完了しました。 <a target="_blank" rel="nofollow noopener" href="https://t.co/6qDO6blIwG">pic.twitter.com/6qDO6blIwG</a></p>— キャップ野球情報局/サイトリニューアル (@cap_bb_info) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/cap_bb_info/status/1215279903710445569?ref_src=twsrc%5Etfw">January 9, 2020</a></blockquote> <p>PC画面での表示を2カラムにしました。</p> ckoshien tag:crieit.net,2005:PublicArticle/1-2 2020-01-05T19:39:25+09:00 2020-01-05T19:40:37+09:00 https://crieit.net/boards/baseball-score-management/1-2 1/2 キャップ野球情報局リニューアル <p><a href="https://crieit.now.sh/upload_images/9b6e8ee098c54faa7c28b6541a9de1875e11b98e338fb.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9b6e8ee098c54faa7c28b6541a9de1875e11b98e338fb.jpg?mw=700" alt="2020.jpg" /></a><br /> 新年2本目のボード投稿になります。<br /> (新年の実績としては1本目)</p> <h1>キャップ野球情報局</h1> <p><a target="_blank" rel="nofollow noopener" href="https://cap-baseball.com/">https://cap-baseball.com/</a></p> <h1>成績管理システムとの連携</h1> <p>今回の改修の目玉は、成績管理システム「<a target="_blank" rel="nofollow noopener" href="https://jcbl-score.com">みんなのSCORE</a>」との連携です。<br /> 成績管理システムのデータベースを利用しているのでいわば兄弟サイトです。</p> <h2>試合結果の表示</h2> <p><a href="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55e11b967ba8d2.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55e11b967ba8d2.jpg?mw=700" alt="" /></a></p> <ul> <li>リーグ戦</li> <li>練習試合</li> <li>大会(大福大会、佐倉大会など)</li> </ul> <p>の結果をトップページで閲覧できるようになりました。<br /> 右下のページング部分から10件ずつ遡ることが可能です。</p> <h2>試合結果詳細</h2> <p><a href="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55e11ba59f1d65.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55e11ba59f1d65.jpg?mw=700" alt="" /></a></p> <p>試合結果とともに関連するツイートの表示と<br /> (あれば)試合動画の表示を行います。</p> <h2>チームページ</h2> <p><a href="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55e11baf99fe6d.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55e11baf99fe6d.jpg?mw=700" alt="" /></a><br /> 例:<a target="_blank" rel="nofollow noopener" href="https://cap-baseball.com/team/58">一橋大のページ</a><br /> 通算成績と直近の試合結果が表示されます。</p> <h1>使っている技術</h1> <ul> <li>ReactJS</li> <li>Netlify</li> <li>NodeJS</li> <li>MySQL</li> <li>Docker</li> <li>nginx</li> </ul> <h1>今後の課題</h1> <ul> <li>一回見ると飽きてしまう(リピーターがいない)</li> <li>ブログ記事などの集約・紹介</li> <li>コンテンツなどの充実</li> <li>個別記事とチームの紐づけ</li> </ul> ckoshien tag:crieit.net,2005:PublicArticle/6036bb73edccdcf84fbd862343577b55 2020-01-05T17:57:48+09:00 2020-01-05T17:58:23+09:00 https://crieit.net/boards/baseball-score-management/6036bb73edccdcf84fbd862343577b55 さよなら自宅サーバー <h1>経緯</h1> <p>これまで、フロントはnetlify、バックエンドは自宅サーバーという構成でサービス提供していたのですが、某アップデートの際にOSの再起動でdockerが止まって度々サービス断が発生してしまうのが段々いやになってきたのと、インフラの基礎ができてきたのでconoha VPSでインスタンス作ってサーバ移行しました。</p> <h1>サーバ構築</h1> <h2>conoha VPSでubuntuインスタンスを作る</h2> <h2>初期設定をする</h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/jqtype/items/e394fb9e027892e9a2a4">ConoHa VPS (ubuntu 16.04) 初期設定メモ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/kazokmr/items/754169cfa996b24fcbf5">SSH公開鍵認証で接続するまで</a></li> </ul> <h2>DNSの設定をする</h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.hogepon.com/original-domain-setting/#toc-4">conoha VPS+ムームードメインの特殊な設定方法</a></li> </ul> <h2>SSL証明書を発行する</h2> <p>またしてもLet's encryptに苦しめられました。今回は<code>docker-compose</code>の中の<code>nginx</code>コンテナに適用するつもりでした。調べたところ、証明書生成用のコンテナは別途あったのですが、わざわざdocker-composeの構成書き直すのも...と思ったので、<br /> nginxだけdockerコンテナを使わない構成に変更しました。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/kuribo2110/items/d725c4f866cf6e4e3727">UbuntuにNginxをインストールする</a></li> </ul> ckoshien