Merge pull request #151 from esSteres/leagues
fix numerical inputs in league options
This commit is contained in:
commit
121d92885a
|
@ -1,4 +1,5 @@
|
||||||
import React, {useState, useRef, useLayoutEffect, useReducer} from 'react';
|
import React, {useState, useRef, useLayoutEffect, useReducer} from 'react';
|
||||||
|
import {removeIndex, replaceIndex, append, arrayOf, shallowClone, getUID, DistributiveOmit} from './util';
|
||||||
import './CreateLeague.css';
|
import './CreateLeague.css';
|
||||||
import twemoji from 'twemoji';
|
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
|
// STRUCTURE REDUCER
|
||||||
|
|
||||||
type StructureReducerActions =
|
type StructureReducerActions =
|
||||||
|
@ -168,35 +164,6 @@ function LeagueOptionsReducer(state: LeagueOptionsState, action: OptionsReducerA
|
||||||
return newState
|
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
|
// CREATE LEAGUE
|
||||||
|
|
||||||
let initLeagueStructure = {
|
let initLeagueStructure = {
|
||||||
|
@ -323,7 +290,7 @@ function validRequest(name:string, structure: LeagueStructureState, options:Leag
|
||||||
}
|
}
|
||||||
|
|
||||||
function validNumber(value: string, min = 1) {
|
function validNumber(value: string, min = 1) {
|
||||||
return Number(value) !== NaN && Number(value) >= min
|
return !isNaN(Number(value)) && Number(value) >= min
|
||||||
}
|
}
|
||||||
|
|
||||||
// LEAGUE STRUCUTRE
|
// 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}/>
|
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) =>
|
<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}/>
|
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}/>
|
props.dispatch({type: 'set_wildcards', value: value})} showError={props.showError}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="cl_option_column">
|
<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 (
|
return (
|
||||||
<div className="cl_option_box">
|
<div className="cl_option_box">
|
||||||
<div className="cl_option_label">{props.title}</div>
|
<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)}/>
|
<input className="cl_option_input" type="number" min={minValue} 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>
|
<div className="cl_option_err">{(!isNaN(Number(props.value)) || Number(props.value) < minValue) && props.showError ? "Must be a number greater than "+minValue : ""}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
.game {
|
.game {
|
||||||
align-self: stretch;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -3,5 +3,35 @@
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
justify-content: space-around;
|
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;
|
||||||
|
}
|
|
@ -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 ReactRouter from 'react-router';
|
||||||
import {GameState, useListener} from './GamesUtil';
|
import {GameState, useListener} from './GamesUtil';
|
||||||
import './GamePage.css';
|
import './GamePage.css';
|
||||||
import Game from './Game';
|
import Game from './Game';
|
||||||
|
import {getUID} from './util';
|
||||||
|
|
||||||
function GamePage(props: ReactRouter.RouteComponentProps<{id: string}>) {
|
function GamePage(props: ReactRouter.RouteComponentProps<{id: string}>) {
|
||||||
let [games, setGames] = useState<[string, GameState][]>([]);
|
let [game, setGame] = useState<[string, GameState]|undefined>(undefined);
|
||||||
useListener((newGames) => setGames(newGames));
|
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 (
|
return (
|
||||||
<div id="game_container">
|
<div id="game_container">
|
||||||
{ game ?
|
<Game gameId={game[0]} state={game[1]}/>
|
||||||
<Game gameId={game[0]} state={game[1]}/> :
|
{ history.current.length > 0 ?
|
||||||
"The game you're looking for either doesn't exist or has already ended."
|
<GameHistory history={history.current}/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</div>
|
</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;
|
export default GamePage;
|
|
@ -76,9 +76,4 @@
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, 0);
|
transform: translate(-50%, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.emptyslot {
|
|
||||||
border: none;
|
|
||||||
min-height: 0px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ function GamesPage() {
|
||||||
let [search, setSearch] = useState(window.location.search);
|
let [search, setSearch] = useState(window.location.search);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSearch(window.location.search);
|
setSearch(window.location.search);
|
||||||
|
//eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [window.location.search])
|
}, [window.location.search])
|
||||||
|
|
||||||
let searchparams = new URLSearchParams(search);
|
let searchparams = new URLSearchParams(search);
|
||||||
|
|
|
@ -20,6 +20,8 @@ interface GameState {
|
||||||
update_text: string
|
update_text: string
|
||||||
is_league: boolean
|
is_league: boolean
|
||||||
leagueoruser: string
|
leagueoruser: string
|
||||||
|
start_delay: number
|
||||||
|
end_delay: number
|
||||||
}
|
}
|
||||||
|
|
||||||
type GameList = ([id: string, game: GameState] | null)[];
|
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('connect', () => socket.emit('recieved', {}));
|
||||||
socket.on('states_update', onUpdate);
|
socket.on('states_update', onUpdate);
|
||||||
return () => {socket.disconnect()};
|
return () => {socket.disconnect()};
|
||||||
|
//eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [url])
|
}, [url])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,14 +32,14 @@ function Header() {
|
||||||
<div id="link_div">
|
<div id="link_div">
|
||||||
<a href="https://www.patreon.com/sixteen" className="patreon_link" target="_blank" rel="noopener noreferrer">
|
<a href="https://www.patreon.com/sixteen" className="patreon_link" target="_blank" rel="noopener noreferrer">
|
||||||
<div className="patreon_container">
|
<div className="patreon_container">
|
||||||
<img className="patreon_logo" src={patreonLogo} />
|
<img className="patreon_logo" src={patreonLogo} alt="Patreon"/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/Sakimori/matteo-the-prestige" className="github_link" target="_blank" rel="noopener noreferrer">
|
<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>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=SIBR_XVI" className="twitter_link" target="_blank" rel="noopener noreferrer">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="utility_links">
|
<div id="utility_links">
|
||||||
|
|
37
simmadome/src/util.tsx
Normal file
37
simmadome/src/util.tsx
Normal 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};
|
Loading…
Reference in New Issue
Block a user