make league structure

This commit is contained in:
Elijah Steres 2021-01-10 01:27:30 -05:00
parent a9cd89d5ce
commit 72edbf5022
3 changed files with 429 additions and 0 deletions

View File

@ -0,0 +1,177 @@
th, td {
border: none;
padding: 0;
height: 100%;
}
table {
border-collapse: collapse;
height: min-content;
}
th .cl_subleague_bg {
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
}
tbody tr:last-child .cl_subleague_bg {
border-bottom-left-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
}
input {
border: none;
border-radius: 1rem;
height: 2rem;
padding-left: 1rem;
background: var(--background-secondary);
font-size: 14pt;
}
.cl_league_main {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
margin-top: 3rem;
}
.cl_league_name {
margin: 1rem;
}
.cl_league_options, .cl_league_structure {
display: flex;
background: var(--background-tertiary);
flex-direction: column;
max-width: 100%;
border-radius: 1rem;
padding-top: 1.5rem;
}
.cl_league_structure, .cl_subleague_add_align {
display: flex;
align-items: center;
justify-content: center;
width: min-content;
}
.cl_league_structure_table {
margin: 1rem;
margin-left: 0rem;
margin-top: 0rem;
}
.cl_league_structure_scrollbox {
max-width: 100%;
overflow-y: scroll;
}
/* Hide scrollbar for Chrome, Safari and Opera */
.cl_league_structure_scrollbox::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.cl_league_structure_scrollbox {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.cl_subleague_add_align{
margin-left: 1.5rem;
padding-right: 1.5rem;
}
.cl_subleague_header {
display: flex;
width:100%;
align-items: center;
justify-content: space-between;
}
.cl_subleague_bg {
background: var(--background-main);
padding:1rem;
padding-bottom: 0rem;
margin: 0rem 0.5rem;
height: 100%;
width: 20rem;
}
.cl_subleague_name, .cl_newteam_name {
flex-grow: 1;
margin-right: 0.5rem;
}
.cl_division_name {
margin-bottom: 0.5rem;
width: 95%;
}
.cl_division {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
border-radius: 0.5rem;
background: var(--background-accent);
}
.cl_team, .cl_team_add {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin: 0.25rem 0rem;
}
.cl_team_name {
font-size: 14pt;
padding-right: 0.5rem;
overflow: hidden;
text-overflow: ellipsis;
}
/* button styles */
button > .emoji {
margin: 0;
}
.cl_subleague_delete, .cl_team_delete, .cl_division_delete, .cl_subleague_add, .cl_division_add, .cl_newteam_add {
padding: 0;
width: 2rem;
height: 2rem;
border: none;
border-radius: 1rem;
display: flex;
align-items: center;
justify-content: center;
}
.cl_subleague_delete, .cl_team_delete, .cl_division_delete {
background: var(--accent-red);
}
.cl_subleague_add, .cl_division_add, .cl_newteam_add {
background: var(--accent-green);
}
.cl_subleague_add {
margin-top: 3rem;
}
.cl_division_add {
margin: 1.25rem;
}
.cl_division_delete {
margin-right: 1rem;
margin-top: 1rem;
}
.cl_delete_filler {
min-width: 3rem;
}

View File

@ -0,0 +1,250 @@
import React, {useState, useRef, useLayoutEffect, useReducer} from 'react';
import './CreateLeague.css';
import twemoji from 'twemoji';
interface LeagueStructureState {
subleagues: SubleagueState[]
}
interface SubleagueState {
name: string
divisions: DivisionState[]
}
interface DivisionState {
name: string
teams: TeamState[]
}
interface TeamState {
name: string
}
let initLeagueStructure = {
subleagues: [0, 1].map((val) => ({
name: "",
divisions: [0, 1].map((val) => ({
name: "",
teams: []
}))
}))
}
type LeagueReducerActions =
{type: 'remove_subleague', subleague_index: number} |
{type: 'add_subleague'} |
{type: 'rename_subleague', subleague_index: number, name: string} |
{type: 'remove_divisions', division_index: number} |
{type: 'add_divisions'} |
{type: 'rename_division', subleague_index: number, division_index: number, name: string} |
{type: 'remove_team', subleague_index: number, division_index: number, name:string} |
{type: 'add_team', subleague_index:number, division_index:number, name:string}
type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
function leagueStructureReducer(state: LeagueStructureState, action: LeagueReducerActions): LeagueStructureState {
switch (action.type) {
case 'remove_subleague':
return {subleagues: removeIndex(state.subleagues, action.subleague_index)};
case 'add_subleague':
return {subleagues: state.subleagues.concat([{
name: "",
divisions: arrayOf(state.subleagues[0].divisions.length, i => ({
name: "",
teams: []
}))
}])};
case 'rename_subleague':
return replaceSubleague(state, action.subleague_index, subleague => ({
name: action.name,
divisions: subleague.divisions
}));
case 'remove_divisions':
return {subleagues: state.subleagues.map(subleague => ({
name: subleague.name,
divisions: removeIndex(subleague.divisions, action.division_index)
}))};
case 'add_divisions':
return {subleagues: state.subleagues.map(subleague => ({
name: subleague.name,
divisions: subleague.divisions.concat([{name: "", teams: []}])
}))};
case 'rename_division':
return replaceDivision(state, action.subleague_index, action.division_index, division => ({
name: action.name,
teams: division.teams
}));
case 'remove_team':
return replaceDivision(state, action.subleague_index, action.division_index, division => ({
name: division.name,
teams: removeIndex(division.teams, division.teams.findIndex(val => val.name === action.name))
}));
case 'add_team':
return replaceDivision(state, action.subleague_index, action.division_index, division => ({
name: division.name,
teams: division.teams.concat([{name: action.name}])
}));
}
}
function replaceSubleague(state: LeagueStructureState, si: number, func: (val: SubleagueState) => SubleagueState) {
return {subleagues: replaceIndex(state.subleagues, si, func(state.subleagues[si]))}
}
function replaceDivision(state: LeagueStructureState, si: number, di: number, func:(val: DivisionState) => DivisionState) {
return replaceSubleague(state, si, subleague => ({
name: subleague.name,
divisions: replaceIndex(subleague.divisions, di, func(subleague.divisions[di]))
}))
}
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 CreateLeague() {
let [name, setName] = useState("");
let [structure, dispatch] = useReducer(leagueStructureReducer, initLeagueStructure);
let self = useRef<HTMLDivElement | null>(null)
useLayoutEffect(() => {
if (self.current) {
twemoji.parse(self.current)
}
})
return (
<div className="cl_league_main" ref={self}>
<input type="text" className="cl_league_name" placeholder="League Name" value={name} onChange={(e) => setName(e.target.value)}/>
<LeagueStructre state={structure} dispatch={dispatch}/>
<LeagueOptions />
</div>
);
}
function LeagueStructre(props: {state: LeagueStructureState, dispatch: React.Dispatch<LeagueReducerActions>}) {
return (
<div className="cl_league_structure">
<div className="cl_league_structure_scrollbox">
<div className="cl_subleague_add_align">
<table className="cl_league_structure_table">
<SubleagueHeaders subleagues={props.state.subleagues} dispatch={props.dispatch} />
<Divisions subleagues={props.state.subleagues} dispatch={props.dispatch} />
</table>
<button className="cl_subleague_add" onClick={e => props.dispatch({type: 'add_subleague'})}></button>
</div>
</div>
<button className="cl_division_add" onClick={e => props.dispatch({type: 'add_divisions'})}></button>
</div>
);
}
function SubleagueHeaders(props: {subleagues: SubleagueState[], dispatch: React.Dispatch<LeagueReducerActions>}) {
return (
<thead>
<tr className="cl_headers">
<th key="filler" className="cl_delete_filler"/>
{props.subleagues.map((subleague, i) => (
<th key={i}>
<div className="cl_subleague_bg">
<SubleageHeader state={subleague} canDelete={props.subleagues.length > 1} dispatch={action =>
props.dispatch(Object.assign({subleague_index: i}, action))
}/>
</div>
</th>
))}
</tr>
</thead>
);
}
function SubleageHeader(props: {state: SubleagueState, canDelete: boolean, dispatch:(action: DistributiveOmit<LeagueReducerActions, 'subleague_index'>) => void}) {
return (
<div className="cl_subleague_header">
<input type="text" className="cl_subleague_name" placeholder="Subleague Name" value={props.state.name} onChange={e =>
props.dispatch({type: 'rename_subleague', name: e.target.value})
}/>
{props.canDelete ? <button className="cl_subleague_delete" onClick={e => props.dispatch({type: 'remove_subleague'})}></button> : null}
</div>
);
}
function Divisions(props: {subleagues: SubleagueState[], dispatch: React.Dispatch<LeagueReducerActions>}) {
return (
<tbody>
{props.subleagues[0].divisions.map((val, di) => (
<tr key={di}>
<td key="delete" className="cl_delete_box">
{props.subleagues[0].divisions.length > 1 ?
<button className="cl_division_delete" onClick={e => props.dispatch({type: 'remove_divisions', division_index: di})}></button> :
null
}
</td>
{props.subleagues.map((subleague, si) => (
<td key={si}>
<div className="cl_subleague_bg">
<Division state={subleague.divisions[di]} dispatch={action =>
props.dispatch(Object.assign({subleague_index: si, division_index: di}, action))
}/>
</div>
</td>
))}
</tr>
))}
</tbody>
);
}
function Division(props: {state: DivisionState, dispatch:(action: DistributiveOmit<LeagueReducerActions, 'subleague_index'|'division_index'>) => void}) {
let [newName, setNewName] = useState("");
return (
<div className="cl_division">
<input type="text" className="cl_division_name" placeholder="Division Name" key="input" value={props.state.name} onChange={e =>
props.dispatch({type: 'rename_division', name: e.target.value})
}/>
{props.state.teams.map((team, i) => (
<div className="cl_team" key={i}>
<div className="cl_team_name">{team.name}</div>
<button className="cl_team_delete" onClick={e => props.dispatch({type:'remove_team', name: team.name})}></button>
</div>
))}
<div className="cl_team_add">
<input type="text" className="cl_newteam_name" placeholder="Add team..." value={newName} onChange={e => setNewName(e.target.value)}/>
<button className="cl_newteam_add" onClick={e => {if (newName.length > 0) {
props.dispatch({type: 'add_team', name: newName});
setNewName("");
}}}></button>
</div>
</div>
);
}
function LeagueOptions() {
return (
<div className="cl_league_options">
<form>
</form>
</div>
);
}
export default CreateLeague;

View File

@ -4,6 +4,7 @@ import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import './index.css'; import './index.css';
import GamesPage from './GamesPage'; import GamesPage from './GamesPage';
import GamePage from './GamePage'; import GamePage from './GamePage';
import CreateLeague from './CreateLeague';
import discordlogo from "./img/discord.png"; import discordlogo from "./img/discord.png";
import reportWebVitals from './reportWebVitals'; import reportWebVitals from './reportWebVitals';
@ -13,6 +14,7 @@ ReactDOM.render(
<Header /> <Header />
<Switch> <Switch>
<Route path="/game/:id" component={GamePage}/> <Route path="/game/:id" component={GamePage}/>
<Route path="/create_league" component={CreateLeague} />
<Route path="/" component={GamesPage}/> <Route path="/" component={GamesPage}/>
</Switch> </Switch>
</Router> </Router>