make league structure
This commit is contained in:
parent
a9cd89d5ce
commit
72edbf5022
177
simmadome/src/CreateLeague.css
Normal file
177
simmadome/src/CreateLeague.css
Normal 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;
|
||||||
|
}
|
250
simmadome/src/CreateLeague.tsx
Normal file
250
simmadome/src/CreateLeague.tsx
Normal 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;
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user