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