Added support for external calendars #14
| @@ -93,6 +93,7 @@ pub async fn get_user_info( | |||||||
|             path: path.clone(), |             path: path.clone(), | ||||||
|             display_name: extract_calendar_name(path), |             display_name: extract_calendar_name(path), | ||||||
|             color: generate_calendar_color(path), |             color: generate_calendar_color(path), | ||||||
|  |             is_visible: true, // Default to visible | ||||||
|         }) |         }) | ||||||
|         .collect(); |         .collect(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -56,6 +56,7 @@ pub struct CalendarInfo { | |||||||
|     pub path: String, |     pub path: String, | ||||||
|     pub display_name: String, |     pub display_name: String, | ||||||
|     pub color: String, |     pub color: String, | ||||||
|  |     pub is_visible: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Deserialize)] | #[derive(Debug, Deserialize)] | ||||||
|   | |||||||
| @@ -1067,6 +1067,21 @@ pub fn App() -> Html { | |||||||
|                                     on_color_picker_toggle={on_color_picker_toggle} |                                     on_color_picker_toggle={on_color_picker_toggle} | ||||||
|                                     available_colors={(*available_colors).clone()} |                                     available_colors={(*available_colors).clone()} | ||||||
|                                     on_calendar_context_menu={on_calendar_context_menu} |                                     on_calendar_context_menu={on_calendar_context_menu} | ||||||
|  |                                     on_calendar_visibility_toggle={Callback::from({ | ||||||
|  |                                         let user_info = user_info.clone(); | ||||||
|  |                                         move |calendar_path: String| { | ||||||
|  |                                             let user_info = user_info.clone(); | ||||||
|  |                                             wasm_bindgen_futures::spawn_local(async move { | ||||||
|  |                                                 if let Some(mut info) = (*user_info).clone() { | ||||||
|  |                                                     // Toggle the visibility | ||||||
|  |                                                     if let Some(calendar) = info.calendars.iter_mut().find(|c| c.path == calendar_path) { | ||||||
|  |                                                         calendar.is_visible = !calendar.is_visible; | ||||||
|  |                                                         user_info.set(Some(info)); | ||||||
|  |                                                     } | ||||||
|  |                                                 } | ||||||
|  |                                             }); | ||||||
|  |                                         } | ||||||
|  |                                     })} | ||||||
|                                     current_view={(*current_view).clone()} |                                     current_view={(*current_view).clone()} | ||||||
|                                     on_view_change={on_view_change} |                                     on_view_change={on_view_change} | ||||||
|                                     current_theme={(*current_theme).clone()} |                                     current_theme={(*current_theme).clone()} | ||||||
|   | |||||||
| @@ -108,10 +108,11 @@ pub fn Calendar(props: &CalendarProps) -> Html { | |||||||
|         let external_events = props.external_calendar_events.clone(); // Clone before the effect |         let external_events = props.external_calendar_events.clone(); // Clone before the effect | ||||||
|         let view = props.view.clone(); // Clone before the effect |         let view = props.view.clone(); // Clone before the effect | ||||||
|          |          | ||||||
|         use_effect_with((*current_date, view.clone(), external_events.len()), move |(date, _view, _external_len)| { |         use_effect_with((*current_date, view.clone(), external_events.len(), props.user_info.clone()), move |(date, _view, _external_len, user_info)| { | ||||||
|             let auth_token: Option<String> = LocalStorage::get("auth_token").ok(); |             let auth_token: Option<String> = LocalStorage::get("auth_token").ok(); | ||||||
|             let date = *date; // Clone the date to avoid lifetime issues |             let date = *date; // Clone the date to avoid lifetime issues | ||||||
|             let external_events = external_events.clone(); // Clone external events to avoid lifetime issues |             let external_events = external_events.clone(); // Clone external events to avoid lifetime issues | ||||||
|  |             let user_info = user_info.clone(); // Clone user_info to avoid lifetime issues | ||||||
|              |              | ||||||
|             if let Some(token) = auth_token { |             if let Some(token) = auth_token { | ||||||
|                 let events = events.clone(); |                 let events = events.clone(); | ||||||
| @@ -148,8 +149,24 @@ pub fn Calendar(props: &CalendarProps) -> Html { | |||||||
|                         .await |                         .await | ||||||
|                     { |                     { | ||||||
|                         Ok(vevents) => { |                         Ok(vevents) => { | ||||||
|                             // Combine regular events with external calendar events |                             // Filter CalDAV events based on calendar visibility | ||||||
|                             let mut all_events = vevents; |                             let mut filtered_events = if let Some(user_info) = user_info.as_ref() { | ||||||
|  |                                 vevents.into_iter() | ||||||
|  |                                     .filter(|event| { | ||||||
|  |                                         if let Some(calendar_path) = event.calendar_path.as_ref() { | ||||||
|  |                                             // Find the calendar info for this event | ||||||
|  |                                             user_info.calendars.iter() | ||||||
|  |                                                 .find(|cal| &cal.path == calendar_path) | ||||||
|  |                                                 .map(|cal| cal.is_visible) | ||||||
|  |                                                 .unwrap_or(true) // Default to visible if not found | ||||||
|  |                                         } else { | ||||||
|  |                                             true // Show events without calendar path | ||||||
|  |                                         } | ||||||
|  |                                     }) | ||||||
|  |                                     .collect() | ||||||
|  |                             } else { | ||||||
|  |                                 vevents // Show all events if no user info | ||||||
|  |                             }; | ||||||
|                              |                              | ||||||
|                             // Mark external events as external by adding a special category |                             // Mark external events as external by adding a special category | ||||||
|                             let marked_external_events: Vec<VEvent> = external_events |                             let marked_external_events: Vec<VEvent> = external_events | ||||||
| @@ -161,9 +178,9 @@ pub fn Calendar(props: &CalendarProps) -> Html { | |||||||
|                                 }) |                                 }) | ||||||
|                                 .collect(); |                                 .collect(); | ||||||
|                              |                              | ||||||
|                             all_events.extend(marked_external_events); |                             filtered_events.extend(marked_external_events); | ||||||
|                              |                              | ||||||
|                             let grouped_events = CalendarService::group_events_by_date(all_events); |                             let grouped_events = CalendarService::group_events_by_date(filtered_events); | ||||||
|                             events.set(grouped_events); |                             events.set(grouped_events); | ||||||
|                             loading.set(false); |                             loading.set(false); | ||||||
|                         } |                         } | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ pub struct CalendarListItemProps { | |||||||
|     pub on_color_picker_toggle: Callback<String>,    // calendar_path |     pub on_color_picker_toggle: Callback<String>,    // calendar_path | ||||||
|     pub available_colors: Vec<String>, |     pub available_colors: Vec<String>, | ||||||
|     pub on_context_menu: Callback<(MouseEvent, String)>, // (event, calendar_path) |     pub on_context_menu: Callback<(MouseEvent, String)>, // (event, calendar_path) | ||||||
|  |     pub on_visibility_toggle: Callback<String>,      // calendar_path | ||||||
| } | } | ||||||
|  |  | ||||||
| #[function_component(CalendarListItem)] | #[function_component(CalendarListItem)] | ||||||
| @@ -32,44 +33,59 @@ pub fn calendar_list_item(props: &CalendarListItemProps) -> Html { | |||||||
|         }) |         }) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     let on_visibility_toggle = { | ||||||
|  |         let cal_path = props.calendar.path.clone(); | ||||||
|  |         let on_visibility_toggle = props.on_visibility_toggle.clone(); | ||||||
|  |         Callback::from(move |_| { | ||||||
|  |             on_visibility_toggle.emit(cal_path.clone()); | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     html! { |     html! { | ||||||
|         <li key={props.calendar.path.clone()} oncontextmenu={on_context_menu}> |         <li key={props.calendar.path.clone()} oncontextmenu={on_context_menu}> | ||||||
|             <span class="calendar-color" |             <div class="calendar-info"> | ||||||
|                   style={format!("background-color: {}", props.calendar.color)} |                 <input | ||||||
|                   onclick={on_color_click}> |                     type="checkbox" | ||||||
|                 { |                     checked={props.calendar.is_visible} | ||||||
|                     if props.color_picker_open { |                     onchange={on_visibility_toggle} | ||||||
|                         html! { |                 /> | ||||||
|                             <div class="color-picker"> |                 <span class="calendar-color" | ||||||
|                                 { |                       style={format!("background-color: {}", props.calendar.color)} | ||||||
|                                     props.available_colors.iter().map(|color| { |                       onclick={on_color_click}> | ||||||
|                                         let color_str = color.clone(); |                     { | ||||||
|                                         let cal_path = props.calendar.path.clone(); |                         if props.color_picker_open { | ||||||
|                                         let on_color_change = props.on_color_change.clone(); |                             html! { | ||||||
|  |                                 <div class="color-picker"> | ||||||
|  |                                     { | ||||||
|  |                                         props.available_colors.iter().map(|color| { | ||||||
|  |                                             let color_str = color.clone(); | ||||||
|  |                                             let cal_path = props.calendar.path.clone(); | ||||||
|  |                                             let on_color_change = props.on_color_change.clone(); | ||||||
|  |  | ||||||
|                                         let on_color_select = Callback::from(move |_: MouseEvent| { |                                             let on_color_select = Callback::from(move |_: MouseEvent| { | ||||||
|                                             on_color_change.emit((cal_path.clone(), color_str.clone())); |                                                 on_color_change.emit((cal_path.clone(), color_str.clone())); | ||||||
|                                         }); |                                             }); | ||||||
|  |  | ||||||
|                                         let is_selected = props.calendar.color == *color; |                                             let is_selected = props.calendar.color == *color; | ||||||
|                                         let class_name = if is_selected { "color-option selected" } else { "color-option" }; |                                             let class_name = if is_selected { "color-option selected" } else { "color-option" }; | ||||||
|  |  | ||||||
|                                         html! { |                                             html! { | ||||||
|                                             <div class={class_name} |                                                 <div class={class_name} | ||||||
|                                                  style={format!("background-color: {}", color)} |                                                      style={format!("background-color: {}", color)} | ||||||
|                                                  onclick={on_color_select}> |                                                      onclick={on_color_select}> | ||||||
|                                             </div> |                                                 </div> | ||||||
|                                         } |                                             } | ||||||
|                                     }).collect::<Html>() |                                         }).collect::<Html>() | ||||||
|                                 } |                                     } | ||||||
|                             </div> |                                 </div> | ||||||
|  |                             } | ||||||
|  |                         } else { | ||||||
|  |                             html! {} | ||||||
|                         } |                         } | ||||||
|                     } else { |  | ||||||
|                         html! {} |  | ||||||
|                     } |                     } | ||||||
|                 } |                 </span> | ||||||
|             </span> |                 <span class="calendar-name">{&props.calendar.display_name}</span> | ||||||
|             <span class="calendar-name">{&props.calendar.display_name}</span> |             </div> | ||||||
|         </li> |         </li> | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -110,6 +110,7 @@ pub struct SidebarProps { | |||||||
|     pub on_color_picker_toggle: Callback<String>, |     pub on_color_picker_toggle: Callback<String>, | ||||||
|     pub available_colors: Vec<String>, |     pub available_colors: Vec<String>, | ||||||
|     pub on_calendar_context_menu: Callback<(MouseEvent, String)>, |     pub on_calendar_context_menu: Callback<(MouseEvent, String)>, | ||||||
|  |     pub on_calendar_visibility_toggle: Callback<String>, | ||||||
|     pub current_view: ViewMode, |     pub current_view: ViewMode, | ||||||
|     pub on_view_change: Callback<ViewMode>, |     pub on_view_change: Callback<ViewMode>, | ||||||
|     pub current_theme: Theme, |     pub current_theme: Theme, | ||||||
| @@ -221,6 +222,7 @@ pub fn sidebar(props: &SidebarProps) -> Html { | |||||||
|                                                     on_color_picker_toggle={props.on_color_picker_toggle.clone()} |                                                     on_color_picker_toggle={props.on_color_picker_toggle.clone()} | ||||||
|                                                     available_colors={props.available_colors.clone()} |                                                     available_colors={props.available_colors.clone()} | ||||||
|                                                     on_context_menu={props.on_calendar_context_menu.clone()} |                                                     on_context_menu={props.on_calendar_context_menu.clone()} | ||||||
|  |                                                     on_visibility_toggle={props.on_calendar_visibility_toggle.clone()} | ||||||
|                                                 /> |                                                 /> | ||||||
|                                             } |                                             } | ||||||
|                                         }).collect::<Html>() |                                         }).collect::<Html>() | ||||||
|   | |||||||
| @@ -44,6 +44,7 @@ pub struct CalendarInfo { | |||||||
|     pub path: String, |     pub path: String, | ||||||
|     pub display_name: String, |     pub display_name: String, | ||||||
|     pub color: String, |     pub color: String, | ||||||
|  |     pub is_visible: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
| // CalendarEvent, EventStatus, and EventClass are now imported from shared library | // CalendarEvent, EventStatus, and EventClass are now imported from shared library | ||||||
|   | |||||||
| @@ -3822,6 +3822,29 @@ body { | |||||||
|     flex-shrink: 0; |     flex-shrink: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* CalDAV Calendar Styles */ | ||||||
|  | .calendar-info { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     gap: 0.75rem; | ||||||
|  |     padding: 0.75rem; | ||||||
|  |     border-radius: 8px; | ||||||
|  |     transition: all 0.2s ease; | ||||||
|  |     cursor: pointer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .calendar-info:hover { | ||||||
|  |     background: rgba(255, 255, 255, 0.1); | ||||||
|  |     transform: translateX(2px); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .calendar-info input[type="checkbox"] { | ||||||
|  |     width: 16px; | ||||||
|  |     height: 16px; | ||||||
|  |     accent-color: rgba(255, 255, 255, 0.8); | ||||||
|  |     cursor: pointer; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Create External Calendar Button */ | /* Create External Calendar Button */ | ||||||
| .create-external-calendar-button { | .create-external-calendar-button { | ||||||
|     background: rgba(255, 255, 255, 0.15); |     background: rgba(255, 255, 255, 0.15); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user