sim16/simmadome/src/CreateLeague.tsx

298 lines
9.3 KiB
TypeScript
Raw Normal View History

2021-01-10 06:27:30 +00:00
import React, {useState, useRef, useLayoutEffect, useReducer} from 'react';
import './CreateLeague.css';
import twemoji from 'twemoji';
interface LeagueStructureState {
subleagues: SubleagueState[]
}
interface SubleagueState {
name: string
2021-01-11 04:47:49 +00:00
id: string|number
2021-01-10 06:27:30 +00:00
divisions: DivisionState[]
}
interface DivisionState {
name: string
2021-01-11 04:47:49 +00:00
id: string|number
2021-01-10 06:27:30 +00:00
teams: TeamState[]
}
interface TeamState {
name: string
2021-01-11 04:47:49 +00:00
id: string|number
2021-01-10 06:27:30 +00:00
}
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;
2021-01-11 04:47:49 +00:00
let getUID = function() { // does NOT generate UUIDs. Meant to create list keys ONLY
let id = 0;
return function() { return id++}
}()
2021-01-10 06:27:30 +00:00
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: "",
2021-01-11 04:47:49 +00:00
id: getUID(),
2021-01-10 06:27:30 +00:00
divisions: arrayOf(state.subleagues[0].divisions.length, i => ({
name: "",
2021-01-11 04:47:49 +00:00
id: getUID(),
2021-01-10 06:27:30 +00:00
teams: []
}))
}])};
case 'rename_subleague':
return replaceSubleague(state, action.subleague_index, subleague => ({
name: action.name,
2021-01-11 04:47:49 +00:00
id: subleague.id,
2021-01-10 06:27:30 +00:00
divisions: subleague.divisions
}));
case 'remove_divisions':
return {subleagues: state.subleagues.map(subleague => ({
name: subleague.name,
2021-01-11 04:47:49 +00:00
id: subleague.id,
2021-01-10 06:27:30 +00:00
divisions: removeIndex(subleague.divisions, action.division_index)
}))};
case 'add_divisions':
return {subleagues: state.subleagues.map(subleague => ({
name: subleague.name,
2021-01-11 04:47:49 +00:00
id: subleague.id,
divisions: subleague.divisions.concat([{
name: "",
id: getUID(),
teams: []
}])
2021-01-10 06:27:30 +00:00
}))};
case 'rename_division':
return replaceDivision(state, action.subleague_index, action.division_index, division => ({
name: action.name,
2021-01-11 04:47:49 +00:00
id: division.id,
2021-01-10 06:27:30 +00:00
teams: division.teams
}));
case 'remove_team':
return replaceDivision(state, action.subleague_index, action.division_index, division => ({
name: division.name,
2021-01-11 04:47:49 +00:00
id: division.id,
2021-01-10 06:27:30 +00:00
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,
2021-01-11 04:47:49 +00:00
id: division.id,
teams: division.teams.concat([{
name: action.name,
id: getUID()
}])
2021-01-10 06:27:30 +00:00
}));
}
}
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,
2021-01-11 04:47:49 +00:00
id: subleague.id,
2021-01-10 06:27:30 +00:00
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;
}
2021-01-11 04:47:49 +00:00
let initLeagueStructure = {
subleagues: [0, 1].map((val) => ({
name: "",
id: getUID(),
divisions: [0, 1].map((val) => ({
name: "",
id: getUID(),
teams: []
}))
}))
}
2021-01-10 06:27:30 +00:00
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">
2021-01-11 04:47:49 +00:00
<div className="cl_league_structure_table">
2021-01-10 06:27:30 +00:00
<SubleagueHeaders subleagues={props.state.subleagues} dispatch={props.dispatch} />
<Divisions subleagues={props.state.subleagues} dispatch={props.dispatch} />
2021-01-11 04:47:49 +00:00
</div>
2021-01-10 06:27:30 +00:00
<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 (
2021-01-11 04:47:49 +00:00
<div className="cl_headers">
<div key="filler" className="cl_delete_filler"/>
{props.subleagues.map((subleague, i) => (
<div key={subleague.id} className="cl_table_header">
<div className="cl_subleague_bg">
<SubleageHeader state={subleague} canDelete={props.subleagues.length > 1} dispatch={action =>
props.dispatch(Object.assign({subleague_index: i}, action))
}/>
</div>
</div>
))}
</div>
2021-01-10 06:27:30 +00:00
);
}
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>}) {
2021-01-11 04:47:49 +00:00
return (<>
{props.subleagues[0].divisions.map((val, di) => (
<div key={val.id} className="cl_table_row">
<div 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
}
</div>
{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))
}/>
</div>
</div>
))}
</div>
))}
</>);
2021-01-10 06:27:30 +00:00
}
function Division(props: {state: DivisionState, dispatch:(action: DistributiveOmit<LeagueReducerActions, 'subleague_index'|'division_index'>) => void}) {
let [newName, setNewName] = useState("");
2021-01-11 04:47:49 +00:00
let [searchResults, setSearchResults] = useState<string[]>([]);
let newNameInput = useRef<HTMLInputElement>(null);
let resultList = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
if (resultList.current) {
twemoji.parse(resultList.current)
}
})
2021-01-10 06:27:30 +00:00
return (
<div className="cl_division">
2021-01-11 21:11:35 +00:00
<div>
<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})
}/>
</div>
2021-01-10 06:27:30 +00:00
{props.state.teams.map((team, i) => (
2021-01-11 04:47:49 +00:00
<div className="cl_team" key={team.id}>
2021-01-10 06:27:30 +00:00
<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">
2021-01-11 04:47:49 +00:00
<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'});
2021-01-11 19:14:54 +00:00
fetch("/api/teams/search?" + params.toString())
.then(response => response.json())
.then(data => setSearchResults(data));
2021-01-11 04:47:49 +00:00
setNewName(e.target.value);
}}/>
2021-01-10 06:27:30 +00:00
</div>
2021-01-11 04:47:49 +00:00
{searchResults.length > 0 && newName.length > 0 ?
(<div className="cl_search_list" ref={resultList}>
{searchResults.map(result =>
<div className="cl_search_result" key={result} onClick={e => {
props.dispatch({type:'add_team', name: result});
setNewName("");
if (newNameInput.current) {
newNameInput.current.focus();
}
}}>{result}</div>
)}
</div>):
<div/>
}
2021-01-10 06:27:30 +00:00
</div>
);
}
function LeagueOptions() {
return (
<div className="cl_league_options">
2021-01-11 04:47:49 +00:00
2021-01-10 06:27:30 +00:00
</div>
);
}
export default CreateLeague;