Merge pull request #176 from Sakimori/indev

miscellaneous fixes
This commit is contained in:
Sakimori 2021-01-23 02:24:19 -05:00 committed by GitHub
commit 318b0f52aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 205 additions and 99 deletions

1
.gitignore vendored
View file

@ -357,3 +357,4 @@ Pipfile
env
/data/leagues
/simmadome/build
/simmadome/.eslintcache

View file

@ -167,7 +167,7 @@ class league_structure(object):
if division_max % 2 != 0:
divisions.append(["OFF" for i in range(0, division_max)])
else:
last_div = divisions.pop
last_div = divisions.pop()
divs_a = list(chain(divisions[int(len(divisions)/2):]))[0]
if last_div is not None:

View file

@ -57,6 +57,7 @@ def create_league():
return jsonify({'status':'err_invalid_subleague_division_total'}), 400
league_dic = {}
all_teams = set()
err_teams = []
for subleague in config['structure']['subleagues']:
if subleague['name'] in league_dic:
@ -67,10 +68,14 @@ def create_league():
if division['name'] in subleague_dic:
return jsonify({'status':'err_duplicate_name', 'cause':f"{subleague['name']}/{division['name']}"}), 400
elif len(division['teams']) > MAX_TEAMS_PER_DIVISION:
return jsonify({'status':'err_too_many_teams', 'cause':f"{subleague['name']}/{division['name']}"})
return jsonify({'status':'err_too_many_teams', 'cause':f"{subleague['name']}/{division['name']}"}), 400
teams = []
for team_name in division['teams']:
if team_name in all_teams:
return jsonify({'status':'err_duplicate_team', 'cause':team_name}), 400
all_teams.add(team_name)
team = games.get_team(team_name)
if team is None:
err_teams.append(team_name)
@ -93,7 +98,7 @@ def create_league():
new_league = league_structure(config['name'])
new_league.setup(
league_dic,
division_games=config['division_series'], # need to add a check that makes sure these values are ok
division_games=config['division_series'],
inter_division_games=config['inter_division_series'],
inter_league_games=config['inter_league_series'],
)

View file

@ -1 +0,0 @@
[{"/Users/elijah/Documents/Projects/matteo/simmadome/src/index.tsx":"1","/Users/elijah/Documents/Projects/matteo/simmadome/src/reportWebVitals.ts":"2","/Users/elijah/Documents/Projects/matteo/simmadome/src/GamesPage.tsx":"3","/Users/elijah/Documents/Projects/matteo/simmadome/src/GamePage.tsx":"4","/Users/elijah/Documents/Projects/matteo/simmadome/src/CreateLeague.tsx":"5","/Users/elijah/Documents/Projects/matteo/simmadome/src/GamesUtil.tsx":"6","/Users/elijah/Documents/Projects/matteo/simmadome/src/util.tsx":"7","/Users/elijah/Documents/Projects/matteo/simmadome/src/Game.tsx":"8"},{"size":2368,"mtime":1610663769654,"results":"9","hashOfConfig":"10"},{"size":425,"mtime":1610566206674,"results":"11","hashOfConfig":"10"},{"size":4725,"mtime":1610664926203,"results":"12","hashOfConfig":"10"},{"size":1836,"mtime":1610677519051,"results":"13","hashOfConfig":"10"},{"size":18825,"mtime":1610778204901,"results":"14","hashOfConfig":"10"},{"size":1116,"mtime":1610677473305,"results":"15","hashOfConfig":"10"},{"size":961,"mtime":1610694553519,"results":"16","hashOfConfig":"10"},{"size":3089,"mtime":1610572714752,"results":"17","hashOfConfig":"10"},{"filePath":"18","messages":"19","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1bvn6qu",{"filePath":"20","messages":"21","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"22","messages":"23","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"24","messages":"25","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"26","messages":"27","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"28","messages":"29","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"30","messages":"31","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"32","messages":"33","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/elijah/Documents/Projects/matteo/simmadome/src/index.tsx",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/reportWebVitals.ts",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/GamesPage.tsx",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/GamePage.tsx",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/CreateLeague.tsx",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/GamesUtil.tsx",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/util.tsx",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/Game.tsx",[]]

View file

@ -16,15 +16,5 @@
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View file

@ -255,7 +255,8 @@ input[type=number]::-webkit-outer-spin-button {
.cl_structure_err_team {
margin-top: -0.5rem;
margin-bottom: 0;
width: 85%;
width: 95%;
padding-left: 0.5rem;
}
.cl_team_name_err {

View file

@ -183,18 +183,51 @@ function CreateLeague() {
let [name, setName] = useState("");
let [showError, setShowError] = useState(false);
let [nameExists, setNameExists] = useState(false);
let [deletedTeams, setDeletedTeams] = useState<string[]>([]);
let [deletedTeams, setDeletedTeams] = useState(new Set<string>());
let [createSuccess, setCreateSuccess] = useState(false);
let [structure, structureDispatch] = useReducer(leagueStructureReducer, initLeagueStructure);
let [options, optionsDispatch] = useReducer(LeagueOptionsReducer, new LeagueOptionsState());
let self = useRef<HTMLDivElement | null>(null)
let self = useRef<HTMLDivElement | null>(null);
useLayoutEffect(() => {
if (self.current) {
twemoji.parse(self.current)
}
})
});
let duplicateTeams = getDuplicateTeams(structure);
let submit = () => {
if (!validRequest(name, structure, options)) {
setShowError(true);
} else {
let req = new XMLHttpRequest();
let data = makeRequest(name, structure, options);
req.open("POST", "/api/leagues", true);
req.setRequestHeader("Content-type", "application/json");
req.onreadystatechange = () => {
if(req.readyState === 4) {
if (req.status === 200) {
setCreateSuccess(true);
}
if (req.status === 400) {
let err = JSON.parse(req.response);
switch (err.status) {
case 'err_league_exists':
setNameExists(true);
break;
case 'err_no_such_team':
setDeletedTeams(new Set(err.cause));
break;
}
setShowError(true);
}
}
}
req.send(data);
}
}
if (createSuccess) {
return(
@ -217,40 +250,21 @@ function CreateLeague() {
nameExists && showError ? "A league by that name already exists" :
""
}</div>
<LeagueStructre state={structure} dispatch={structureDispatch} deletedTeams={deletedTeams} showError={showError}/>
<LeagueStructre
state={structure}
dispatch={structureDispatch}
deletedTeams={deletedTeams}
duplicateTeams={duplicateTeams}
showError={showError}
/>
<div className="cl_league_options">
<LeagueOptions state={options} dispatch={optionsDispatch} showError={showError}/>
<LeagueOptions
state={options}
dispatch={optionsDispatch}
showError={showError}
/>
<div className="cl_option_submit_box">
<button className="cl_option_submit" onClick={e => {
if (!validRequest(name, structure, options)) {
setShowError(true);
} else {
let req = new XMLHttpRequest();
let data = makeRequest(name, structure, options);
req.open("POST", "/api/leagues", true);
req.setRequestHeader("Content-type", "application/json");
req.onreadystatechange = () => {
if(req.readyState === 4) {
if (req.status === 200) {
setCreateSuccess(true);
}
if (req.status === 400) {
let err = JSON.parse(req.response);
switch (err.status) {
case 'err_league_exists':
setNameExists(true);
break;
case 'err_no_such_team':
setDeletedTeams(err.cause);
break;
}
setShowError(true);
}
}
}
req.send(data);
}
}}>Submit</button>
<button className="cl_option_submit" onClick={submit}>Submit</button>
<div className="cl_option_err">{
!validRequest(name, structure, options) && showError ?
"Cannot create league. Some information is missing or invalid." : ""
@ -283,8 +297,6 @@ function makeRequest(name:string, structure: LeagueStructureState, options:Leagu
}
function validRequest(name:string, structure: LeagueStructureState, options:LeagueOptionsState) {
return (
name !== "" &&
@ -296,6 +308,7 @@ function validRequest(name:string, structure: LeagueStructureState, options:Leag
validNumber(options.wildcards, 0) &&
structure.subleagues.length % 2 === 0 &&
getDuplicateTeams(structure).size === 0 &&
structure.subleagues.every((subleague, si) =>
subleague.name !== "" &&
@ -314,9 +327,27 @@ function validNumber(value: string, min = 1) {
return !isNaN(Number(value)) && Number(value) >= min;
}
function getDuplicateTeams(structure: LeagueStructureState) {
return new Set(
structure.subleagues.map(subleague =>
subleague.divisions.map(division =>
division.teams.map(team => team.name)
).reduce((prev, curr) => prev.concat(curr), [])
).reduce((prev, curr) => prev.concat(curr), [])
.filter((val, i, arr) => arr.slice(0, i).indexOf(val) >= 0)
)
}
// LEAGUE STRUCUTRE
function LeagueStructre(props: {state: LeagueStructureState, dispatch: React.Dispatch<StructureReducerActions>, deletedTeams: string[], showError: boolean}) {
function LeagueStructre(props: {
state: LeagueStructureState,
dispatch: React.Dispatch<StructureReducerActions>,
deletedTeams: Set<string>,
duplicateTeams: Set<string>,
showError: boolean
}) {
let nSubleagues = props.state.subleagues.length;
let nDivisions = props.state.subleagues[0].divisions.length;
return (
@ -324,8 +355,18 @@ function LeagueStructre(props: {state: LeagueStructureState, dispatch: React.Dis
<div className="cl_league_structure_scrollbox">
<div className="cl_subleague_add_align">
<div className="cl_league_structure_table">
<SubleagueHeaders subleagues={props.state.subleagues} dispatch={props.dispatch} showError={props.showError}/>
<Divisions subleagues={props.state.subleagues} dispatch={props.dispatch} deletedTeams={props.deletedTeams} showError={props.showError}/>
<SubleagueHeaders
subleagues={props.state.subleagues}
dispatch={props.dispatch}
showError={props.showError}
/>
<Divisions
subleagues={props.state.subleagues}
dispatch={props.dispatch}
deletedTeams={props.deletedTeams}
duplicateTeams={props.duplicateTeams}
showError={props.showError}
/>
</div>
{ (nSubleagues+1) * (nDivisions+1) < MAX_SUBLEAGUE_DIVISION_TOTAL ?
<button className="cl_subleague_add" onClick={e => props.dispatch({type: 'add_subleague'})}></button> :
@ -380,7 +421,14 @@ function SubleageHeader(props: {state: SubleagueState, canDelete: boolean, dispa
);
}
function Divisions(props: {subleagues: SubleagueState[], dispatch: React.Dispatch<StructureReducerActions>, deletedTeams: string[], showError: boolean}) {
function Divisions(props: {
subleagues: SubleagueState[],
dispatch: React.Dispatch<StructureReducerActions>,
deletedTeams: Set<string>,
duplicateTeams: Set<string>,
showError: boolean
}) {
return (<>
{props.subleagues[0].divisions.map((val, di) => (
<div key={val.id} className="cl_table_row">
@ -393,11 +441,16 @@ function Divisions(props: {subleagues: SubleagueState[], dispatch: React.Dispatc
{props.subleagues.map((subleague, si) => (
<div key={subleague.id} className="cl_division_cell">
<div className="cl_subleague_bg">
<Division state={subleague.divisions[di]} dispatch={action =>
props.dispatch(Object.assign({subleague_index: si, division_index: di}, action))
}
isDuplicate={subleague.divisions.slice(0, di).some(val => val.name === subleague.divisions[di].name)}
deletedTeams={props.deletedTeams} showError={props.showError} />
<Division
state={subleague.divisions[di]}
dispatch={action =>
props.dispatch(Object.assign({subleague_index: si, division_index: di}, action))
}
isDuplicate={subleague.divisions.slice(0, di).some(val => val.name === subleague.divisions[di].name)}
deletedTeams={props.deletedTeams}
duplicateTeams={props.duplicateTeams}
showError={props.showError}
/>
</div>
</div>
))}
@ -410,7 +463,8 @@ function Division(props: {
state: DivisionState,
dispatch: (action: DistributiveOmit<StructureReducerActions, 'subleague_index'|'division_index'>) => void,
isDuplicate: boolean,
deletedTeams: string[],
deletedTeams: Set<string>,
duplicateTeams: Set<string>,
showError: boolean
}) {
@ -442,24 +496,27 @@ function Division(props: {
}/>
<div className="cl_structure_err cl_structure_err_div">{props.showError ? divisionErr : ""}</div>
</div>
{props.state.teams.map((team, i) => {
let showDeleted = props.showError && props.deletedTeams.includes(team.name)
return (<>
<div className="cl_team" key={team.id}>
<div className={"cl_team_name" + (showDeleted ? " cl_team_name_err" : "")}>{team.name}</div>
<button className="cl_team_delete" onClick={e => props.dispatch({type:'remove_team', name: team.name})}></button>
</div>
<div className="cl_structure_err cl_structure_err_team">{showDeleted ? "This team was deleted" : ""}</div>
</>)
})}
{ props.state.teams.length < MAX_TEAMS_PER_DIVISION ? <>
{props.state.teams.map((team, i) => (
<Team key={team.id}
state={team}
dispatch={props.dispatch}
isDuplicate={props.duplicateTeams.has(team.name)}
isDeleted={props.deletedTeams.has(team.name)}
showError={props.showError}
/>
))}
{props.state.teams.length < MAX_TEAMS_PER_DIVISION ? <>
<div className="cl_team_add">
<input type="text" className="cl_newteam_name" placeholder="Add team..." value={newName} ref={newNameInput}
onChange={e => {
let params = new URLSearchParams({query: e.target.value, page_len: '5', page_num: '0'});
fetch("/api/teams/search?" + params.toString())
.then(response => response.json())
.then(data => setSearchResults(data));
if (e.target.value === "") {
setSearchResults([]);
} else {
let params = new URLSearchParams({query: e.target.value, page_len: '5', page_num: '0'});
fetch("/api/teams/search?" + params.toString())
.then(response => response.json())
.then(data => setSearchResults(data));
}
setNewName(e.target.value);
}}/>
</div>
@ -485,6 +542,30 @@ function Division(props: {
);
}
function Team(props: {
state: TeamState,
dispatch: (action: DistributiveOmit<StructureReducerActions, 'subleague_index'|'division_index'>) => void,
isDuplicate: boolean,
isDeleted: boolean,
showError: boolean
}) {
let errMsg =
props.isDeleted ?
"This team was deleted" :
props.isDuplicate ?
"Each team in a league must be unique" :
"";
return (<>
<div className="cl_team">
<div className={"cl_team_name" + (errMsg && props.showError ? " cl_team_name_err" : "")}>{props.state.name}</div>
<button className="cl_team_delete" onClick={e => props.dispatch({type:'remove_team', name: props.state.name})}></button>
</div>
<div className="cl_structure_err cl_structure_err_team">{props.showError ? errMsg : ""}</div>
</>);
}
// LEAGUE OPTIONS
function LeagueOptions(props: {state: LeagueOptionsState, dispatch: React.Dispatch<OptionsReducerActions>, showError: boolean}) {

View file

@ -171,6 +171,8 @@
.update_text {
text-align: start;
margin-top: 0.25rem;
margin-bottom: 0.25rem;
}
.field {

View file

@ -70,10 +70,5 @@
@media only screen and (max-device-width: 800px) {
.container {
display: grid;
grid-template-columns: repeat(1, minmax(700px, 90%));
position: absolute;
left: 50%;
transform: translate(-50%, 0);
}
}

View file

@ -5,23 +5,29 @@ import './GamesPage.css';
import Game from './Game';
function GamesPage() {
let gameList = useRef<(string | null)[]>([]);
let [search, setSearch] = useState(window.location.search);
useEffect(() => {
setSearch(window.location.search);
gameList.current = [];
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [window.location.search])
// get filter term
let searchparams = new URLSearchParams(search);
let filter = searchparams.get('league') ?? ""
// set up socket listener
let [games, setGames] = useState<[string, GameState][]>([]);
useListener(setGames);
// build filter list
let filters = useRef(filter !== "" ? [filter] : []);
games.forEach((game) => { if (game[1].is_league && !filters.current.includes(game[1].leagueoruser)) { filters.current.push(game[1].leagueoruser) }});
filters.current = filters.current.filter((f) => games.find((game) => game && game[1].is_league && game[1].leagueoruser === f) || f === filter);
let gameList = useRef<(string | null)[]>([]);
// update game list
let filterGames = games.filter((game, i) => filter === "" || game[1].leagueoruser === filter);
updateList(gameList.current, filterGames, searchparams.get('game'));

View file

@ -55,6 +55,12 @@ h2 {
margin: auto;
}
#links {
display: flex;
width: 100%;
justify-content: space-between;
}
#link_div {
text-align: right;
position: absolute;
@ -63,6 +69,12 @@ h2 {
display: flex;
}
#nav_links {
position: absolute;
top: 1rem;
left: 2rem;
}
.github_logo, .twitter_logo, .patreon_container {
height: 2rem;
width: 2rem;
@ -97,15 +109,18 @@ a:hover {
color: white;
}
#utility_links {
position: absolute;
top: 1rem;
left: 2rem;
}
img.emoji {
height: 1em;
width: 1em;
margin: 0 .05em 0 .1em;
vertical-align: -0.1em;
}
@media only screen and (max-width: 800px) {
#link_div, #nav_links {
position: static;
margin-top: 0.5rem;
margin-left: 2rem;
margin-right: 2rem;
}
}

View file

@ -29,6 +29,10 @@ ReactDOM.render(
function Header() {
return (
<div id="header">
<div id="links">
<div id="nav_links">
<Link to="/create_league">Create a League</Link>
</div>
<div id="link_div">
<a href="https://www.patreon.com/sixteen" className="patreon_link" target="_blank" rel="noopener noreferrer">
<div className="patreon_container">
@ -42,11 +46,9 @@ function Header() {
<img className="twitter_logo" src={twitterLogo} alt="Twitter"/>
</a>
</div>
<div id="utility_links">
<Link to="/create_league">Create a League</Link>
</div>
<a href="/" className="page_header"><h2 className="page_header" style={{fontSize:"50px"} as React.CSSProperties}>THE SIMMADOME</h2></a>
<h2 className="page_header">Join SIBR on <a href="https://discord.gg/UhAajY2NCW" className="link"><img src={discordlogo} alt="" height="30"/></a> to start your own games!</h2>
</div>
<a href="/" className="page_header"><h2 className="page_header" style={{fontSize:"50px"} as React.CSSProperties}>THE SIMMADOME</h2></a>
<h2 className="page_header">Join SIBR on <a href="https://discord.gg/UhAajY2NCW" className="link"><img src={discordlogo} alt="" height="30"/></a> to start your own games!</h2>
</div>
);
}

View file

@ -997,6 +997,7 @@ client = discord.Client()
gamesarray = []
active_tournaments = []
active_leagues = []
active_standings = {}
setupmessages = {}
thread1 = threading.Thread(target=main_controller.update_loop)
@ -1028,7 +1029,7 @@ def config():
@client.event
async def on_ready():
db.initialcheck()
print(f"logged in as {client.user} with token {config()['token']}")
print(f"logged in as {client.user} with token {config()['token']} to {len(client.guilds)} servers")
watch_task = asyncio.create_task(game_watcher())
await watch_task
@ -1827,9 +1828,11 @@ async def league_day_watcher(channel, league, games_list, filter_url, last = Fal
wait_seconds = (next_start - now).seconds
leagues.save_league(league)
await channel.send(embed=league.standings_embed())
if league in active_standings.keys():
await active_standings[league].unpin()
active_standings[league] = await channel.send(embed=league.standings_embed())
active_standings[league].pin()
await channel.send(f"The day {league.day} games for the {league.name} will start in {math.ceil(wait_seconds/60)} minutes.")
leagues.save_league(league)
await asyncio.sleep(wait_seconds)
await channel.send(f"A {league.name} series is continuing now at {filter_url}")
games_list = await continue_league_series(league, queued_games, games_list, series_results, missed)
@ -1837,7 +1840,10 @@ async def league_day_watcher(channel, league, games_list, filter_url, last = Fal
league.active = False
if league.autoplay == 0 or config()["game_freeze"]: #if number of series to autoplay has been reached
await channel.send(embed=league.standings_embed())
if league in active_standings.keys():
await active_standings[league].unpin()
active_standings[league] = await channel.send(embed=league.standings_embed())
active_standings[league].pin()
await channel.send(f"The {league.name} is no longer autoplaying.")
if config()["game_freeze"]:
await channel.send("Patch incoming.")
@ -1901,7 +1907,10 @@ async def league_day_watcher(channel, league, games_list, filter_url, last = Fal
wait_seconds = (next_start - now).seconds
leagues.save_league(league)
await channel.send(embed=league.standings_embed())
if league in active_standings.keys():
await active_standings[league].unpin()
active_standings[league] = await channel.send(embed=league.standings_embed())
active_standings[league].pin()
await channel.send(f"""This {league.name} series is now complete! The next series will be starting in {int(wait_seconds/60)} minutes.""")
await asyncio.sleep(wait_seconds)