forked from TWS/kalkutago
Compare commits
7 commits
0f7011aa8e
...
284d40c8b9
Author | SHA1 | Date | |
---|---|---|---|
D. Scott Boggs | 284d40c8b9 | ||
D. Scott Boggs | 9fc85d9db0 | ||
D. Scott Boggs | 646eb60dc9 | ||
D. Scott Boggs | ea5e5325b5 | ||
D. Scott Boggs | 6f5e23e022 | ||
scott | 9b876511d9 | ||
D. Scott Boggs | 7a1dee61c9 |
|
@ -1,5 +1,6 @@
|
|||
import { reactive } from "vue"
|
||||
import { Track } from "./track"
|
||||
import { Tick } from './ticks'
|
||||
import { error } from "./error"
|
||||
|
||||
enum State {
|
||||
|
@ -17,7 +18,7 @@ export const state = reactive({
|
|||
source.addEventListener('message', event => console.log(event))
|
||||
source.addEventListener('TickAdded', event => {
|
||||
console.log(event)
|
||||
const tick: Tick = JSON.parse(event.data)
|
||||
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 ?? []
|
||||
|
@ -29,9 +30,12 @@ export const state = reactive({
|
|||
})
|
||||
this.tracks = tracks
|
||||
})
|
||||
source.addEventListener('TrackAdded', ({ data }) => {
|
||||
const track: Track = Track.fromJSON(JSON.parse(data))
|
||||
this.tracks = [track, ...this.tracks]
|
||||
})
|
||||
source.addEventListener('TickDropped', event => {
|
||||
console.log(event)
|
||||
const tick: Tick = JSON.parse(event.data)
|
||||
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)
|
||||
|
@ -40,6 +44,14 @@ export const state = reactive({
|
|||
})
|
||||
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
|
||||
|
@ -80,7 +92,7 @@ export const state = reactive({
|
|||
if (!ok)
|
||||
error(`error deleting ticks for ${track.id}: ${statusText} (${status})`)
|
||||
},
|
||||
async addTrack(track: Track) {
|
||||
async addTrack(track: Track): Promise<boolean> {
|
||||
const response = await fetch('/api/v1/tracks', {
|
||||
method: "POST",
|
||||
body: JSON.stringify(track),
|
||||
|
@ -88,6 +100,7 @@ export const state = reactive({
|
|||
})
|
||||
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" })
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
interface ITick {
|
||||
export interface ITick {
|
||||
id: number
|
||||
track_id?: number
|
||||
year?: number
|
||||
|
@ -10,7 +10,7 @@ interface ITick {
|
|||
has_time_info?: number
|
||||
}
|
||||
|
||||
class Tick implements ITick {
|
||||
export class Tick implements ITick {
|
||||
id: number
|
||||
track_id?: number
|
||||
year?: number
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Track } from '../track';
|
|||
import { computed, ref } from 'vue';
|
||||
import { state } from '../state';
|
||||
|
||||
const props = defineProps<{initialState?: Track}>()
|
||||
const props = defineProps<{ initialState?: Track }>()
|
||||
const router = useRouter()
|
||||
|
||||
const name = ref(props.initialState?.name ?? "")
|
||||
|
@ -12,19 +12,22 @@ const description = ref(props.initialState?.description?.toString() ?? "")
|
|||
const icon = ref(props.initialState?.icon ?? "")
|
||||
const enabled = ref(props.initialState?.enabled ?? true)
|
||||
const multipleEntriesPerDay = ref(props.initialState?.multiple_entries_per_day ?? false)
|
||||
const color = ref(props.initialState?.color ?? null)
|
||||
const order = ref(props.initialState?.order ?? null)
|
||||
const color = ref(props.initialState?.color ?? undefined)
|
||||
const order = ref<any>(props.initialState?.order ?? undefined)
|
||||
|
||||
const submittingNow = ref(false)
|
||||
const submitButtonClass = computed(() => 'button is-primary' + (submittingNow.value ? ' is-loading' : ''))
|
||||
|
||||
const submit = async () => {
|
||||
submittingNow.value = true
|
||||
// if you make a change to order then erase the value in the box it's ""
|
||||
if (order.value === "") order.value = undefined
|
||||
if (order.value instanceof String || typeof order.value === 'string') order.value = Number(order.value)
|
||||
const track = new Track(undefined, name.value, description.value,
|
||||
icon.value, Number(enabled.value), Number(multipleEntriesPerDay.value),
|
||||
color.value, order.value)
|
||||
await state.addTrack(track)
|
||||
router.push('/')
|
||||
if (await state.addTrack(track))
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
|
@ -32,7 +35,7 @@ const submit = async () => {
|
|||
<div class="field">
|
||||
<label for="name" class="label">Name</label>
|
||||
<div class="control">
|
||||
<input type="text" name="name" class="input" v-model="name"/>
|
||||
<input type="text" name="name" class="input" v-model="name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
|
@ -56,7 +59,7 @@ const submit = async () => {
|
|||
</div>
|
||||
<div class="control">
|
||||
<label for="multiple-entries" class="label">
|
||||
<input type="checkbox" name="multiple-entries" class="checkbox" v-model="multipleEntriesPerDay"/>
|
||||
<input type="checkbox" name="multiple-entries" class="checkbox" v-model="multipleEntriesPerDay" />
|
||||
Multiple Entries per Day?
|
||||
</label>
|
||||
</div>
|
||||
|
@ -69,7 +72,7 @@ const submit = async () => {
|
|||
<div class="field">
|
||||
<div class="control">
|
||||
<label for="order" class="label">
|
||||
<input type="number" name="order" class="input" v-model="order"/>
|
||||
<input type="number" name="order" class="input" v-model="order" />
|
||||
Order
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -8,10 +8,7 @@ use crate::error::Error;
|
|||
use super::error::ApiResult;
|
||||
|
||||
#[post("/dump", data = "<sql_dump>")]
|
||||
pub(crate) async fn sql_dump(
|
||||
db: &State<DatabaseConnection>,
|
||||
sql_dump: &str,
|
||||
) -> ApiResult<Status> {
|
||||
pub(crate) async fn sql_dump(db: &State<DatabaseConnection>, sql_dump: &str) -> ApiResult<Status> {
|
||||
for line in sql_dump.lines() {
|
||||
let line = line.to_ascii_lowercase();
|
||||
if line.starts_with("insert into")
|
||||
|
@ -25,11 +22,8 @@ pub(crate) async fn sql_dump(
|
|||
Ok(Status::Ok)
|
||||
}
|
||||
|
||||
#[post("/", data="<sqlite_db>")]
|
||||
pub(crate) async fn db_file(
|
||||
db: &State<DatabaseConnection>,
|
||||
sqlite_db: &[u8],
|
||||
) -> ApiResult<Status> {
|
||||
#[post("/", data = "<sqlite_db>")]
|
||||
pub(crate) async fn db_file(db: &State<DatabaseConnection>, sqlite_db: &[u8]) -> ApiResult<Status> {
|
||||
use std::{
|
||||
io::Write,
|
||||
process::{Command, Stdio},
|
||||
|
@ -51,8 +45,6 @@ pub(crate) async fn db_file(
|
|||
if result.status.success() {
|
||||
sql_dump(db, &String::from_utf8(result.stdout).map_err(Error::from)?).await
|
||||
} else {
|
||||
Err(Error::SqliteCommandError(String::from_utf8_lossy(
|
||||
&result.stderr,
|
||||
).to_string()).into())
|
||||
Err(Error::SqliteCommandError(String::from_utf8_lossy(&result.stderr).to_string()).into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ use either::Either::{self, Left, Right};
|
|||
use rocket::http::Status;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use sea_orm::{prelude::*, DatabaseConnection};
|
||||
use std::default::default;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
|
||||
use super::update::Update;
|
||||
|
@ -54,37 +53,46 @@ pub(super) async fn ticks_for_track(
|
|||
#[post("/", format = "application/json", data = "<track>")]
|
||||
pub(super) async fn insert_track(
|
||||
db: &State<DatabaseConnection>,
|
||||
tx: &State<Sender<Update>>,
|
||||
track: Json<serde_json::Value>,
|
||||
) -> ApiResult<Json<tracks::Model>> {
|
||||
let track = track.0;
|
||||
let db = db as &DatabaseConnection;
|
||||
let mut model: tracks::ActiveModel = default();
|
||||
model.set_from_json(track).map_err(Error::from)?;
|
||||
Ok(Json(model.insert(db).await.map_err(Error::from)?))
|
||||
let model = tracks::ActiveModel::from_json(track).map_err(Error::from)?;
|
||||
let track = model.insert(db).await.map_err(Error::from)?;
|
||||
tx.send(Update::track_added(track.clone()))
|
||||
.map_err(Error::from)?;
|
||||
Ok(Json(track))
|
||||
}
|
||||
|
||||
#[put("/", format = "application/json", data = "<track>")]
|
||||
pub(super) async fn update_track(
|
||||
db: &State<DatabaseConnection>,
|
||||
tx: &State<Sender<Update>>,
|
||||
track: Json<serde_json::Value>,
|
||||
) -> ApiResult<Json<tracks::Model>> {
|
||||
let db = db as &DatabaseConnection;
|
||||
Ok(Json(
|
||||
tracks::ActiveModel::from_json(track.0)
|
||||
.map_err(Error::from)?
|
||||
.update(db)
|
||||
.await
|
||||
.map_err(Error::from)?,
|
||||
))
|
||||
let track = tracks::ActiveModel::from_json(track.0)
|
||||
.map_err(Error::from)?
|
||||
.update(db)
|
||||
.await
|
||||
.map_err(Error::from)?;
|
||||
tx.send(Update::track_changed(track.clone()))
|
||||
.map_err(Error::from)?;
|
||||
Ok(Json(track))
|
||||
}
|
||||
|
||||
#[delete("/<id>")]
|
||||
pub(super) async fn delete_track(db: &State<DatabaseConnection>, id: i32) -> ApiResult<Status> {
|
||||
pub(super) async fn delete_track(
|
||||
db: &State<DatabaseConnection>,
|
||||
tx: &State<Sender<Update>>,
|
||||
id: i32,
|
||||
) -> ApiResult<Status> {
|
||||
let db = db as &DatabaseConnection;
|
||||
Tracks::delete_by_id(id)
|
||||
.exec(db)
|
||||
.await
|
||||
.map_err(Error::from)?;
|
||||
let Some(track) = Tracks::find_by_id(id).one(db).await.map_err(Error::from)? else {
|
||||
return Ok(Status::NotFound);
|
||||
};
|
||||
tx.send(Update::track_removed(track)).map_err(Error::from)?;
|
||||
Ok(Status::Ok)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,10 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::json;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
|
||||
use crate::{entities::ticks, error::Result};
|
||||
use crate::{
|
||||
entities::{ticks, tracks},
|
||||
error::Result,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Update {
|
||||
|
@ -16,6 +19,10 @@ pub enum Update {
|
|||
kind: UpdateType,
|
||||
count: u64,
|
||||
},
|
||||
TrackChanged {
|
||||
kind: UpdateType,
|
||||
track: tracks::Model,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
@ -23,6 +30,9 @@ pub enum Update {
|
|||
pub enum UpdateType {
|
||||
TickAdded,
|
||||
TickDropped,
|
||||
TrackAdded,
|
||||
TrackChanged,
|
||||
TrackDropped,
|
||||
Error,
|
||||
}
|
||||
|
||||
|
@ -48,6 +58,26 @@ impl Update {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn track_added(track: tracks::Model) -> Self {
|
||||
Self::TrackChanged {
|
||||
kind: UpdateType::TrackAdded,
|
||||
track,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track_removed(track: tracks::Model) -> Self {
|
||||
Self::TrackChanged {
|
||||
kind: UpdateType::TrackDropped,
|
||||
track,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track_changed(track: tracks::Model) -> Self {
|
||||
Self::TrackChanged {
|
||||
kind: UpdateType::TrackChanged,
|
||||
track,
|
||||
}
|
||||
}
|
||||
pub fn to_event(&self) -> Event {
|
||||
use Update::*;
|
||||
match self {
|
||||
|
@ -56,6 +86,7 @@ impl Update {
|
|||
Event::json(&json! {{"message": "error: lagged", "count": count}})
|
||||
.event(format!("{kind:?}"))
|
||||
}
|
||||
TrackChanged { kind, track } => Event::json(track).event(format!("{kind:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue