forked from TWS/kalkutago
126 lines
4.7 KiB
TypeScript
126 lines
4.7 KiB
TypeScript
import { reactive } from "vue"
|
|
import { Track } from "./track"
|
|
import { Tick } from './ticks'
|
|
import { error } from "./error"
|
|
|
|
enum State {
|
|
Unfetched,
|
|
Fetching,
|
|
Fetched,
|
|
}
|
|
|
|
function dateQuery(date: Date): URLSearchParams {
|
|
let query = new URLSearchParams()
|
|
query.set("year", date.getUTCFullYear().toString())
|
|
query.set("month", (date.getUTCMonth() + 1).toString())
|
|
// good thing I still had this ^^^^^^^^^^^^^^ in mind when I wrote this 😬
|
|
query.set("day", date.getUTCDate().toString())
|
|
return query
|
|
}
|
|
|
|
interface LoggedInUser {
|
|
name: string
|
|
}
|
|
|
|
class AppState {
|
|
tracks: Array<Track>
|
|
state: State
|
|
user?: LoggedInUser
|
|
|
|
constructor() {
|
|
this.tracks = new Array<Track>
|
|
this.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 => {
|
|
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<Tick> {
|
|
const query = dateQuery(date)
|
|
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, date: Date) {
|
|
const query = dateQuery(date)
|
|
const { ok, status, statusText } = await fetch(`/api/v1/tracks/${track.id}/all-ticks?${query.toString()}`, { method: 'DELETE' })
|
|
if (!ok)
|
|
error(`error deleting ticks for ${track.id}: ${statusText} (${status})`)
|
|
}
|
|
async addTrack(track: Track): Promise<boolean> {
|
|
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})`)
|
|
}
|
|
}
|
|
|
|
export const state = reactive(new AppState)
|