diff --git a/rsc/css/league/main.css b/rsc/css/league/main.css
index 7c4bbd5..2988329 100644
--- a/rsc/css/league/main.css
+++ b/rsc/css/league/main.css
@@ -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;
-}
\ No newline at end of file
+ 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;
+}
diff --git a/rsc/js/main.js b/rsc/js/main.js
index 523732a..2c54bd4 100644
--- a/rsc/js/main.js
+++ b/rsc/js/main.js
@@ -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();
diff --git a/rsc/js/sslo_lib/ndpc.js b/rsc/js/sslo_lib/ndpc.js
index 12a9089..0143fa3 100644
--- a/rsc/js/sslo_lib/ndpc.js
+++ b/rsc/js/sslo_lib/ndpc.js
@@ -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
diff --git a/sslo_league/src/http/routes_html/presets.rs b/sslo_league/src/http/routes_html/presets.rs
index 9110fe9..4177ffe 100644
--- a/sslo_league/src/http/routes_html/presets.rs
+++ b/sslo_league/src/http/routes_html/presets.rs
@@ -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());
diff --git a/sslo_league/src/ndpc/coll_preset.rs b/sslo_league/src/ndpc/coll_preset.rs
index 88c6896..32909d8 100644
--- a/sslo_league/src/ndpc/coll_preset.rs
+++ b/sslo_league/src/ndpc/coll_preset.rs
@@ -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 = "
".to_string();
- html += &format!("
{}
", self.label);
+ let mut html = "
".to_string();
+ html += &format!("
{}
", self.label);
// general
html+= "
";
- // self.get_parameter("")
+ html += &self.html_view_general();
html+= "
";
html += "
";
html
}
+
+ fn html_view_general(&self) -> String {
+ let mut html = "
".to_string();
+
+ fn format_param(param: &dyn NdpcParameter, icon: &str) -> String {
+ format!("
{} {}
",
+ 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 += "
";
+ html
+ }
}
impl NdpcCollection for CollPreset {
diff --git a/sslo_league/src/ndpc/coll_preset/coll_general.rs b/sslo_league/src/ndpc/coll_preset/coll_general.rs
index bc953ca..b6fae6d 100644
--- a/sslo_league/src/ndpc/coll_preset/coll_general.rs
+++ b/sslo_league/src/ndpc/coll_preset/coll_general.rs
@@ -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 {
diff --git a/sslo_league/src/ndpc/coll_preset/coll_session.rs b/sslo_league/src/ndpc/coll_preset/coll_session.rs
index 3f18fed..d7042cb 100644
--- a/sslo_league/src/ndpc/coll_preset/coll_session.rs
+++ b/sslo_league/src/ndpc/coll_preset/coll_session.rs
@@ -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![] }
diff --git a/sslo_lib/src/ndpc2.rs b/sslo_lib/src/ndpc2.rs
index 2f25aea..fac10fc 100644
--- a/sslo_lib/src/ndpc2.rs
+++ b/sslo_lib/src/ndpc2.rs
@@ -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;
diff --git a/sslo_lib/src/ndpc2/param_date_time_local.rs b/sslo_lib/src/ndpc2/param_date_time_local.rs
new file mode 100644
index 0000000..ebd48ca
--- /dev/null
+++ b/sslo_lib/src/ndpc2/param_date_time_local.rs
@@ -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
,
+ meta: NdpcParameterMetaData,
+ value: DateTime,
+}
+
+impl ParamDateTimeLocal {
+ pub fn new(upid: &'static str, label: String, unit: Option, default: DateTime, description: String) -> Self { Self {
+ upid,
+ label,
+ description,
+ unit,
+ meta: NdpcParameterMetaData::new(),
+ value: default,
+ } }
+
+ pub fn value(&self) -> &DateTime { &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!("", self.value)
+ }
+}
\ No newline at end of file
diff --git a/sslo_lib/src/ndpc2/parameter.rs b/sslo_lib/src/ndpc2/parameter.rs
index ba2536e..becb25d 100644
--- a/sslo_lib/src/ndpc2/parameter.rs
+++ b/sslo_lib/src/ndpc2/parameter.rs
@@ -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>;