野球リーグスコア管理システムとキャップ野球情報局に関する進捗です。
旧システムについてはこちらの記事をご覧ください。
手軽に円形メニュー(Circular Menu)を提供できるライブラリ。
react-planetではリサイズイベントをResizeObseverによって検知しています。
ただし、ResizeObserverはsafari 13.1以降に対応しています。
手元にあるiPhone5Sはsafari12系でした。
react-planet
┗ use-resize-observer
┗ resize-observer-polyfill
今回、おそらく動いていないのはresize-observer-polyfillのようです。
きちんとメンテされてそうなのはこれ。
resize-observer
ただ、use-resize-observerのソースを書き換えて、
react-planetのdependencyも変えないといけないので一旦調査してエラー回避する方向で終了。
TopMenuコンポーネントの中でreact-planetを使っています。
{typeof ResizeObserver !== 'undefined'
?
<div id="topMenu">
<TopMenu/>
</div>
:<></>
}
基本的に動画から蓋速を算出する場合はコマ送りできるソフト(aviutlなど)を使ってフレーム数から到達時間を計算しているのですが、aviutlはwindows専用なので、誰でも同じように簡単に速度を算出できる仕組みがあれば便利だと思い、reactから動画を扱えるコンポーネントを使って、コマ送り機能と、リリースの瞬間とキャッチの瞬間の時間を記録すれば誰でも速度を計算できる機能を実装しました。
video-reactはtypescriptの型がないようだったので少し面倒でした。
9.22*3.6/(cTime-rTime)
9.22mを(「キャッチした時間」-「リリースした時間」)の差で割って3.6(秒速から時速への変換)をかけます。
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);
}
}}
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);
}}
>
+0.03s
</button>
<button
onClick={() => {
const player = playerRef.current.getState();
playerRef.current.seek(player.player.currentTime - 0.03);
console.log(player.player.currentTime);
}}
>
-0.03s
</button>
<button
onClick={()=>{
const player = playerRef.current.getState();
setRTime(player.player.currentTime);
}}
>リリース</button>
<button
onClick={()=>{
const player = playerRef.current.getState();
setCTime(player.player.currentTime);
}}
>キャッチ</button>
<br/>リリース:{rTime}
<br/>キャッチ:{cTime}
<br/>蓋速:{cTime && rTime && (cTime - rTime > 0) ? (9.22*3.6/(cTime-rTime)).toFixed(2)+'km/h':''}
</>
);
};
export default calcSpeed;
割と思い付きで機能を作ってしまう方なのですが、
(旧バージョンの情報局でムダとなった機能をリニューアル時に捨てた)
今回はブラウザで作れる野球選手カードについて。
画像を見てやり方に気づいた方はご名答。
角のフレームは四角形を傾けて重ねてるだけなのです。
transform 15degで15度傾けて表示しています。
親要素をrelativeにし、この要素自体をabsoluteにすると
位置調整ができます。
<div
style={{
position: "absolute",
transform: "rotate(15deg)",
bottom: -40,
right: 5,
width: 350,
height: 100,
backgroundColor: "色を指定",
}}
/>
親要素の大きさを指定して、overflow:hidden
とすると、はみ出した要素を非表示にできます。
<div
style={{
width: 340,
height:450,
backgroundColor:'#e6e6e6',
overflow: "hidden",
position: "relative",
}}
/>
div要素のbackgroundImageに画像を指定して、
border-radius 50%で丸にします。
div要素の高さはwidth,heightで指定し、
画像の大きさはbackground-sizeで調整します。
親要素をrelativeにし、この要素自体をabsoluteにすると
位置調整ができます。
<div
style={{
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,
}}
/>
これまでも色々CSSで遊んできたので比較的サクッと実装できました。(ただし不評)
以下のパッケージを使う。
- passport-twitter
- twitterログインを行うライブラリ
- express-session
- ログイン情報をセッション(メモリ)に格納するライブラリ。
- express-mysql-session
- 揮発するメモリのセッション情報をmysqlに格納するライブラリ。
reactJSからAPI(NodeJS/Express)へはcredentialsを要求するようにする。
ユーザが書き換えるテーブルに履歴テーブルを用意した。
これでこのプロジェクトの合計テーブル数は21となった。
これまで権限レベルが単一だったが、今回権限を制限したユーザを想定しているので権限周りの実装を行った。
スコア入力で大人数の中から選手を選ぶのに自動入力コンポーネントを使用している。
react-autocompleteはどうもモバイルフレンドリーではなかったようで、モバイルでサジェストが見えなくなってしまう欠点があったので使用するコンポーネントを移行した。
他プロジェクトではすでに導入しているSCSSをこのプロジェクトでも導入。最も古参プロジェクトだったのでCRA(react-scripts)のバージョンを上げるなど。
オートサジェスト修正した pic.twitter.com/ZMB7IBKccB
— ckoshien/みんなのSCORE ver4α (@ckoshien_tech) April 18, 2020
アカウントの申請をいただくと、「チーム管理者」として招待します。チーム管理者は・新規試合登録・自チーム試合結果編集・自チームメンバー登録ができます。 pic.twitter.com/3TCUtyyRCz
— ckoshien/みんなのSCORE ver4α (@ckoshien_tech) April 18, 2020
長らくボードを放置していましたが、
みんなのSCOREのversion4に向けて開発を開始しました。
の計算を三塁打を加味したものに修正。
メンテナンスの都合上、セイバーメトリクスの計算をSQLからTypescript側で行うように修正。
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,
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;
}
チーム管理者は
みんなのSCORE、キャップ野球情報局はユーザの皆さんのご意見を募集しています。
新年2本目のボード投稿になります。
(新年の実績としては1本目)
今回の改修の目玉は、成績管理システム「みんなのSCORE」との連携です。
成績管理システムのデータベースを利用しているのでいわば兄弟サイトです。
の結果をトップページで閲覧できるようになりました。
右下のページング部分から10件ずつ遡ることが可能です。
試合結果とともに関連するツイートの表示と
(あれば)試合動画の表示を行います。
例:一橋大のページ
通算成績と直近の試合結果が表示されます。
以前、7月ごろに技術ブログを作ってみたという記事を書いたのですが、結局保守が面倒ということでボツにしまして。
キーワードによる記事の絞込みが可能です。
新着記事とキーワードで探すメニューを用意しています。
主に試合結果を掲載しています。youtubeの埋め込みはmarkdownにソースを挿入するだけで問題なく動作しました。
あと、1文字目の色を変えるところに凝りました。
h1::first-letter{
color: #468847;
}
今回は記事を簡単に書けること(markdownとか)を目指して、ヘッドレスCMSを調査したのですが、コンポーネントの再利用を考えるとwordpressは除外、strapiやcontentfulも試してみたものの、データの準備に時間がかかり過ぎるので今回は見送りました。今回もDBレスで、マスタデータはjsonです。
急遽クソアプリ2 Advent Calendar 2019の1日目に参加することになりました。
計算を自動化するスプレッドシートを作っていて、やっぱりReactで書いた方が動的にスタイル変更できるしいいじゃない!と思って作りました。
試合数、チームの成績をもとにリーグ戦の優勝ラインをシミュレーションします。勝率と勝ち点方式に対応しています。
2019/12/01~
突貫で最低限の機能だけ作ったので今回はUIに凝っていないです。
https://championship.netlify.com
結果のテーブルヘッダを固定してスクロールできるようにしています。元々<table>
タグで書いていたのですが、固定しようとすると横幅が一致しない問題が出てきたので<div>
タグで初めてテーブル書きました。
div.table_header{
display: table-row;
}
div.table_cell{
display: table-cell;
border: 0.5px solid #696969;
padding: 2px;
min-width: 4rem;
text-align: right;
white-space: nowrap;
}
div.scroll{
overflow-y: scroll;
height: 80vh;
}
先日の記事でreact-slideshowを推しましたが、スワイプに対応していないという問題があったのでいくつかtouchイベントに対応しているカルーセルコンポーネントを調査しました。
react-slick、君に決めた!
会社の同僚にアイデアをもらったので自力で実装してみました。
<div style={{
padding:5
}}
>
<div className="question">掲載料金はかかりますか?</div>
<div className="answer">いいえ。料金はかかりません.</div>
<div className="question">データはどうやって登録するのですか?</div>
<div className="answer">現在、運営がデータを入力する形となっていますのでデータをお送りください。</div>
</div>
.question{
width: 70%;
position: relative;
padding: 10px;
background-color: #f2f3f7;
font-size: 16px;
color: #231815;
border-radius: 12px;
box-sizing: border-box;
margin: 5px;
}
.answer{
width: 70%;
position: relative;
left:50px;
padding: 10px;
background-color: #fde5e5;
padding: 10px;
font-size: 16px;
color: #231815;
border-radius: 12px;
box-sizing: border-box;
margin: 5px;
}
https://gist.github.com/manabuyasuda/b5c867a7cbd17d1eb905b3a8cfd621a6
今回はグリッドレイアウトを使わずにflex-boxを使いました。
幅500pxを境界に、flex-directionを切り替えるメディアクエリを書きます。flex-directionを切り替えるのと同時に画像をウインドウの50%か100%に切り替えます。
サイズの異なる画像を並べるのにobject-fit : coverが便利でした。
JSX
const imageLink = (toLink,image_url,text) => {
return(
<div
style={{
position:'relative'
}}
>
<div
style={{
fontSize: 20,
cursor:'pointer',
position:'relative'
}}
onClick={()=>{
window.location.href= toLink
}}
>
<img
style={{
height:'20vh',
objectFit:'cover'
}}
src={image_url}/>
<span
style={{
fontSize: 20,
color:'aliceblue',
textShadow:'2px 2px 2px black',
position:'absolute',
top:0,
left:0
}}
>{text}
</span>
</div>
</div>
)
}
CSS
@media(max-width:500px){
.flex-parent{
display: flex;
flex-direction: column;
}
.flex-parent img{
width: 100vw;
}
}
@media(min-width:501px){
.flex-parent{
display: flex;
flex-direction: row;
}
.flex-parent img{
width: 50vw;
}
}
参考
- Flexbox【第1回】並べる方向 〜flex-direction編〜
- 縦横比の違う画像を均等に横並びにする方法
- 画像の上におしゃれに文字やボタンをのせる方法
非公式特設サイト作りました。
先週末に千葉県佐倉市で開催されたキャップ野球の全国大会の結果をまとめたサイトをCRA(CreateReactApp)でサクッと作りました。
結局手直し含めると2.5人日ぐらいです。
テーマカラーは運営の公式twitterのロゴがこんな感じだったので
(実際は改行されているけれども)そこから採用しました。
カラーピッカーでだいたいの色コードを入れてそこから構成します。
今回はグラデーション使えるようになったので楽しくて多用しています。え、昔マーキーとかいっぱい使いませんでした?(インターネット老人会
)
ちなみに、この後さらにUIをいじっています。
どこが変わったかわかるかな?
最初は手書きしかないトーナメントをまとめるだけ...のつもりだったのですが、どうせならリーグ戦も表示させたいなーと思い、
よさそうなツールを探したのですが、あまりピンとくるものがなくて自分で書いてしまった次第。最近UI書くの楽しすぎる問題。
トーナメント結果と表彰状に個人開発のサービスを採用しています。いつもお世話になっております...!
おおにしさんの「THE TOURNAMENT」ですよね。
鉄板のあんどさんの「WEB表彰」ですよね。
体裁的にあった方がいいかなーと思ったのですが、
まあ誰も見ませんよね...!
快適に楽しく使ってもらえるのが制作者冥利に尽きるので。
最近このボードの野球リーグスコア管理システムの進捗があまり出ていません。何だかんだver2で安定しています...w
おかげでtoFixed()
とかコケます。parseFloat
(文字列を小数にパースするメソッド)とか間に挟みました。
undefの判定に! value
って書いてたらvalue
が0
で躓きましたw
今回からdockerコンテナを使い始めたのですが、小文字のテーブルを大文字でSQL書くと認識しません。やっぱりlinuxですね。
複数リーグ対応のためにUIからAPIに渡すパラメータなどを増やす必要があるので調整。
関東キャップリーグさんの試合データが公開されているので入力していきます。
1カード分探しても見つからなかったのですが。
現在右上メニューバー内から遷移できます。
チームの一覧と所属選手一覧を実装しました。materializeのchipsを使用しています。それから、チーム名・選手名検索ができるフォームの実装をしました。今回は所属チームごとの表示の振り分けをクライアント側で行うようにしました。チームの一覧を最近試合を行った順に並び替えできていないのが課題です。
フォームに何も入力されていないときは選手一覧を返すようにして、
入力された場合はAPIに検索結果を取りに行く方式にしています。
フォームがクリアされるとまた選手一覧を取得します。
ページごとにシェアするURLを動的にしたいのでまだ課題として置いておきます。
参考にした記事:Twitterシェアボタンの設置 - Qiita
ふとIE11で表示させてみたら真っ白に近かったので急遽対応しました。下記記事のbabel-polyfill入れてindex.jsでインポートする方を採用しました。
参考記事:Reactアプリを IE11 で表示すると 「オブジェクトは 'startsWith' プロパティまたはメソッドをサポートしていません。」 が発生する
1/17のボードでシーズンをプルダウンで選択する方式にしたのですが、他の箇所でアコーディオン化に成功して思いのほかよかったのでこちらもアコーディオン化しました。
ただ、苦労したのはアコーディオンの中に格納したテーブルの横幅。
テーブルの中で表示するコンテンツが多く、結局paddingで5px程度取っても横幅が足りなくなるのでfont-sizeを下げることにしました。
シーズンごとにリストアイテム化していた試合日ですが、
今回アコーディオンの中に格納しました。
コンポーネントごとにstateを持ってdisplay:noneを切り替える方法を考えていたのですが、CSSで作るとトランジションが使えて表現が豊かになるのでこちらに変えました。
参考:CSSだけでアコーディオンを作る方法
昨日の時点でheightが0になってしまってどうしようもなかった問題ですが、
1. BootstrapTableにidを設定する
BootstrapTableにはidというプロパティがないので外側のdivに設定しました。
return (
<div className='col s12'
id={this.props.id}>
<BootstrapTable
keyField={this.props.keyField}
data={this.props.data}
columns={this.props.columns} />
</div>
)
document.getElementById('trophy').clientHeight + 22
this.setState({
height:document.getElementById('trophy').clientHeight
})
style={
{ height: this.state.height}}