Merge pull request #151 from esSteres/leagues

fix numerical inputs in league options
This commit is contained in:
Sakimori 2021-01-14 23:39:31 -05:00 committed by GitHub
commit 121d92885a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 131 additions and 55 deletions

View File

@ -1,4 +1,5 @@
import React, {useState, useRef, useLayoutEffect, useReducer} from 'react';
import {removeIndex, replaceIndex, append, arrayOf, shallowClone, getUID, DistributiveOmit} from './util';
import './CreateLeague.css';
import twemoji from 'twemoji';
@ -46,11 +47,6 @@ class TeamState {
}
}
let getUID = function() { // does NOT generate UUIDs. Meant to create list keys ONLY
let id = 0;
return function() { return id++ }
}()
// STRUCTURE REDUCER
type StructureReducerActions =
@ -168,35 +164,6 @@ function LeagueOptionsReducer(state: LeagueOptionsState, action: OptionsReducerA
return newState
}
// UTIL
function removeIndex(arr: any[], index: number) {
return arr.slice(0, index).concat(arr.slice(index+1));
}
function replaceIndex<T>(arr: T[], index: number, val: T) {
return arr.slice(0, index).concat([val]).concat(arr.slice(index+1));
}
function append<T>(arr: T[], val: T) {
return arr.concat([val]);
}
function arrayOf<T>(length: number, func: (i: number) => T): T[] {
var out: T[] = [];
for (var i = 0; i < length; i++) {
out.push(func(i));
}
return out;
}
function shallowClone<T>(obj: T): T {
return Object.assign({}, obj);
}
type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
type DistributivePick<T, K extends keyof T> = T extends any ? Pick<T, K> : never;
// CREATE LEAGUE
let initLeagueStructure = {
@ -323,7 +290,7 @@ function validRequest(name:string, structure: LeagueStructureState, options:Leag
}
function validNumber(value: string, min = 1) {
return Number(value) !== NaN && Number(value) >= min
return !isNaN(Number(value)) && Number(value) >= min
}
// LEAGUE STRUCUTRE
@ -464,7 +431,7 @@ function LeagueOptions(props: {state: LeagueOptionsState, dispatch: React.Dispat
props.dispatch({type: 'set_games_series', value: value})} showError={props.showError}/>
<NumberInput title="Number of teams from top of division to postseason" value={props.state.top_postseason} setValue={(value: string) =>
props.dispatch({type: 'set_top_postseason', value: value})} showError={props.showError}/>
<NumberInput title="Number of wildcards" value={props.state.wildcards} setValue={(value: string) =>
<NumberInput title="Number of wildcards" value={props.state.wildcards} minValue={0} setValue={(value: string) =>
props.dispatch({type: 'set_wildcards', value: value})} showError={props.showError}/>
</div>
<div className="cl_option_column">
@ -479,12 +446,16 @@ function LeagueOptions(props: {state: LeagueOptionsState, dispatch: React.Dispat
);
}
function NumberInput(props: {title: string, value: string, setValue: (newVal: string) => void, showError: boolean}) {
function NumberInput(props: {title: string, value: string, setValue: (newVal: string) => void, showError: boolean, minValue?:number}) {
let minValue = 1;
if (props.minValue !== undefined) {
minValue = props.minValue
}
return (
<div className="cl_option_box">
<div className="cl_option_label">{props.title}</div>
<input className="cl_option_input" type="number" min="0" value={props.value} onChange={e => props.setValue(e.target.value)}/>
<div className="cl_option_err">{(Number(props.value) === NaN || Number(props.value) < 0) && props.showError ? "Must be a number greater than 0" : ""}</div>
<input className="cl_option_input" type="number" min={minValue} value={props.value} onChange={e => props.setValue(e.target.value)}/>
<div className="cl_option_err">{(!isNaN(Number(props.value)) || Number(props.value) < minValue) && props.showError ? "Must be a number greater than "+minValue : ""}</div>
</div>
);
}

View File

@ -1,5 +1,4 @@
.game {
align-self: stretch;
text-align: center;
display: flex;
flex-direction: column;

View File

@ -3,5 +3,35 @@
margin-left: 1rem;
margin-right: 1rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
}
.history_box {
width: 100%;
margin-top: 3rem;
padding: 1rem;
padding-top: 0.5rem;
background: var(--background-main);
border-radius: 0.25rem;
width: 100%;
min-width: 32rem;
max-width: 44rem;
box-sizing: border-box;
border: 4px solid;
border-radius: 4px;
border-color: var(--highlight);
border-top: none;
border-right: none;
border-bottom: none;
}
.history_title {
font-size: 14pt;
}
.history_update {
height: 4rem;
margin: 0.5rem;
}

View File

@ -1,22 +1,62 @@
import React, {useState} from 'react';
import React, {useState, useRef, useLayoutEffect} from 'react';
import twemoji from 'twemoji';
import ReactRouter from 'react-router';
import {GameState, useListener} from './GamesUtil';
import './GamePage.css';
import Game from './Game';
import {getUID} from './util';
function GamePage(props: ReactRouter.RouteComponentProps<{id: string}>) {
let [games, setGames] = useState<[string, GameState][]>([]);
useListener((newGames) => setGames(newGames));
let [game, setGame] = useState<[string, GameState]|undefined>(undefined);
let history = useRef<[number, string, string][]>([]);
useListener((newGames) => {
let newGame = newGames.find((gamePair) => gamePair[0] === props.match.params.id);
setGame(newGame);
console.log(newGame);
if (newGame !== undefined && newGame[1].start_delay < 0 && newGame[1].end_delay > 8) {
history.current.unshift([getUID(), newGame[1].update_emoji, newGame[1].update_text]);
if (history.current.length > 8) {
history.current.pop();
}
}
});
if (game === undefined) {
return <div id="game_container">The game you're looking for either doesn't exist or has already ended.</div>
}
let game = games.find((game) => game[0] === props.match.params.id)
return (
<div id="game_container">
{ game ?
<Game gameId={game[0]} state={game[1]}/> :
"The game you're looking for either doesn't exist or has already ended."
<Game gameId={game[0]} state={game[1]}/>
{ history.current.length > 0 ?
<GameHistory history={history.current}/> :
null
}
</div>
);
}
function GameHistory(props: {history: [number, string, string][]}) {
let self = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
if (self.current) {
twemoji.parse(self.current);
}
})
return (
<div className="history_box" ref={self}>
<div className="history_title">History</div>
{props.history.map((update) => (
<div className="update history_update" key={update[0]}>
<div className="update_emoji">{update[1]}</div>
<div className="update_text">{update[2]}</div>
</div>
))}
</div>
);
}
export default GamePage;

View File

@ -76,9 +76,4 @@
left: 50%;
transform: translate(-50%, 0);
}
.emptyslot {
border: none;
min-height: 0px;
}
}

View File

@ -8,6 +8,7 @@ function GamesPage() {
let [search, setSearch] = useState(window.location.search);
useEffect(() => {
setSearch(window.location.search);
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [window.location.search])
let searchparams = new URLSearchParams(search);

View File

@ -20,6 +20,8 @@ interface GameState {
update_text: string
is_league: boolean
leagueoruser: string
start_delay: number
end_delay: number
}
type GameList = ([id: string, game: GameState] | null)[];
@ -32,6 +34,7 @@ const useListener = (onUpdate: (update: [string, GameState][]) => void, url: str
socket.on('connect', () => socket.emit('recieved', {}));
socket.on('states_update', onUpdate);
return () => {socket.disconnect()};
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [url])
}

View File

@ -32,14 +32,14 @@ function Header() {
<div id="link_div">
<a href="https://www.patreon.com/sixteen" className="patreon_link" target="_blank" rel="noopener noreferrer">
<div className="patreon_container">
<img className="patreon_logo" src={patreonLogo} />
<img className="patreon_logo" src={patreonLogo} alt="Patreon"/>
</div>
</a>
<a href="https://github.com/Sakimori/matteo-the-prestige" className="github_link" target="_blank" rel="noopener noreferrer">
<img className="github_logo" src={githubLogo} />
<img className="github_logo" src={githubLogo} alt="Github"/>
</a>
<a href="https://twitter.com/intent/follow?screen_name=SIBR_XVI" className="twitter_link" target="_blank" rel="noopener noreferrer">
<img className="twitter_logo" src={twitterLogo} />
<img className="twitter_logo" src={twitterLogo} alt="Twitter"/>
</a>
</div>
<div id="utility_links">

37
simmadome/src/util.tsx Normal file
View File

@ -0,0 +1,37 @@
import {useRef} from 'react';
function removeIndex(arr: any[], index: number) {
return arr.slice(0, index).concat(arr.slice(index+1));
}
function replaceIndex<T>(arr: T[], index: number, val: T) {
return arr.slice(0, index).concat([val]).concat(arr.slice(index+1));
}
function append<T>(arr: T[], val: T) {
return arr.concat([val]);
}
function arrayOf<T>(length: number, func: (i: number) => T): T[] {
var out: T[] = [];
for (var i = 0; i < length; i++) {
out.push(func(i));
}
return out;
}
function shallowClone<T>(obj: T): T {
return Object.assign({}, obj);
}
let getUID = function() { // does NOT generate UUIDs. Meant to create list keys ONLY
let id = 0;
return function() { return id++ }
}()
type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
//type DistributivePick<T, K extends keyof T> = T extends any ? Pick<T, K> : never;
export {removeIndex, replaceIndex, append, arrayOf, shallowClone, getUID};
export type {DistributiveOmit};