Skip to content
This repository has been archived by the owner on Jan 5, 2025. It is now read-only.

Commit

Permalink
fix proper yaml selector
Browse files Browse the repository at this point in the history
  • Loading branch information
waozixyz committed Dec 18, 2024
1 parent 9be0284 commit a192ba7
Show file tree
Hide file tree
Showing 5 changed files with 398 additions and 159 deletions.
147 changes: 93 additions & 54 deletions src/components/timeline/events_view.rs
Original file line number Diff line number Diff line change
@@ -1,76 +1,115 @@
use crate::managers::timeline_manager::get_timeline_manager;
use crate::models::timeline::{LifePeriodEvent, Yaml};
use chrono::{Duration, Local, NaiveDate};
use dioxus::prelude::*;
use uuid::Uuid;

#[component]
pub fn EventView(selected_life_period_id: Uuid) -> Element {
let yaml_state = use_context::<Signal<Yaml>>();
let mut events = use_signal(|| None::<Result<Vec<LifePeriodEvent>, String>>);
let mut timeline = use_signal(|| None::<Result<Yaml, String>>);

let life_period = use_memo(move || {
yaml_state()
.life_periods
.iter()
.find(|p| p.id == Some(selected_life_period_id))
.cloned()
});
// Load data effect
{
let mut events = events.clone();
let mut timeline = timeline.clone();
use_effect(move || {
spawn(async move {
let events_result = get_timeline_manager()
.get_period_events(selected_life_period_id)
.await;
events.set(Some(events_result));

match life_period() {
Some(period) => {
let events = &period.events;
if events.is_empty() {
return rsx! {
div { class: "event-view-empty",
h2 { "No events in this life period" }
p { "This life period from {period.start} currently has no events." }
p { "You can add events to this period to track important moments or milestones." }
}
};
}
let timeline_result = get_timeline_manager().get_timeline().await;
timeline.set(Some(timeline_result));
});
});
}

let start_date = events
.iter()
.filter_map(|event| NaiveDate::parse_from_str(&event.start, "%Y-%m-%d").ok())
.min()
.unwrap_or_else(|| {
NaiveDate::parse_from_str(&period.start, "%Y-%m")
.unwrap_or(Local::now().date_naive())
});
let events_ref = events.read();
let timeline_ref = timeline.read();

let end_date = yaml_state()
match (&*events_ref, &*timeline_ref) {
(Some(Ok(events)), Some(Ok(yaml))) => {
let period = yaml
.life_periods
.iter()
.find(|p| p.start > period.start)
.and_then(|next_period| {
NaiveDate::parse_from_str(&format!("{}-01", next_period.start), "%Y-%m-%d").ok()
})
.unwrap_or_else(|| Local::now().date_naive());
.find(|p| p.id == Some(selected_life_period_id))
.cloned();

let total_days = (end_date - start_date).num_days() as usize;
let cols = 28;

rsx! {
div {
class: "event-view",
style: "grid-template-columns: repeat({cols}, 1fr);",
{(0..total_days).map(|day| {
let date = start_date + Duration::days(day as i64);
let color = get_color_for_event(&date, events, &end_date);
rsx! {
div {
key: "{day}",
class: "event-cell",
style: "background-color: {color};",
title: "{date}"
match period {
Some(period) => {
if events.is_empty() {
return rsx! {
div { class: "event-view-empty",
h2 { "No events in this life period" }
p { "This life period from {period.start} currently has no events." }
p { "You can add events to this period to track important moments or milestones." }
}
};
}

let start_date = events
.iter()
.filter_map(|event| {
NaiveDate::parse_from_str(&event.start, "%Y-%m-%d").ok()
})
.min()
.unwrap_or_else(|| {
NaiveDate::parse_from_str(&period.start, "%Y-%m")
.unwrap_or(Local::now().date_naive())
});

let end_date = yaml
.life_periods
.iter()
.find(|p| p.start > period.start)
.and_then(|next_period| {
NaiveDate::parse_from_str(
&format!("{}-01", next_period.start),
"%Y-%m-%d",
)
.ok()
})
.unwrap_or_else(|| Local::now().date_naive());

let total_days = (end_date - start_date).num_days() as usize;
let cols = 28;

rsx! {
div {
class: "event-view",
style: "grid-template-columns: repeat({cols}, 1fr);",
{(0..total_days).map(|day| {
let date = start_date + Duration::days(day as i64);
let color = get_color_for_event(&date, &events, &end_date);
rsx! {
div {
key: "{day}",
class: "event-cell",
style: "background-color: {color};",
title: "{date}"
}
}
})}
}
})}
}
}
None => rsx! {
div { class: "event-view-not-found",
"Selected life period not found."
}
},
}
}
None => rsx! {
div { class: "event-view-not-found",
"Selected life period not found."
(Some(Err(ref e)), _) | (_, Some(Err(ref e))) => rsx! {
div { class: "error-message",
"Failed to load data: {e}"
}
},
_ => rsx! {
div { class: "loading-message",
"Loading..."
}
},
}
Expand Down
150 changes: 104 additions & 46 deletions src/components/timeline/top_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,62 +6,94 @@ use arboard::Clipboard;
use dioxus::prelude::*;
use qrcode::render::svg;
use qrcode::QrCode;
use tracing::error;
use tracing::{debug, error};

#[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
use wl_clipboard_rs::copy::{MimeType, Options as WlOptions, Source};

use crate::utils::compression::compress_and_encode;
#[cfg(target_arch = "wasm32")]
use crate::utils::screenshot::share_screenshot;

#[component]
fn YamlSelector(
app_state: Signal<MyLifeApp>,
yaml_state: Signal<Yaml>,
available_timelines: Signal<Vec<String>>,
) -> Element {
// Add a loading state to prevent multiple selections while loading
let mut is_switching = use_signal(|| false);
// Add state to track the actual current timeline
let mut current_timeline = use_signal(|| app_state().selected_yaml.clone());

rsx! {
select {
value: "{app_state().selected_yaml}",
onchange: {
move |evt: Event<FormData>| {
div {
class: "yaml-selector-container",
select {
disabled: is_switching(),
value: "{current_timeline()}",
onchange: move |evt: Event<FormData>| {
let selected_yaml = evt.value().to_string();
use_future(move || {
let selected_yaml = selected_yaml.clone();
async move {
app_state.write().selected_yaml = selected_yaml.clone();
if let Ok(new_yaml) = get_timeline_manager().get_timeline_by_name(&selected_yaml).await {
yaml_state.set(new_yaml.clone());
// Also update the timeline in the manager
if let Err(e) = get_timeline_manager().update_timeline(&new_yaml.clone()).await {
error!("Failed to update timeline: {}", e);
}
let previous_yaml = current_timeline();

if selected_yaml == previous_yaml {
return;
}

is_switching.set(true);

spawn(async move {
let timeline_manager = get_timeline_manager();
debug!("Attempting to switch from '{}' to '{}'", previous_yaml, selected_yaml);

match timeline_manager.select_timeline(&selected_yaml).await {
Ok(new_yaml) => {
debug!("Successfully switched to '{}'", selected_yaml);
// Update both states only after successful switch
app_state.write().selected_yaml = selected_yaml.clone();
yaml_state.set(new_yaml);
current_timeline.set(selected_yaml);
}
Err(e) => {
error!("Failed to switch to timeline '{}': {}", selected_yaml, e);
// Keep the previous selection on failure
current_timeline.set(previous_yaml);
}
}

is_switching.set(false);
});
},
if available_timelines().is_empty() {
option {
value: "default",
"default"
}

} else {

{ available_timelines.read().iter().map(|name| {
rsx! {
option {
value: "{name}",
selected: name == &current_timeline(),
"{name}"
}
}
})}

}
},
if available_timelines().is_empty() {
option {
value: "default",
"default"
}
// Optional: Add loading indicator
{if is_switching() {
rsx! {
span { class: "loading-indicator", "⟳" }
}
} else {
{ available_timelines.read().iter().map(|name| {
rsx! {
option {
value: "{name}",
selected: name == &app_state().selected_yaml,
"{name}"
}
}
})}
}
rsx! {}
}}
}
}
}

#[component]
pub fn TopPanel(y: String) -> Element {
let mut app_state = use_context::<Signal<MyLifeApp>>();
Expand All @@ -74,17 +106,54 @@ pub fn TopPanel(y: String) -> Element {
let available_timelines = use_signal(Vec::new);

// Load timeline functionality

let load_timeline = move |_| {
let mut yaml_state = yaml_state.clone();
let mut app_state = app_state.clone();

use_future(move || async move {
if let Some((name, new_yaml)) = get_timeline_manager().import_timeline().await {
// Update both states atomically
yaml_state.set(new_yaml.clone());
app_state.write().selected_yaml = name;
app_state.write().selected_yaml = name.clone();

// Make sure to select the imported timeline
if let Err(e) = get_timeline_manager().select_timeline(&name).await {
error!("Failed to switch to imported timeline: {}", e);
}
} else {
error!("Failed to import timeline");
}
});
};

// Update the life expectancy handler
let life_expectancy_handler = move |evt: Event<FormData>| {
let mut yaml_state = yaml_state.clone();

if let Ok(value) = evt.value().parse() {
yaml_state.write().life_expectancy = value;

// Update timeline after changing life expectancy
use_future(move || async move {
if let Err(e) = get_timeline_manager().update_timeline(&yaml_state()).await {
error!("Failed to update timeline: {}", e);
}
});
} else {
error!("Failed to parse life expectancy: {}", evt.value());
}
};

use_effect(move || {
to_owned![available_timelines];
spawn(async move {
let timelines = get_timeline_manager().get_available_timelines().await;
available_timelines.set(timelines);
});
(|| ())()
});

// Export timeline functionality
let export_timeline = move |_| {
use_future(move || async move {
Expand Down Expand Up @@ -219,19 +288,7 @@ pub fn TopPanel(y: String) -> Element {
},
select {
value: "{yaml_state().life_expectancy}",
onchange: move |evt| {
if let Ok(value) = evt.value().parse() {
yaml_state.write().life_expectancy = value;
// Update timeline after changing life expectancy
use_future(move || async move {
if let Err(e) = get_timeline_manager().update_timeline(&yaml_state()).await {
error!("Failed to update timeline: {}", e);
}
});
} else {
error!("Failed to parse life expectancy: {}", evt.value());
}
},
onchange: life_expectancy_handler,
{
(40..=120).map(|year| {
rsx! {
Expand All @@ -245,6 +302,7 @@ pub fn TopPanel(y: String) -> Element {
}
}
}

}
}
// Screenshot Modal
Expand Down
Loading

0 comments on commit a192ba7

Please sign in to comment.