この記事を書いたときに96 well plateのインターフェースをjavascriptで書いてみたいと思っていたのですが、なんとなくReactのドキュメント読んで適当に書いてみたら自分でもよくわからないまま、なんか動くものができたので一旦ここに記録を残しておきたいと思います。
今この記事を書いてる時点ですでに当コードを書いてから時間が経っていて自分でもどうやって開発したかわからなくなってしまったのでコードの添付だけですが、確かcreate-react-app
を使ってどうのこうのした気がします。
正直すでに世の中にはreact-well-plates
なるものが存在するので実際にはそれを使うのが良い気がしますが、使い方がわかりません。
index.css
body { font: 14px "Century Gothic", Futura, sans-serif; margin: 20px; } ol, ul { padding-left: 30px; } .plate-row:after { clear: both; content: ""; display: table; } .well { background: #fff; border: 1px solid #999; float: left; font-size: 24px; font-weight: bold; line-height: 34px; height: 34px; margin: 2px; padding: 0; text-align: center; width: 34px; border-radius: 17px; } .selected { border: 3px solid #999; } .well:focus { outline: none; } .label { background: #fff; color: #444; border: 0px solid #999; float: left; font-size: 24px; font-weight: bold; line-height: 34px; height: 34px; margin: 2px; padding: 0; text-align: center; width: 34px; border-radius: 17px; } .label:focus { outline: none; } .kbd-navigation .well:focus { background: #ddd; } .wellplate { display: flex; flex-direction: row; user-select: none; } .wellplate-plate { border: 1px solid #999; padding: 30px; } .plate { border: 2px solid #999; padding: 10px; } .game-info { margin-left: 20px; }
index.js
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; function Well(props) { const rowLabels = ['\\', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; let className; let value; if (props.row === 0 || props.col === 0) { className = 'label'; value = props.col === 0 ? rowLabels[props.row] : props.col; } else { className = 'well' + (props.isConfirmed || props.isSelected ? " selected" : ""); } return( <button className={className} onMouseDown={props.onMouseDown} onMouseUp={props.onMouseUp} onMouseOver={props.onMouseOver} > {value} </button> ); } class Plate extends React.Component { renderWell(row, col) { const rowLabels = ['\\', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; return ( <Well key={rowLabels[row]+col.toString()} row={row} col={col} isConfirmed={this.props.isConfirmed[(row-1)*12 + (col-1)]} isSelected={this.props.isSelected[(row-1)*12 + (col-1)]} onMouseDown={(e) => this.props.onMouseDown(e, row, col)} onMouseOver={(e) => this.props.onMouseOver(e, row, col)} onClick={(e) => e.stopPropagation() } /> ); } renderRow(row) { const rowLabels = ['\\', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] return( <div key={row} className="plate-row"> { Array(13).fill(null).map((_, col) => this.renderWell(row, col)) } </div> ); } render() { return ( <div className="plate" onMouseLeave={this.props.onMouseLeave} onMouseUp={this.props.onMouseUp}> {Array(9).fill(0).map((_, row) => this.renderRow(row))} </div> ); } } class WellPlate extends React.Component { constructor(props) { super(props); this.state = { isConfirmed: Array(96).fill(false), isSelected: Array(96).fill(false), wellMouseDown: [null, null], }; } selectWells(row0, col0, row, col) { if (row0 > row) [row0, row] = [row, row0]; if (col0 > col) [col0, col] = [col, col0]; let isSelected = Array(96).fill(false); if (row0 === 0 && col0 === 0) { [row, col] = [8, 12]; } else if (row0 === 0) { row0 = 1; row = (row === 0 ? 8 : -1); } else if (col0 === 0) { col0 = 1; col = (col === 0 ? 12 : -1); } for (let r = row0-1; r < row; r++) { for (let c = col0-1; c < col; c++) { isSelected[r * 12 + c] = true; } } return isSelected; } handleMouseDown(e, row, col) { console.log(['down', row, col, e.ctrlKey]); if (!e.ctrlKey) { this.setState({ isConfirmed: Array(96).fill(false), }); } this.setState({ wellMouseDown: [row, col], }); e.stopPropagation(); } handleMouseDown2(e) { this.setState({ isConfirmed: Array(96).fill(false), }); } handleMouseUp() { const isSelected = this.state.isSelected.slice(); let isConfirmed = this.state.isConfirmed.slice(); for (let i = 0; i < 96; ++i) { isConfirmed[i] |= isSelected[i]; } this.setState({ isConfirmed: isConfirmed, wellMouseDown: [null, null], }); } handleMouseOver(e, row, col) { console.log(['over', row, col]); let row0, col0; if (this.state.wellMouseDown[0] === null && this.state.wellMouseDown[1] === null) { [row0, col0] = [row, col]; } else { [row0, col0] = this.state.wellMouseDown; } const isSelected = this.selectWells(row0, col0, row, col); this.setState({ isSelected: isSelected, }) } handleMouseLeave() { this.setState({ isSelected: Array(96).fill(false), }) } render() { const isConfirmed = this.state.isConfirmed.slice(); const isSelected = this.state.isSelected.slice(); return ( <div className="wellplate" onContextMenu={ (e) => e.preventDefault() }> <div className='wellplate-plate' onMouseDown={() => this.handleMouseDown2()}> <Plate isConfirmed={isConfirmed} isSelected={isSelected} onMouseDown={(e, row, col) => this.handleMouseDown(e, row, col)} onMouseUp={() => this.handleMouseUp()} onMouseOver={(e, row, col) => this.handleMouseOver(e, row, col)} onMouseLeave={() => this.handleMouseLeave()} onMouseDown2={() => this.handleMouseDown2()} /> </div> </div> ); } } const root = ReactDOM.createRoot(document.getElementById("root")); root.render(<WellPlate />);
実行結果