Skip to content

Commit

Permalink
Added match review and audience display fix.
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle-flynn committed Aug 10, 2018
1 parent b40a7c9 commit 3037d18
Show file tree
Hide file tree
Showing 12 changed files with 342 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
flex-direction: row;
font-family: "Roboto", sans-serif;
font-weight: bold;
font-size: 3.7vw;
font-size: 3.5vw;
}

#score-container-red {
Expand Down
2 changes: 1 addition & 1 deletion ems-core/package.electron.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "ems-core",
"author": "The Orange Alliance",
"description": "An improved event experience.",
"version": "2.1.10",
"version": "2.2.1",
"private": true,
"main": "public/desktop/electron.js",
"homepage": "./public/desktop/"
Expand Down
2 changes: 1 addition & 1 deletion ems-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ems-core",
"version": "2.1.10",
"version": "2.2.1",
"private": true,
"main": "public/electron.js",
"dependencies": {
Expand Down
3 changes: 3 additions & 0 deletions ems-core/src/components/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import MatchPlayView from "../views/match-play/MatchPlayView";
import MatchTestView from "../views/match-test/MatchTestView";
import AboutView from "../views/about/AboutView";
import ReportsView from "../views/reports/ReportsView";
import MatchReviewView from "../views/match-review/MatchReviewView";

interface IProps {
slaveMode?: boolean,
Expand Down Expand Up @@ -69,6 +70,8 @@ class AppContainer extends React.Component<IProps, IState> {
return <MatchPlayView/>;
case "Match Test":
return <MatchTestView/>;
case "Match Review":
return <MatchReviewView/>;
case "Reports":
return <ReportsView/>;
case "About":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,5 +260,4 @@ export function mapDispatchToProps(dispatch: Dispatch<ApplicationActions>) {
};
}

export default connect(mapStateToProps, mapDispatchToProps)(EnergyImpactBlueScorecard as any);
// This is 100% not type safe, but TypeScript started randomly complaining and all other components are fine.
export default connect(mapStateToProps, mapDispatchToProps)(EnergyImpactBlueScorecard);
Original file line number Diff line number Diff line change
Expand Up @@ -262,5 +262,4 @@ export function mapDispatchToProps(dispatch: Dispatch<ApplicationActions>) {
};
}

export default connect(mapStateToProps, mapDispatchToProps)(EnergyImpactRedScorecard as any);
// This is 100% not type safe, but TypeScript started randomly complaining and all other components are fine.
export default connect(mapStateToProps, mapDispatchToProps)(EnergyImpactRedScorecard);
23 changes: 14 additions & 9 deletions ems-core/src/views/match-play/containers/MatchPlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ interface IProps {
interface IState {
selectedLevel: TournamentLevels
configModalOpen: boolean,
activeMatch: Match
activeMatch: Match,
committingScores: boolean
}

class MatchPlay extends React.Component<IProps, IState> {
Expand All @@ -63,7 +64,8 @@ class MatchPlay extends React.Component<IProps, IState> {
this.state = {
selectedLevel: "Practice",
configModalOpen: false,
activeMatch: this.props.activeMatch
activeMatch: this.props.activeMatch,
committingScores: false
};
this.changeSelectedLevel = this.changeSelectedLevel.bind(this);
this.changeSelectedMatch = this.changeSelectedMatch.bind(this);
Expand All @@ -82,7 +84,7 @@ class MatchPlay extends React.Component<IProps, IState> {
}

public render() {
const {selectedLevel} = this.state;
const {selectedLevel, committingScores} = this.state;
const {eventConfig, matchState, connected, matchDuration} = this.props;
const fieldControl: number[] = (typeof eventConfig.fieldsControlled === "undefined" ? [1] : eventConfig.fieldsControlled);

Expand Down Expand Up @@ -111,7 +113,7 @@ class MatchPlay extends React.Component<IProps, IState> {
const disabledStates = MatchFlowController.getDisabledStates(this.props.matchState);
const hasRedAlliance = typeof activeMatch.participants !== "undefined" && activeMatch.participants.filter((participant) => participant.station < 20).length > 0;
const hasBlueAlliance = typeof activeMatch.participants !== "undefined" && activeMatch.participants.filter((participant) => participant.station >= 20).length > 0;
const canPrestart = activeMatch.matchKey.length > 0 && activeMatch.fieldNumber > 0 && typeof activeMatch.participants !== null && hasRedAlliance && hasBlueAlliance;
const canPrestart = activeMatch.matchKey.length > 0 && activeMatch.fieldNumber > 0 && typeof activeMatch.participants !== null && hasRedAlliance && hasBlueAlliance && connected;
const hasPrestarted = matchState !== MatchState.PRESTART_READY && matchState !== MatchState.PRESTART_IN_PROGRESS && matchState !== MatchState.MATCH_ABORTED;
const disMin = matchDuration.minutes() < 10 ? "0" + matchDuration.minutes().toString() : matchDuration.minutes().toString();
const disSec = matchDuration.seconds() < 10 ? "0" + matchDuration.seconds().toString() : matchDuration.seconds().toString();
Expand Down Expand Up @@ -139,7 +141,7 @@ class MatchPlay extends React.Component<IProps, IState> {
<Grid.Column width={3}><Button fluid={true} disabled={disabledStates[1]} color="blue" onClick={this.setAudienceDisplay}>Set Audience Display</Button></Grid.Column>
<Grid.Column width={3}><Button fluid={true} disabled={disabledStates[2]} color="yellow" onClick={this.startMatch}>Start Match</Button></Grid.Column>
<Grid.Column width={3}><Button fluid={true} disabled={disabledStates[3]} color="red" onClick={this.abortMatch}>Abort Match</Button></Grid.Column>
<Grid.Column width={3}><Button fluid={true} disabled={disabledStates[4]} color="green" onClick={this.commitScores}>Commit Scores</Button></Grid.Column>
<Grid.Column width={3}><Button fluid={true} disabled={disabledStates[4] || !connected} loading={committingScores} color="green" onClick={this.commitScores}>Commit Scores</Button></Grid.Column>
</Grid.Row>
</Grid>
<Divider/>
Expand Down Expand Up @@ -246,11 +248,13 @@ class MatchPlay extends React.Component<IProps, IState> {

private commitScores() {
// Make sure all of our 'active' objects are on the same page.
this.setState({committingScores: true});
this.props.activeMatch.matchDetails = this.props.activeDetails;
this.props.activeMatch.participants = this.props.activeParticipants;
MatchFlowController.commitScores(this.props.activeMatch, this.props.eventConfig).then(() => {
this.props.setNavigationDisabled(false);
this.props.setMatchState(MatchState.PRESTART_READY);
this.setState({committingScores: false});
if (this.props.activeMatch.tournamentLevel >= 10) {
MatchFlowController.checkForAdvancements(this.props.activeMatch.tournamentLevel, this.props.eventConfig.elimsFormat).then((matches: Match[]) => {
if (this.props.elimsMatches.length < matches.length) {
Expand All @@ -261,6 +265,7 @@ class MatchPlay extends React.Component<IProps, IState> {
});
}
}).catch((error: HttpError) => {
this.setState({committingScores: false});
DialogManager.showErrorBox(error);
});
}
Expand All @@ -272,13 +277,13 @@ class MatchPlay extends React.Component<IProps, IState> {
private getMatchesByTournamentLevel(tournamentLevel: TournamentLevels): Match[] { // TODO - Only show fields that EMS controls
switch (tournamentLevel) {
case "Practice":
return this.props.practiceMatches;
return this.props.practiceMatches.filter(match => this.props.eventConfig.fieldsControlled.indexOf(match.fieldNumber) > -1);
case "Qualification":
return this.props.qualificationMatches;
return this.props.qualificationMatches.filter(match => this.props.eventConfig.fieldsControlled.indexOf(match.fieldNumber) > -1);
case "Finals":
return this.props.finalsMatches;
return this.props.finalsMatches.filter(match => this.props.eventConfig.fieldsControlled.indexOf(match.fieldNumber) > -1);
case "Eliminations":
return this.props.elimsMatches;
return this.props.elimsMatches.filter(match => this.props.eventConfig.fieldsControlled.indexOf(match.fieldNumber) > -1);
default:
return [];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,10 @@ class MatchFlowController {
});
}, 500);
} else {
SocketProvider.send("commit-scores", match.matchKey);
resolve();
setTimeout(() => {
SocketProvider.send("commit-scores", match.matchKey);
resolve();
}, 250);
}
}).catch((error: any) => {
reject(error);
Expand Down
207 changes: 207 additions & 0 deletions ems-core/src/views/match-review/MatchReviewView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import * as React from "react";
import {Button, Card, Dimmer, DropdownProps, Form, Grid, Loader} from "semantic-ui-react";
import GameSpecificScorecard from "../../components/GameSpecificScorecard";
import EventConfiguration from "../../shared/models/EventConfiguration";
import {ApplicationActions, IApplicationState} from "../../stores";
import {connect} from "react-redux";
import {PostQualConfig, TournamentLevels} from "../../shared/AppTypes";
import Match from "../../shared/models/Match";
import {getTheme} from "../../shared/AppTheme";
import {SyntheticEvent} from "react";
import MatchFlowController from "../match-play/controllers/MatchFlowController";
import {ISetActiveDetails, ISetActiveMatch, ISetActiveParticipants} from "../../stores/scoring/types";
import MatchDetails from "../../shared/models/MatchDetails";
import MatchParticipant from "../../shared/models/MatchParticipant";
import {Dispatch} from "redux";
import {setActiveDetails, setActiveMatch, setActiveParticipants} from "../../stores/scoring/actions";
import HttpError from "../../shared/models/HttpError";
import DialogManager from "../../shared/managers/DialogManager";
import {IDisableNavigation, ISetEliminationsMatches} from "../../stores/internal/types";
import {disableNavigation, setEliminationsMatches} from "../../stores/internal/actions";
import ConfirmActionModal from "../../components/ConfirmActionModal";

interface IProps {
eventConfig?: EventConfiguration,
practiceMatches?: Match[],
qualificationMatches?: Match[],
finalsMatches?: Match[],
elimsMatches?: Match[],
activeMatch?: Match,
activeDetails?: MatchDetails,
activeParticipants?: MatchParticipant[],
setActiveMatch?: (match: Match) => ISetActiveMatch,
setActiveDetails?: (details: MatchDetails) => ISetActiveDetails,
setActiveParticipants?: (participants: MatchParticipant[]) => ISetActiveParticipants,
setNavigationDisabled?: (disabled: boolean) => IDisableNavigation,
setEliminationsMatches?: (match: Match[]) => ISetEliminationsMatches
}

interface IState {
selectedLevel: TournamentLevels,
updatingScores: boolean,
confirmModalOpen: boolean
}

class MatchReviewView extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
selectedLevel: "Practice",
updatingScores: false,
confirmModalOpen: false
};
this.changeSelectedLevel = this.changeSelectedLevel.bind(this);
this.changeSelectedMatch = this.changeSelectedMatch.bind(this);
this.openConfirmModal = this.openConfirmModal.bind(this);
this.closeConfirmModal = this.closeConfirmModal.bind(this);
this.updateScores = this.updateScores.bind(this);
}

public render() {
const {eventConfig, activeMatch} = this.props;
const {selectedLevel, updatingScores, confirmModalOpen} = this.state;

const availableLevels = this.getAvailableTournamentLevels(eventConfig.postQualConfig).map(tournamentLevel => {
return {
text: tournamentLevel,
value: tournamentLevel
};
});

const availableMatches = this.getMatchesByTournamentLevel(selectedLevel).map(match => {
return {
text: match.matchName,
value: match.matchKey
};
});
const selectedMatch: Match = activeMatch === null ? new Match() : activeMatch;
return (
<div className="view">
<ConfirmActionModal open={confirmModalOpen} onClose={this.closeConfirmModal} onConfirm={this.updateScores} innerText={`Are you sure you want to update the scores for ${selectedMatch.matchName}? (${selectedMatch.matchKey})`}/>
<Card fluid={true} color={getTheme().secondary}>
<Card.Content>
<Grid columns={16}>
<Grid.Row>
<Grid.Column width={3}><Form.Dropdown fluid={true} selection={true} disabled={updatingScores} value={selectedLevel} options={availableLevels} onChange={this.changeSelectedLevel} label="Tournament Level"/></Grid.Column>
<Grid.Column width={3}><Form.Dropdown fluid={true} selection={true} disabled={updatingScores} value={selectedMatch.matchKey} options={availableMatches} onChange={this.changeSelectedMatch} label="Match"/></Grid.Column>
<Grid.Column width={7}/>
<Grid.Column width={3} verticalAlign={"bottom"}><Button fluid={true} color="red" disabled={selectedMatch === null || typeof selectedMatch === "undefined" || updatingScores} loading={updatingScores} onClick={this.openConfirmModal}>Update Scores</Button></Grid.Column>
</Grid.Row>
</Grid>
</Card.Content>
</Card>
<Card.Group itemsPerRow={2}>
<Dimmer active={updatingScores}>
<Loader/>
</Dimmer>
<GameSpecificScorecard type={eventConfig.eventType} alliance={"Red"}/>
<GameSpecificScorecard type={eventConfig.eventType} alliance={"Blue"}/>
</Card.Group>
</div>
);
}

private openConfirmModal() {
this.setState({confirmModalOpen: true});
}

private closeConfirmModal() {
this.setState({confirmModalOpen: false});
}

private getAvailableTournamentLevels(postQualConfig: PostQualConfig): TournamentLevels[] {
return ["Practice", "Qualification", postQualConfig === "elims" ? "Eliminations" : "Finals"];
}

private getMatchesByTournamentLevel(tournamentLevel: TournamentLevels): Match[] { // TODO - Only show fields that EMS controls
switch (tournamentLevel) {
case "Practice":
return this.props.practiceMatches.filter(match => this.props.eventConfig.fieldsControlled.indexOf(match.fieldNumber) > -1);
case "Qualification":
return this.props.qualificationMatches.filter(match => this.props.eventConfig.fieldsControlled.indexOf(match.fieldNumber) > -1);
case "Finals":
return this.props.finalsMatches.filter(match => this.props.eventConfig.fieldsControlled.indexOf(match.fieldNumber) > -1);
case "Eliminations":
return this.props.elimsMatches.filter(match => this.props.eventConfig.fieldsControlled.indexOf(match.fieldNumber) > -1);
default:
return [];
}
}

private changeSelectedLevel(event: SyntheticEvent, props: DropdownProps) {
const matches = this.getMatchesByTournamentLevel((props.value as TournamentLevels));
if (matches.length > 0) {
this.props.setActiveMatch(matches[0]);
this.setState({
selectedLevel: (props.value as TournamentLevels),
});
} else {
this.setState({
selectedLevel: (props.value as TournamentLevels)
});
}
}

private changeSelectedMatch(event: SyntheticEvent, props: DropdownProps) {
for (const match of this.getMatchesByTournamentLevel(this.state.selectedLevel)) {
if (match.matchKey === (props.value as string)) {
this.props.setActiveMatch(match);
// Temporarily set the match to what we have now, and then get ALL the details.
MatchFlowController.getMatchResults(match.matchKey).then((data: Match) => {
this.props.setActiveMatch(data);
this.props.setActiveParticipants(data.participants);
this.props.setActiveDetails(data.matchDetails);
});
break;
}
}
}

private updateScores() {
// Make sure all of our 'active' objects are on the same page.
this.setState({updatingScores: true, confirmModalOpen: false});
this.props.activeMatch.matchDetails = this.props.activeDetails;
this.props.activeMatch.participants = this.props.activeParticipants;
MatchFlowController.commitScores(this.props.activeMatch, this.props.eventConfig).then(() => {
this.props.setNavigationDisabled(false);
this.setState({updatingScores: false});
if (this.props.activeMatch.tournamentLevel >= 10) {
MatchFlowController.checkForAdvancements(this.props.activeMatch.tournamentLevel, this.props.eventConfig.elimsFormat).then((matches: Match[]) => {
if (this.props.elimsMatches.length < matches.length) {
this.props.setEliminationsMatches(matches);
}
}).catch((error: HttpError) => {
console.error(error);
});
}
}).catch((error: HttpError) => {
this.setState({updatingScores: false});
DialogManager.showErrorBox(error);
});
}
}

export function mapStateToProps({configState, internalState, scoringState}: IApplicationState) {
return {
eventConfig: configState.eventConfiguration,
practiceMatches: internalState.practiceMatches,
qualificationMatches: internalState.qualificationMatches,
finalsMatches: internalState.finalsMatches,
elimsMatches: internalState.eliminationsMatches,
activeMatch: scoringState.activeMatch,
activeDetails: scoringState.activeDetails,
activeParticipants: scoringState.activeParticipants
};
}

export function mapDispatchToProps(dispatch: Dispatch<ApplicationActions>) {
return {
setActiveMatch: (match: Match) => dispatch(setActiveMatch(match)),
setActiveDetails: (details: MatchDetails) => dispatch(setActiveDetails(details)),
setActiveParticipants: (participants: MatchParticipant[]) => dispatch(setActiveParticipants(participants)),
setNavigationDisabled: (disabled: boolean) => dispatch(disableNavigation(disabled)),
setEliminationsMatches: (matches: Match[]) => dispatch(setEliminationsMatches(matches))
};
}

export default connect(mapStateToProps, mapDispatchToProps)(MatchReviewView);
Loading

0 comments on commit 3037d18

Please sign in to comment.