implement datetime parameter [refs #19]
This commit is contained in:
parent
6f967341c8
commit
a49f81799c
10 changed files with 198 additions and 29 deletions
|
|
@ -1,8 +1,39 @@
|
|||
|
||||
div.NdpcCollPresetView {
|
||||
|
||||
div.PresetView {
|
||||
border: thin solid var(--color-red);
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
div.NdpcCollPresetViewTitle {
|
||||
div.PresetView svg {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
div.PresetView div.PresetParamUnLocked {
|
||||
color: var(--color-grey);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
|
||||
div.PresetViewTitle {
|
||||
font-weight: bold;
|
||||
}
|
||||
background-color: var(--color-red);
|
||||
color: var(--color-white);
|
||||
padding: 0.3em 0.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
div.PresetViewGeneral {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
div.PresetViewGeneral > div {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
div.PresetViewGeneral > div:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,58 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
busy_spinner(true);
|
||||
})
|
||||
}
|
||||
|
||||
// convert utc to local time
|
||||
document
|
||||
.querySelectorAll('input[type="datetime-local"][data-utc]')
|
||||
.forEach(input => {
|
||||
const utc = input.dataset.utc;
|
||||
if (!utc) return;
|
||||
const date = new Date(utc);
|
||||
if (isNaN(date)) return;
|
||||
input.value = toDatetimeLocalValue(date);
|
||||
});
|
||||
});
|
||||
|
||||
function toDatetimeLocalValue(date) {
|
||||
const pad = n => String(n).padStart(2, "0");
|
||||
|
||||
return (
|
||||
date.getFullYear() + "-" +
|
||||
pad(date.getMonth() + 1) + "-" +
|
||||
pad(date.getDate()) + "T" +
|
||||
pad(date.getHours()) + ":" +
|
||||
pad(date.getMinutes())
|
||||
);
|
||||
}
|
||||
|
||||
function datetimeLocalToUtcISOString(localValue) {
|
||||
// localValue: "YYYY-MM-DDTHH:mm"
|
||||
|
||||
const [datePart, timePart] = localValue.split("T");
|
||||
if (!datePart || !timePart) return null;
|
||||
|
||||
const [year, month, day] = datePart.split("-").map(Number);
|
||||
const [hour, minute] = timePart.split(":").map(Number);
|
||||
|
||||
// Create Date in *local time*
|
||||
const localDate = new Date(
|
||||
year,
|
||||
month - 1, // JS months are 0-based
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
if (isNaN(localDate)) return null;
|
||||
|
||||
// Convert to UTC
|
||||
return localDate.toISOString();
|
||||
}
|
||||
|
||||
|
||||
function virtual_user_grade_callback(return_code, json_data, callback_data) {
|
||||
if (return_code == 200 || return_code == 204) {
|
||||
window.location.reload();
|
||||
|
|
|
|||
|
|
@ -43,8 +43,12 @@ function ndpcGetCollectionIoContainer(ndpc_collection) {
|
|||
// extract parameter value
|
||||
const param_input = child_param.getElementsByClassName("NdpcParameterInput")[0].children[1];
|
||||
if (param_input.tagName === "INPUT" || param_input.tagName === "SELECT") {
|
||||
// console.log("HERE: " + parameter_io.upid + "; " + param_input.value);
|
||||
parameter_io.value = param_input.value;
|
||||
if (param_input.type === "datetime-local") {
|
||||
parameter_io.value = datetimeLocalToUtcISOString(param_input.value);
|
||||
} else {
|
||||
parameter_io.value = param_input.value;
|
||||
}
|
||||
// console.log("HERE: " + parameter_io.upid + "; " + param_input.value + "; " + parameter_io.value);
|
||||
} else {
|
||||
console.log("ERROR: Unexpected input type '" + param_input.tagName + "' for " + child_param.id);
|
||||
}
|
||||
|
|
@ -190,7 +194,6 @@ function ndpcUpdate() {
|
|||
|
||||
// iterate to find other parameter
|
||||
for (const other_parameter of collection.parentElement.parentElement.querySelectorAll(":scope > .NdpcContainerParameters > .NdpcParameter")) {
|
||||
console.log("HERE: " + other_parameter)
|
||||
if (other_parameter.dataset.ndpcUpid === other_upid) {
|
||||
|
||||
// check if value matches
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ async fn generate_html(app_state: &AppState,
|
|||
|
||||
// dialogue for preset editing
|
||||
if html.http_user_permissions.presets.edit().allowed() {
|
||||
let title = format!("Edit Preset{}", match &preset{None=>"".to_string(), Some(p)=>p.name().await});
|
||||
let title = format!("Edit Preset: {}", match &preset{None=>"".to_string(), Some(p)=>p.name().await});
|
||||
let mut dia = Dialog::new(&title, "PresetEditDialogue");
|
||||
dia.add_button(DialogButtonStyle::Save, "Save Preset".to_string(), "savePreset".to_string());
|
||||
dia.add_button(DialogButtonStyle::Cancel, "Cancel Editing".to_string(), "cancelPresetEdit".to_string());
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ mod param_weather_offset;
|
|||
|
||||
use std::net::ToSocketAddrs;
|
||||
use sslo_lib::ndpc2::collection::{NdpcCollection, NdpcCollectionMetaData};
|
||||
use sslo_lib::ndpc2::parameter::{NdpcParameter, NdpcParameterMetaData};
|
||||
use sslo_lib::ndpc2::parameter::{NdpcAccess, NdpcParameter, NdpcParameterMetaData};
|
||||
use crate::db2::DatabaseManager;
|
||||
|
||||
|
||||
|
|
@ -33,12 +33,12 @@ use crate::db2::DatabaseManager;
|
|||
pub struct CollPreset {
|
||||
meta: NdpcCollectionMetaData,
|
||||
label: String,
|
||||
coll_general: coll_general::CollPresetGeneral,
|
||||
coll_rating: coll_rating::CollRating,
|
||||
coll_session: coll_session::CollSession,
|
||||
coll_server: coll_server::CollServer,
|
||||
coll_classes: coll_car_classes::CollCarClasses,
|
||||
coll_weathers: coll_weathers::CollWeathers,
|
||||
pub coll_general: coll_general::CollPresetGeneral,
|
||||
pub coll_rating: coll_rating::CollRating,
|
||||
pub coll_session: coll_session::CollSession,
|
||||
pub coll_server: coll_server::CollServer,
|
||||
pub coll_classes: coll_car_classes::CollCarClasses,
|
||||
pub coll_weathers: coll_weathers::CollWeathers,
|
||||
}
|
||||
|
||||
impl CollPreset {
|
||||
|
|
@ -58,17 +58,40 @@ impl CollPreset {
|
|||
}
|
||||
|
||||
pub fn html_view(&self) -> String {
|
||||
let mut html = "<div class=\"NdpcCollPresetView\">".to_string();
|
||||
html += &format!("<div class=\"NdpcCollPresetViewTitle\">{}</div>", self.label);
|
||||
let mut html = "<div class=\"PresetView\">".to_string();
|
||||
html += &format!("<div class=\"PresetViewTitle\">{}</div>", self.label);
|
||||
|
||||
// general
|
||||
html+= "<div>";
|
||||
// self.get_parameter("")
|
||||
html += &self.html_view_general();
|
||||
html+= "</div>";
|
||||
|
||||
html += "</div>";
|
||||
html
|
||||
}
|
||||
|
||||
fn html_view_general(&self) -> String {
|
||||
let mut html = "<div class=\"PresetViewGeneral\">".to_string();
|
||||
|
||||
fn format_param(param: &dyn NdpcParameter, icon: &str) -> String {
|
||||
format!("<div title=\"{}\" class=\"{}\">{} {}</div>",
|
||||
param.label(),
|
||||
match param.acces() {
|
||||
NdpcAccess::Locked => "PresetParamLocked",
|
||||
NdpcAccess::Unlocked => "PresetParamUnLocked",
|
||||
},
|
||||
icon,
|
||||
param.value2str(),
|
||||
)
|
||||
}
|
||||
|
||||
html += &format_param(&self.coll_general.driving_aids, phosphor_svgs::icon::first_aid::DUOTONE);
|
||||
html += &format_param(&self.coll_general.damage, phosphor_svgs::icon::heart_break::DUOTONE);
|
||||
html += &format_param(&self.coll_general.penalties, phosphor_svgs::icon::sword::DUOTONE);
|
||||
|
||||
html += "</div>";
|
||||
html
|
||||
}
|
||||
}
|
||||
|
||||
impl NdpcCollection for CollPreset {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ use crate::ndpc::coll_preset::param_sim::ParamSim;
|
|||
pub struct CollPresetGeneral {
|
||||
meta: NdpcCollectionMetaData,
|
||||
sim: ParamSim,
|
||||
driving_aids: ParamDrivingAids,
|
||||
damage: ParamDamage,
|
||||
penalties: ParamPenalties,
|
||||
pub driving_aids: ParamDrivingAids,
|
||||
pub damage: ParamDamage,
|
||||
pub penalties: ParamPenalties,
|
||||
}
|
||||
impl CollPresetGeneral {
|
||||
pub fn new() -> Self {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use utoipa::openapi::KnownFormat::DateTime;
|
||||
use sslo_lib::ndpc2::collection::{NdpcCollection, NdpcCollectionMetaData};
|
||||
use sslo_lib::ndpc2::param_date_time_local::ParamDateTimeLocal;
|
||||
use sslo_lib::ndpc2::param_i64::ParamI64;
|
||||
use sslo_lib::ndpc2::param_time::ParamTime;
|
||||
use sslo_lib::ndpc2::parameter::NdpcParameter;
|
||||
|
|
@ -13,7 +15,8 @@ pub struct CollSession {
|
|||
qual_duration: ParamI64,
|
||||
race_distance: ParamRaceDistance,
|
||||
race_duration: ParamI64,
|
||||
race_time: ParamTime,
|
||||
race_date: ParamDateTimeLocal,
|
||||
virtual_time: ParamTime,
|
||||
multiplicator: ParamI64,
|
||||
}
|
||||
impl CollSession {
|
||||
|
|
@ -50,8 +53,14 @@ impl CollSession {
|
|||
"The duration of the race in minutes or laps".to_string(),
|
||||
);
|
||||
|
||||
let race_time = ParamTime::new(
|
||||
"race_time", "Virtual Race Time".to_string(), None,
|
||||
let race_date = ParamDateTimeLocal::new(
|
||||
"race_date", "Race Session Date".to_string(), None,
|
||||
chrono::Utc::now(),
|
||||
"When shall the race session start".to_string()
|
||||
);
|
||||
|
||||
let virtual_time = ParamTime::new(
|
||||
"virtual_time", "Virtual Time".to_string(), None,
|
||||
14, 0,
|
||||
"The simulated, track-local daytime at when the race shall start".to_string()
|
||||
);
|
||||
|
|
@ -70,7 +79,8 @@ impl CollSession {
|
|||
qual_duration,
|
||||
race_distance: ParamRaceDistance::new(),
|
||||
race_duration,
|
||||
race_time,
|
||||
race_date,
|
||||
virtual_time,
|
||||
multiplicator,
|
||||
}
|
||||
}
|
||||
|
|
@ -82,10 +92,10 @@ impl NdpcCollection for CollSession {
|
|||
fn label(&self) -> &str { "Session" }
|
||||
|
||||
fn child_parameters(&self) -> Vec<&dyn NdpcParameter> {
|
||||
vec![&self.practice_opening, &self.practice_duration, &self.warmup_duration, &self.qual_duration, &self.race_distance, &self.race_duration, &self.race_time, &self.multiplicator]
|
||||
vec![&self.practice_opening, &self.practice_duration, &self.warmup_duration, &self.qual_duration, &self.race_distance, &self.race_duration, &self.race_date, &self.virtual_time, &self.multiplicator]
|
||||
}
|
||||
fn child_parameters_mut(&mut self) -> Vec<&mut dyn NdpcParameter> {
|
||||
vec![&mut self.practice_opening, &mut self.practice_duration, &mut self.warmup_duration, &mut self.qual_duration, &mut self.race_distance, &mut self.race_duration, &mut self.race_time, &mut self.multiplicator]
|
||||
vec![&mut self.practice_opening, &mut self.practice_duration, &mut self.warmup_duration, &mut self.qual_duration, &mut self.race_distance, &mut self.race_duration, &mut self.race_date, &mut self.virtual_time, &mut self.multiplicator]
|
||||
}
|
||||
|
||||
fn child_collections(&self) -> Vec<&dyn NdpcCollection> { vec![] }
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ pub mod param_i64;
|
|||
pub mod param_enum;
|
||||
pub mod param_time;
|
||||
pub mod param_bool;
|
||||
pub mod param_date_time_local;
|
||||
|
|
|
|||
49
sslo_lib/src/ndpc2/param_date_time_local.rs
Normal file
49
sslo_lib/src/ndpc2/param_date_time_local.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use chrono::{DateTime, ParseResult, Utc};
|
||||
use crate::error::SsloError;
|
||||
use crate::ndpc2::parameter::{NdpcParameter, NdpcParameterMetaData};
|
||||
|
||||
pub struct ParamDateTimeLocal {
|
||||
upid: &'static str,
|
||||
label: String,
|
||||
description: String,
|
||||
unit: Option<String>,
|
||||
meta: NdpcParameterMetaData,
|
||||
value: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl ParamDateTimeLocal {
|
||||
pub fn new(upid: &'static str, label: String, unit: Option<String>, default: DateTime<Utc>, description: String) -> Self { Self {
|
||||
upid,
|
||||
label,
|
||||
description,
|
||||
unit,
|
||||
meta: NdpcParameterMetaData::new(),
|
||||
value: default,
|
||||
} }
|
||||
|
||||
pub fn value(&self) -> &DateTime<Utc> { &self.value }
|
||||
}
|
||||
|
||||
impl NdpcParameter for ParamDateTimeLocal {
|
||||
fn upid(&self) -> &'static str { self.upid}
|
||||
fn label(&self) -> &str { &self.label}
|
||||
fn description(&self) -> &str { &self.description }
|
||||
fn unit(&self) -> Option<&str> { match &self.unit {None=>None, Some(s)=>Some(s)} }
|
||||
fn meta(&self) -> &NdpcParameterMetaData { &self.meta}
|
||||
fn meta_mut(&mut self) -> &mut NdpcParameterMetaData { &mut self.meta }
|
||||
|
||||
fn value_from_string(&mut self, value: &str) -> Result<(), SsloError> {
|
||||
self.value = match DateTime::parse_from_rfc3339(value) {
|
||||
Ok(dt) => dt.to_utc(),
|
||||
Err(e) => {log::warn!("Invalid value='{}' for upid={}: {}", value, self.upid, e);
|
||||
return Err(SsloError::NdpcValueMismatch(self.upid.to_string(), value.to_string()))}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn value2str(&self) -> String { format!("{:?}", self.value) }
|
||||
|
||||
fn html_input(&self) -> String {
|
||||
format!("<input type=\"datetime-local\" data-utc=\"{:?}\"/>", self.value)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,14 +4,14 @@ use crate::error::SsloError;
|
|||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[derive(ToSchema)]
|
||||
enum NdpcValueInheritance {
|
||||
pub enum NdpcValueInheritance {
|
||||
Inherit,
|
||||
Overwrite,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[derive(ToSchema)]
|
||||
enum NdpcAccess {
|
||||
pub enum NdpcAccess {
|
||||
Locked,
|
||||
Unlocked,
|
||||
}
|
||||
|
|
@ -64,6 +64,8 @@ pub trait NdpcParameter : Send {
|
|||
fn meta(&self) -> &NdpcParameterMetaData;
|
||||
fn meta_mut(&mut self) -> &mut NdpcParameterMetaData;
|
||||
|
||||
fn acces(&self) -> NdpcAccess { self.meta().access.clone() }
|
||||
|
||||
/// Shall set the parameter value
|
||||
/// If the passed value is invalid, the parameter shall remain unchanged
|
||||
fn value_from_string(&mut self, value: &str) -> Result<(), SsloError>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue