Make bump session button pretty
This commit is contained in:
@ -64,6 +64,9 @@ async fn main() -> io::Result<()> {
|
|||||||
routes::pages::index,
|
routes::pages::index,
|
||||||
routes::pages::history,
|
routes::pages::history,
|
||||||
routes::pages::session_edit,
|
routes::pages::session_edit,
|
||||||
|
routes::pages::start_session,
|
||||||
|
routes::pages::end_session,
|
||||||
|
routes::pages::bump_session,
|
||||||
routes::pages::stats::single_stats,
|
routes::pages::stats::single_stats,
|
||||||
routes::pages::stats::all_stats,
|
routes::pages::stats::all_stats,
|
||||||
],
|
],
|
||||||
@ -80,7 +83,6 @@ async fn main() -> io::Result<()> {
|
|||||||
routes::api::category::set_parent,
|
routes::api::category::set_parent,
|
||||||
routes::api::start_session,
|
routes::api::start_session,
|
||||||
routes::api::end_session,
|
routes::api::end_session,
|
||||||
routes::api::bump_session,
|
|
||||||
routes::api::edit_session,
|
routes::api::edit_session,
|
||||||
routes::api::delete_session,
|
routes::api::delete_session,
|
||||||
routes::api::wait_for_event,
|
routes::api::wait_for_event,
|
||||||
|
|||||||
@ -15,50 +15,6 @@ use rocket_contrib::uuid::Uuid;
|
|||||||
use sled::Transactional;
|
use sled::Transactional;
|
||||||
use stl_lib::wfe::WaitForEvent;
|
use stl_lib::wfe::WaitForEvent;
|
||||||
|
|
||||||
#[post("/category/<category_uuid>/bump_session/minutes/<minutes>")]
|
|
||||||
pub fn bump_session(
|
|
||||||
_auth: Authorized,
|
|
||||||
category_uuid: Uuid,
|
|
||||||
minutes: i64,
|
|
||||||
db: State<'_, sled::Db>,
|
|
||||||
) -> Result<Redirect, StatusJson> {
|
|
||||||
use crate::database::latest::trees::category;
|
|
||||||
|
|
||||||
let duration = Duration::minutes(minutes);
|
|
||||||
let category_uuid_s = sled::IVec::from(serialize(&category_uuid.into_inner())?);
|
|
||||||
|
|
||||||
let categories_tree = db.open_tree(category::NAME)?;
|
|
||||||
|
|
||||||
(&categories_tree).transaction(|tx_categories| {
|
|
||||||
match tx_categories.get(&category_uuid_s)? {
|
|
||||||
None => Ok(Err(Status::NotFound.into())),
|
|
||||||
Some(data) => {
|
|
||||||
let mut category: category::V = deserialize(&data).unwrap();
|
|
||||||
match category.started.as_mut() {
|
|
||||||
Some(started) => {
|
|
||||||
if let Some(new_started) = started.checked_sub_signed(duration) {
|
|
||||||
*started = new_started;
|
|
||||||
tx_categories
|
|
||||||
.insert(&category_uuid_s, serialize(&category).unwrap())?;
|
|
||||||
} else {
|
|
||||||
return Ok(Err(StatusJson::new(
|
|
||||||
Status::BadRequest,
|
|
||||||
"Duration subtract resulted in overflow",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Ok(Redirect::to(uri!(pages::index))))
|
|
||||||
}
|
|
||||||
None => Ok(Err(StatusJson::new(
|
|
||||||
Status::BadRequest,
|
|
||||||
"No active session",
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/category/<category_uuid>/start_session")]
|
#[post("/category/<category_uuid>/start_session")]
|
||||||
pub fn start_session(
|
pub fn start_session(
|
||||||
_auth: Authorized,
|
_auth: Authorized,
|
||||||
|
|||||||
@ -3,9 +3,11 @@ pub mod stats;
|
|||||||
use crate::auth::Authorized;
|
use crate::auth::Authorized;
|
||||||
use crate::database::latest::trees::{category, session};
|
use crate::database::latest::trees::{category, session};
|
||||||
use crate::status_json::StatusJson;
|
use crate::status_json::StatusJson;
|
||||||
|
use crate::util::EventNotifier;
|
||||||
use bincode::{deserialize, serialize};
|
use bincode::{deserialize, serialize};
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::{get, State};
|
use rocket::response::Redirect;
|
||||||
|
use rocket::{get, post, uri, State};
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_contrib::templates::Template;
|
||||||
use rocket_contrib::uuid::Uuid;
|
use rocket_contrib::uuid::Uuid;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
@ -89,6 +91,72 @@ pub fn index(_auth: Authorized, db: State<'_, sled::Db>) -> Result<Template, Sta
|
|||||||
Ok(Template::render("index", &context))
|
Ok(Template::render("index", &context))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/category/<category_uuid>/start_session")]
|
||||||
|
pub fn start_session(
|
||||||
|
_auth: Authorized,
|
||||||
|
category_uuid: Uuid,
|
||||||
|
event_notifier: State<'_, EventNotifier>,
|
||||||
|
db: State<'_, sled::Db>,
|
||||||
|
) -> Result<Redirect, StatusJson> {
|
||||||
|
super::api::toggle_category_session(category_uuid, true, event_notifier, db)?;
|
||||||
|
Ok(Redirect::to(uri!(index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/category/<category_uuid>/end_session")]
|
||||||
|
pub fn end_session(
|
||||||
|
_auth: Authorized,
|
||||||
|
category_uuid: Uuid,
|
||||||
|
event_notifier: State<'_, EventNotifier>,
|
||||||
|
db: State<'_, sled::Db>,
|
||||||
|
) -> Result<Redirect, StatusJson> {
|
||||||
|
super::api::toggle_category_session(category_uuid, false, event_notifier, db)?;
|
||||||
|
Ok(Redirect::to(uri!(index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/category/<category_uuid>/bump_session/minutes/<minutes>")]
|
||||||
|
pub fn bump_session(
|
||||||
|
_auth: Authorized,
|
||||||
|
category_uuid: Uuid,
|
||||||
|
minutes: i64,
|
||||||
|
db: State<'_, sled::Db>,
|
||||||
|
) -> Result<Redirect, StatusJson> {
|
||||||
|
use crate::database::latest::trees::category;
|
||||||
|
|
||||||
|
let duration = chrono::Duration::minutes(minutes);
|
||||||
|
let category_uuid_s = sled::IVec::from(serialize(&category_uuid.into_inner())?);
|
||||||
|
|
||||||
|
let categories_tree = db.open_tree(category::NAME)?;
|
||||||
|
|
||||||
|
(&categories_tree).transaction(|tx_categories| {
|
||||||
|
match tx_categories.get(&category_uuid_s)? {
|
||||||
|
None => Ok(Err(Status::NotFound.into())),
|
||||||
|
Some(data) => {
|
||||||
|
let mut category: category::V = deserialize(&data).unwrap();
|
||||||
|
match category.started.as_mut() {
|
||||||
|
Some(started) => {
|
||||||
|
if let Some(new_started) = started.checked_sub_signed(duration) {
|
||||||
|
*started = new_started;
|
||||||
|
tx_categories
|
||||||
|
.insert(&category_uuid_s, serialize(&category).unwrap())?;
|
||||||
|
} else {
|
||||||
|
return Ok(Err(StatusJson::new(
|
||||||
|
Status::BadRequest,
|
||||||
|
"Duration subtract resulted in overflow",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Ok(Redirect::to(uri!(index))))
|
||||||
|
}
|
||||||
|
None => Ok(Err(StatusJson::new(
|
||||||
|
Status::BadRequest,
|
||||||
|
"No active session",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/session/<session_uuid>/edit")]
|
#[get("/session/<session_uuid>/edit")]
|
||||||
pub fn session_edit(
|
pub fn session_edit(
|
||||||
_auth: Authorized,
|
_auth: Authorized,
|
||||||
|
|||||||
@ -300,7 +300,7 @@ struct CalendarDayCtx {
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct CalendarCtx {
|
struct CalendarCtx {
|
||||||
days: Vec<Vec<Option<CalendarDayCtx>>>,
|
weeks: Vec<Vec<Option<CalendarDayCtx>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_calendar_stats<'a, I>(sessions: I) -> CalendarCtx
|
fn compute_calendar_stats<'a, I>(sessions: I) -> CalendarCtx
|
||||||
@ -339,54 +339,57 @@ where
|
|||||||
.num_seconds() as f32;
|
.num_seconds() as f32;
|
||||||
|
|
||||||
let weeks = days.iter().group_by(|(day, _)| day.iso_week());
|
let weeks = days.iter().group_by(|(day, _)| day.iso_week());
|
||||||
|
let mut weeks: Vec<_> = weeks
|
||||||
|
.into_iter()
|
||||||
|
.map(|(week, weekdays)| {
|
||||||
|
weekdays
|
||||||
|
.map(|(&day, duration)| {
|
||||||
|
let duration = if duration.is_zero() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(duration.num_seconds() as u64)
|
||||||
|
};
|
||||||
|
|
||||||
CalendarCtx {
|
let month = day.month();
|
||||||
days: weeks
|
|
||||||
.into_iter()
|
|
||||||
.map(|(week, weekdays)| {
|
|
||||||
weekdays
|
|
||||||
.map(|(&day, duration)| {
|
|
||||||
let duration = if duration.is_zero() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(duration.num_seconds() as u64)
|
|
||||||
};
|
|
||||||
|
|
||||||
let month = day.month();
|
let month_border = |other_day| match days.get(&other_day) {
|
||||||
|
Some(_) => other_day.month() != month,
|
||||||
|
None => true,
|
||||||
|
};
|
||||||
|
|
||||||
let month_border = |other_day| match days.get(&other_day) {
|
let month_or_week_border = |other_day| match days.get(&other_day) {
|
||||||
Some(_) => other_day.month() != month,
|
Some(_) => other_day.iso_week() != week || month_border(other_day),
|
||||||
None => true,
|
None => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let month_or_week_border = |other_day| match days.get(&other_day) {
|
const MIN_WEIGHT: f32 = 0.5;
|
||||||
Some(_) => other_day.iso_week() != week || month_border(other_day),
|
|
||||||
None => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MIN_WEIGHT: f32 = 0.5;
|
let ctx = CalendarDayCtx {
|
||||||
|
border_left: month_border(day - Duration::weeks(1)),
|
||||||
|
border_right: month_border(day + Duration::weeks(1)),
|
||||||
|
border_top: month_or_week_border(day - Duration::days(1)),
|
||||||
|
border_bottom: month_or_week_border(day + Duration::days(1)),
|
||||||
|
|
||||||
let ctx = CalendarDayCtx {
|
weight: duration
|
||||||
border_left: month_border(day - Duration::weeks(1)),
|
.map(|d| d as f32 / biggest_day_duration)
|
||||||
border_right: month_border(day + Duration::weeks(1)),
|
.map(|w| (MIN_WEIGHT + w * (1.0 - MIN_WEIGHT)).clamp(0.0, 1.0))
|
||||||
border_top: month_or_week_border(day - Duration::days(1)),
|
.unwrap_or(1.0),
|
||||||
border_bottom: month_or_week_border(day + Duration::days(1)),
|
|
||||||
|
|
||||||
weight: duration
|
duration,
|
||||||
.map(|d| d as f32 / biggest_day_duration)
|
};
|
||||||
.map(|w| (MIN_WEIGHT + w * (1.0 - MIN_WEIGHT)).clamp(0.0, 1.0))
|
|
||||||
.unwrap_or(1.0),
|
|
||||||
|
|
||||||
duration,
|
//(day.weekday(), Some(ctx))
|
||||||
};
|
Some(ctx)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
//(day.weekday(), Some(ctx))
|
// calendar is shown as flex-direction: row-reverse
|
||||||
Some(ctx)
|
// because it should be scrolled from the right
|
||||||
})
|
weeks.reverse();
|
||||||
.collect()
|
|
||||||
})
|
CalendarCtx { weeks }
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
.cal {
|
.cal {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row-reverse;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-width: max-content;
|
||||||
|
padding-left: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cal_col {
|
.cal_col {
|
||||||
|
|||||||
@ -120,6 +120,36 @@ ul.striped_list > li:nth-child(odd) ul li:nth-child(odd) { background-color:#30
|
|||||||
border-width: 0px 0 0px 50px;
|
border-width: 0px 0 0px 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.category_bump_button {
|
||||||
|
position: absolute;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
margin-left: -1.2rem;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
border: solid #a4829c 0.3rem;
|
||||||
|
font-family: Ubuntu;
|
||||||
|
font-weight: 1000;
|
||||||
|
background: dimgray;
|
||||||
|
color: wheat;
|
||||||
|
|
||||||
|
transition: all ease-in-out 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category_bump_button:hover {
|
||||||
|
background: wheat;
|
||||||
|
color: dimgray;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
transition: background color ease-in-out 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category_bump_button:active {
|
||||||
|
}
|
||||||
|
|
||||||
.history_entry {
|
.history_entry {
|
||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
padding-left: 0.5em;
|
padding-left: 0.5em;
|
||||||
|
|||||||
@ -4,14 +4,34 @@
|
|||||||
style="background-color: {{this.category.color}}"
|
style="background-color: {{this.category.color}}"
|
||||||
></div>
|
></div>
|
||||||
<span class="category_name">{{this.category.name}}</span>
|
<span class="category_name">{{this.category.name}}</span>
|
||||||
{{#if this.category.started}}
|
|
||||||
<form action="/api/category/{{@key}}/bump_session/minutes/5", method="post">
|
|
||||||
<button style="height: 100%; color: green;" type="submit">+5</button>
|
|
||||||
</form>
|
|
||||||
{{/if}}
|
|
||||||
<div class="category_button_container">
|
<div class="category_button_container">
|
||||||
|
{{#if this.category.started}}
|
||||||
|
<form
|
||||||
|
action="/category/{{@key}}/bump_session/minutes/5"
|
||||||
|
id="bump-form-{{@key}}"
|
||||||
|
method="post"></form>
|
||||||
|
<button
|
||||||
|
class="category_bump_button"
|
||||||
|
form="bump-form-{{@key}}"
|
||||||
|
type="submit">+5m</button>
|
||||||
|
{{/if}}
|
||||||
|
<!-- without this extra tag, every even noscript tag seems to not trigger -->
|
||||||
|
<!-- what is even happening help -->
|
||||||
|
<noscript></noscript>
|
||||||
|
<noscript>
|
||||||
|
<form
|
||||||
|
{{#if this.category.started}}
|
||||||
|
action="/category/{{@key}}/end_session"
|
||||||
|
{{else}}
|
||||||
|
action="/category/{{@key}}/start_session"
|
||||||
|
{{/if}}
|
||||||
|
id="toggle-form-{{@key}}"
|
||||||
|
method="post"></form>
|
||||||
|
</noscript>
|
||||||
<button
|
<button
|
||||||
id="toggle-button-{{@key}}"
|
id="toggle-button-{{@key}}"
|
||||||
|
type="submit"
|
||||||
|
form="toggle-form-{{@key}}"
|
||||||
onClick="toggle_category('{{@key}}')"
|
onClick="toggle_category('{{@key}}')"
|
||||||
{{#if this.category.started}}
|
{{#if this.category.started}}
|
||||||
class="category_button category_button_toggled"
|
class="category_button category_button_toggled"
|
||||||
|
|||||||
@ -2,7 +2,9 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
|
<!--
|
||||||
<meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline'">
|
<meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline'">
|
||||||
|
-->
|
||||||
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/icon.svg">
|
<link rel="icon" type="image/svg+xml" href="/static/icon.svg">
|
||||||
<link rel="stylesheet" href="/static/styles/common.css">
|
<link rel="stylesheet" href="/static/styles/common.css">
|
||||||
|
|||||||
@ -2,6 +2,14 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
{{> head}}
|
{{> head}}
|
||||||
<body>
|
<body>
|
||||||
|
{{> header}}
|
||||||
|
|
||||||
|
<ul class="striped_list">
|
||||||
|
{{#each categories}}
|
||||||
|
{{>category_entry}}
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function toggle_category(id) {
|
function toggle_category(id) {
|
||||||
// Find out whether the button is in active (play) or inactive (paused) state
|
// Find out whether the button is in active (play) or inactive (paused) state
|
||||||
@ -26,16 +34,15 @@
|
|||||||
//Send the proper header information along with the request
|
//Send the proper header information along with the request
|
||||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if(xhr.readyState === XMLHttpRequest.DONE) {
|
||||||
|
console.log(xhr.status, xhr.responseText);
|
||||||
|
document.location.reload(false /* don't reset scroll position */);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{> header}}
|
|
||||||
|
|
||||||
<ul class="striped_list">
|
|
||||||
{{#each categories}}
|
|
||||||
{{>category_entry}}
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<h2>Senaste veckorna:</h2>
|
<h2>Senaste veckorna:</h2>
|
||||||
<div class="cal">
|
<div class="cal">
|
||||||
{{#each calendar.days}}
|
{{#each calendar.weeks}}
|
||||||
<div class="cal_col">
|
<div class="cal_col">
|
||||||
{{#each this}}
|
{{#each this}}
|
||||||
{{#if this}}
|
{{#if this}}
|
||||||
|
|||||||
Reference in New Issue
Block a user