import { reactive } from "vue" import { Track } from "./track" import { Tick } from './ticks' import { error } from "./error" enum State { Unfetched, Fetching, Fetched, } export const state = reactive({ tracks: new Array, state: State.Unfetched, streamUpdatesFromServer() { const source = new EventSource("/api/v1/updates") source.addEventListener("open", () => console.debug("opened event source")) source.addEventListener('message', event => console.log(event)) source.addEventListener('TickAdded', event => { console.log(event) const tick: Tick = Tick.fromJSON(JSON.parse(event.data)) const tracks = this.tracks.map(track => { if (track.id === tick.track_id) { const ticks = track.ticks ?? [] ticks.push(tick) track.ticks = ticks } return track }) this.tracks = tracks }) source.addEventListener('TrackAdded', ({ data }) => { const track: Track = Track.fromJSON(JSON.parse(data)) this.tracks = [track, ...this.tracks] }) source.addEventListener('TickDropped', event => { const tick: Tick = Tick.fromJSON(JSON.parse(event.data)) const tracks = this.tracks.map(track => { if (track.id === tick.track_id) { track.ticks = track.ticks?.filter($tick => $tick.id !== tick.id) } return track }) this.tracks = tracks }) source.addEventListener('TrackDropped', ({ data }) => { const track: Track = Track.fromJSON(JSON.parse(data)) this.tracks = this.tracks.filter($track => $track.id !== track.id) }) source.addEventListener('TrackChanged', ({ data }) => { const track: Track = Track.fromJSON(JSON.parse(data)) this.tracks = this.tracks.map($track => $track.id === track.id ? track : $track) }) source.addEventListener('Lagged', event => { console.log(event) // Refresh the page, refetching the list of tracks and ticks window.location = window.location }) source.addEventListener('error', event => { error(event) window.location = window.location }) window.addEventListener('beforeunload', () => source.close()) }, async repopulate() { this.state = State.Fetching this.tracks = await Track.fetchAll() }, async populate() { if (this.state != State.Unfetched) return await this.repopulate() this.streamUpdatesFromServer() this.state = State.Fetched }, async taskCompleted(track: Track, date: Date): Promise { let query = new URLSearchParams() query.append("year", date.getUTCFullYear().toString()) query.append("month", (date.getUTCMonth() + 1).toString()) // good thing I still had this ^^^^^^^^^^^^^^ in mind when I wrote this 😬 query.append("day", date.getUTCDate().toString()) const response: Response = await fetch(`/api/v1/tracks/${track.id}/ticked?${query.toString()}`, { method: "PATCH" }) const body = await response.text() if (!response.ok) { error(body) throw new Error(`error setting tick for track ${track.id} ("${track.name}"): ${response.status} ${response.statusText}`) } return JSON.parse(body) }, async taskMarkedIncomplete(track: Track) { const { ok, status, statusText } = await fetch(`/api/v1/tracks/${track.id}/all-ticks`, { method: 'DELETE' }) if (!ok) error(`error deleting ticks for ${track.id}: ${statusText} (${status})`) }, async addTrack(track: Track): Promise { const response = await fetch('/api/v1/tracks', { method: "POST", body: JSON.stringify(track), headers: { "Content-Type": "application/json" } }) if (!response.ok) error(`error submitting track: ${track}: ${response.statusText} (${response.status})`) return response.ok }, async removeTrack(trackID: number) { const response = await fetch(`/api/v1/tracks/${trackID}`, { method: "DELETE" }) if (!response.ok) error(`error deleting track with ID ${trackID}: ${response.statusText} (${response.status})`) } })