Skip to content
This repository has been archived by the owner on Jul 10, 2023. It is now read-only.

Restructure Stores #2

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ Features:
- [X] Post alliances to TBA after formation for better playoff bracket
- [ ] Customizable lower thirds with timers for things like lunch
- [ ] More structured team and match storage to avoid iterative searching
- [ ] Team icons on the scores screen
- [ ] Team icons on the scores screen
- [ ] Allow putting in teams without transitioning to match
- [ ] Add red card conditions
15 changes: 15 additions & 0 deletions backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ ws.on("connection", (socket) => {
})
socket.on("matchData", (data) => {
const latestMatch = getCurrentMatch()
if (data == null) {
console.warn("Empty match payload")
return;
}
if (latestMatch.id == data.id) {
latestMatch.blueScoreBreakdown = data.blueScoreBreakdown ?? latestMatch.blueScoreBreakdown
latestMatch.redScoreBreakdown = data.redScoreBreakdown ?? latestMatch.redScoreBreakdown
Expand Down Expand Up @@ -180,7 +184,18 @@ ws.on("connection", (socket) => {
})
cb(alliance)
})
socket.on("getMatch", (cb) => {
cb(getCurrentMatch())
})
socket.on("getMatches", (cb) => {
cb(matches)
})
socket.on("getTeams", (cb) => {
cb(teams)
})

socket.emit("matchData", getCurrentMatch())
socket.emit("teamData", teams)
updateAudienceScreen(currentScreen, socket)
})

Expand Down
16 changes: 11 additions & 5 deletions common/ws_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,22 @@ export interface ServerToClientEvents {
}

export interface ClientToServerEvents {
matchData: (data:Partial<MatchData>) => void;
teamData: (data:TeamData[]) => void;
loadMatch: (id:MatchID) => void;
teamRemove: (id:number) => void;
matchData: (data:Partial<MatchData>) => void;
matchStart: () => void;
matchAbort: () => void;
matchCommit: () => void;

teamData: (data:TeamData[]) => void;
teamRemove: (id:number) => void;

showScreen: (screen:AudienceScreen) => void;
getAlliance(alliance:PlayoffAlliance, cb:(teams: number[]) => void)
getAllianceForTeams(teams:number[], cb:(alliance:PlayoffAlliance) => void)

getAlliance:(alliance:PlayoffAlliance, cb:(teams: number[]) => void) => void
getAllianceForTeams:(teams:number[], cb:(alliance:PlayoffAlliance) => void) => void
getTeams: (cb:(teams:TeamData[]) => void) => void
getMatches: (cb:(matches:MatchData[]) => void) => void
getMatch: (cb:(match:MatchData) => void) => void
}

export type ThisSocket = Socket<ClientToServerEvents, ServerToClientEvents>;
Expand Down
4 changes: 2 additions & 2 deletions frontend/auth/Auth.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
const key = writable(getCookie("auth"))

key.subscribe((value) => {setCookie("auth", value)})

console.log(document.referrer, document.location.href)
</script>
<main>
<h1>Authorization</h1>
Auth Key
<table>
<tr>
<input placeholder="Auth Key" type=string bind:value={$key}>
<button on:click={() => window.location.assign(document.referrer)} id=submit class=green>Submit</button>
<button on:click={() => window.location.assign(document.referrer == document.location.href ? "/" : document.referrer)} id=submit class=green>Submit</button>
</tr>
</table>

Expand Down
121 changes: 121 additions & 0 deletions frontend/bawkStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import type { MatchData, TeamData } from "common/types";
import type { ClientToServerEvents } from "common/ws_types";
import { derived, get, readable, writable, type Readable, type Writable } from "svelte/store";
import structuredClone from '@ungap/structured-clone';

import { socket } from "./socket";
import { matchID } from "./store";
import isEqual from "lodash.isequal"

export interface BawkReadable<T, I> extends Readable<T> {
/**
* Sets the value of the store, updates subscribers, does not send the value to the server
* @param data The value to set the store to. Will be passed into the converter to get the result
* @returns
*/
updateLocal: (data:I) => void

/**
* Sets the value of the store, updates subscribers, does not send the value to the server
* @param data The value to set the store to. Will not be converted
* @returns
*/
updateLocalRaw: (data:T) => void
}

export interface BawkMatchStore<T> extends BawkReadable<T, MatchData>{
asWritable: () => Writable<T>
}

export interface BawkDataStore<T, I> extends BawkReadable<T, I>{
asWritable: () => Writable<T>
}

export function dedupe<T>(store: Readable<T>): Readable<T> {
let previous: T
return derived(store, ($value, set) => {
if (!isEqual($value, previous)) {
previous = structuredClone($value) // Avoid copy by reference, otherwise they will always be equal
set($value)
}
})
}

export function bawkMatchStore<T extends keyof MatchData>(property: T):BawkMatchStore<MatchData[T]> {
const store = writable(null)
const readableStore = dedupe(store)
let ignoreUpdates = false;
let hasBeenMadeWritable = false;
return {
subscribe:readableStore.subscribe,
updateLocal: (data) => {
ignoreUpdates = true
store.set(data[property])
ignoreUpdates = false
},
updateLocalRaw: (data) => {
ignoreUpdates = true
store.set(data)
ignoreUpdates = false
},
asWritable: () => {
if (!hasBeenMadeWritable) {
console.debug("SUBSCRIBING", property)
readableStore.subscribe((value) => {
if (!ignoreUpdates && hasBeenMadeWritable && value !== null) {
console.debug("SENDING", property)
socket.emit("matchData", {
[property]: value,
id: get(matchID)
})
}
})
hasBeenMadeWritable = true
} else {

}

return store
}
}
}



export function bawkDataStore<T, Input, EmitEvent extends keyof ClientToServerEvents,>(initial:Input, converter:(data:Input) => T, event:EmitEvent, emitCallback:(input:T) => Parameters<ClientToServerEvents[EmitEvent]>):BawkDataStore<T,Input> {
const store = writable(converter(initial))
let hasBeenMadeWritable = false;
let ignoreUpdates = false;
return {
subscribe:store.subscribe,
updateLocal: (data) => {
ignoreUpdates = true
store.set(converter(data))
ignoreUpdates = false
},
updateLocalRaw: (data) => {
ignoreUpdates = true
store.set(data)
ignoreUpdates = false
},
asWritable: () => {
if (!hasBeenMadeWritable) {
console.log("SUBSCRIBING", event)
store.subscribe((value) => {
if (!ignoreUpdates && hasBeenMadeWritable && value !== null) {
console.log("SENDING", event, value)
socket.emit(event, ...emitCallback(value))
} else {
console.log("IGNORING", event, value)
}
})
hasBeenMadeWritable = true
} else {

}

return store
}

}
}
7 changes: 4 additions & 3 deletions frontend/event/Event.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<script lang="ts">
import {teams} from "../store"
import {updateTeams} from "../socket"
import {updateTeamData} from "../store"
import Team from "./components/Team.svelte";
import { PlayoffAlliance } from "../../common/alliances";

const teamsWritable = teams.asWritable();
function addTeam() {
teams.update((items) => [...items, {display_id:null, id:null, name:null, playoffAlliance:PlayoffAlliance.NONE, matchIDs: [], matchLosses:0, matchTies:0, matchWins:0, rankingPoints:0}])
teamsWritable.update((items) => [...items, {display_id:null, id:null, name:null, playoffAlliance:PlayoffAlliance.NONE, matchIDs: [], matchLosses:0, matchTies:0, matchWins:0, rankingPoints:0}])
}
</script>

Expand All @@ -31,7 +32,7 @@
<table id=buttons>
<tr>
<button on:click={addTeam}>Add Team</button>
<button on:click={updateTeams}>Save</button>
<button on:click={() => updateTeamData($teams)}>Save</button>
</tr>
</table>
</main>
Expand Down
47 changes: 30 additions & 17 deletions frontend/match/Match.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,41 @@
import Teams from "./components/teams/Teams.svelte";

import { onMount } from "svelte";
import { decodeMatchID, nextMatchID, prettyMatchID } from "../../common/calculations";
import { nextMatchID, prettyMatchID } from "../../common/calculations";
import { AudienceScreenLayout, isCompLevel, MatchState, type MatchData } from "../../common/types";
import match_end from "../assets/audio/match_end.wav";
import match_start from "../assets/audio/match_start.wav";
import match_teleop from "../assets/audio/match_teleop.wav";
import { socket } from "../socket";
import { isDoneLoading, matches, matchID, matchStartTime, matchState, timer, audienceScreen } from "../store";
import { isDoneLoading, matches, matchID, matchStartTime, matchState, timer, audienceScreen, initializer } from "../store";
import ScoreSummary from "./components/ScoreSummary.svelte";
import AudienceControl from "./components/AudienceControl.svelte";
import CommitButton from "./components/CommitButton.svelte";
import { writable } from "svelte/store";

const match_end_sound = new Audio(match_end);
const match_start_sound = new Audio(match_start);
const match_teleop_sound = new Audio(match_teleop);


const matchStateText = writable("")
let matchTextInterval:NodeJS.Timer = null;
matchState.subscribe((value => {
clearInterval(matchTextInterval)
switch (value) {
case MatchState.POSTED: matchStateText.set("Posted"); break
case MatchState.COMPLETED: matchStateText.set("Completed"); break
case MatchState.IN_PROGRESS:
matchStateText.set(timer.remainingTimeFormatted);
matchTextInterval = setInterval(() => matchStateText.set(timer.remainingTimeFormatted), 100)
break
case MatchState.PENDING: matchStateText.set("Pending"); break
}

}))
onMount(() => {
isDoneLoading.then(() => {
initializer.then(() => {
console.log($matchState)
setInterval(() => document.getElementById("match-time").innerText = $matchState == MatchState.COMPLETED ? "Complete" : timer.remainingTimeFormatted, 100)
if ($matchState == MatchState.IN_PROGRESS) {
setButtonStop()
timer.startWithTime($matchStartTime)
Expand Down Expand Up @@ -80,22 +96,18 @@
setStage()
}
}

const matchButtonState = writable({text: "Start", color: "green", onclick: startMatch})
function setButtonStart() {
document.getElementById("match-control").innerText = "Start"
document.getElementById("match-control").classList.remove("red")
document.getElementById("match-control").classList.add("green")
document.getElementById("match-control").onclick = startMatch
matchButtonState.set({text: "Start", color: "green", onclick: startMatch})
}
function setButtonStop() {
document.getElementById("match-control").innerText = "Abort"
document.getElementById("match-control").classList.remove("green")
document.getElementById("match-control").classList.add("red")
document.getElementById("match-control").onclick = abortMatch
matchButtonState.set({text: "Stop", color: "red", onclick: abortMatch})
}

</script>

<main>

<div id="header">
<p>Bunnybots Scoreboard</p>
</div>
Expand All @@ -117,14 +129,15 @@
<button id=new-match class="green" on:click={newMatch}>Next</button>
<button id=new-match class="green" on:click={setStage}>Stage</button>
</div>
{#await initializer then}
<ScoreSummary/>
<CommitButton/>
<div class="sidebar-r">
<h2>Match {$matchID.toUpperCase()} Controls</h2>
<h2>Match {($matchID ?? "").toUpperCase()} Controls</h2>
<div class=row>
<button id=match-control class="green" disabled={$matchState == MatchState.POSTED}>Start</button>
<button id=match-control class="{$matchButtonState.color}" on:click={$matchButtonState.onclick} disabled={$matchState == MatchState.POSTED}>{$matchButtonState.text}</button>
</div>
<h3 id=match-time>0:00</h3>
<h3 id=match-time>{$matchStateText}</h3>
<div id="control-buttons">
<AudienceControl screen={{layout:AudienceScreenLayout.BLANK, match:$matchID}} text="Show Blank"></AudienceControl><br>
<AudienceControl screen={{layout:AudienceScreenLayout.MATCH, match:$matchID}} text="Show Match Screen"></AudienceControl><br>
Expand All @@ -137,7 +150,7 @@

<Teams/>
</div>

{/await}
</main>

<style lang="scss">
Expand Down
Loading