Full Printing Now Available (with some bugs) #20
@@ -5,7 +5,7 @@
 | 
			
		||||
 | 
			
		||||
.print-preview-modal {
 | 
			
		||||
    width: 90vw;
 | 
			
		||||
    max-width: 1200px;
 | 
			
		||||
    max-width: 1600px;
 | 
			
		||||
    height: auto; /* Let modal size itself based on content */
 | 
			
		||||
    max-height: 90vh; /* But don't exceed viewport */
 | 
			
		||||
    margin: 5vh auto;
 | 
			
		||||
@@ -1021,60 +1021,6 @@
 | 
			
		||||
/* End hour 24 (midnight next day) hides no hours since it includes the full day */
 | 
			
		||||
.print-preview-paper[data-end-hour="24"] { /* Shows all hours - no additional hiding needed */ }
 | 
			
		||||
 | 
			
		||||
/* Event positioning adjustments - shift events up when start hours are hidden */
 | 
			
		||||
/* Each hidden hour = 60px in 30-min mode, 120px in 15-min mode */
 | 
			
		||||
 | 
			
		||||
/* Reposition events to align with visible time labels */
 | 
			
		||||
/* 30-minute mode: Each hidden hour = 60px to shift up */
 | 
			
		||||
.print-preview-paper[data-start-hour="1"] .week-event { transform: translateY(-60px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="2"] .week-event { transform: translateY(-120px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="3"] .week-event { transform: translateY(-180px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="4"] .week-event { transform: translateY(-240px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="5"] .week-event { transform: translateY(-300px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="6"] .week-event { transform: translateY(-360px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="7"] .week-event { transform: translateY(-420px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="8"] .week-event { transform: translateY(-480px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="9"] .week-event { transform: translateY(-540px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="10"] .week-event { transform: translateY(-600px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="11"] .week-event { transform: translateY(-660px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="12"] .week-event { transform: translateY(-720px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="13"] .week-event { transform: translateY(-780px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="14"] .week-event { transform: translateY(-840px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="15"] .week-event { transform: translateY(-900px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="16"] .week-event { transform: translateY(-960px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="17"] .week-event { transform: translateY(-1020px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="18"] .week-event { transform: translateY(-1080px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="19"] .week-event { transform: translateY(-1140px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="20"] .week-event { transform: translateY(-1200px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="21"] .week-event { transform: translateY(-1260px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="22"] .week-event { transform: translateY(-1320px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="23"] .week-event { transform: translateY(-1380px); }
 | 
			
		||||
 | 
			
		||||
/* 15-minute mode: Each hidden hour = 120px to shift up */
 | 
			
		||||
.print-preview-paper[data-start-hour="1"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-120px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="2"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-240px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="3"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-360px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="4"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-480px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="5"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-600px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="6"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-720px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="7"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-840px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="8"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-960px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="9"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-1080px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="10"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-1200px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="11"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-1320px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="12"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-1440px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="13"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-1560px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="14"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-1680px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="15"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-1800px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="16"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-1920px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="17"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-2040px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="18"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-2160px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="19"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-2280px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="20"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-2400px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="21"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-2520px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="22"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-2640px); }
 | 
			
		||||
.print-preview-paper[data-start-hour="23"]:has(.time-slot.quarter-mode) .week-event { transform: translateY(-2760px); }
 | 
			
		||||
 | 
			
		||||
/* Hide events that are completely outside the visible time range */
 | 
			
		||||
/* Clip events outside the visible hour range using overflow: hidden */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -140,6 +140,7 @@ pub fn PrintPreviewModal(props: &PrintPreviewModalProps) -> Html {
 | 
			
		||||
    // Calculate print dimensions for the current hour range
 | 
			
		||||
    let (base_unit, pixels_per_hour, _available_height) = calculate_print_dimensions(*start_hour, *end_hour, props.time_increment);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    html! {
 | 
			
		||||
        <div class="modal-backdrop print-preview-modal-backdrop" onclick={backdrop_click}>
 | 
			
		||||
            <div class="modal-content print-preview-modal">
 | 
			
		||||
@@ -239,6 +240,7 @@ pub fn PrintPreviewModal(props: &PrintPreviewModalProps) -> Html {
 | 
			
		||||
                                                time_increment={props.time_increment}
 | 
			
		||||
                                                print_mode={true}
 | 
			
		||||
                                                print_pixels_per_hour={Some(pixels_per_hour)}
 | 
			
		||||
                                                print_start_hour={Some(*start_hour)}
 | 
			
		||||
                                            />
 | 
			
		||||
                                        },
 | 
			
		||||
                                        ViewMode::Month => html! {
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,8 @@ pub struct WeekViewProps {
 | 
			
		||||
    pub print_mode: bool,
 | 
			
		||||
    #[prop_or_default]
 | 
			
		||||
    pub print_pixels_per_hour: Option<f64>,
 | 
			
		||||
    #[prop_or_default]
 | 
			
		||||
    pub print_start_hour: Option<u32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, PartialEq)]
 | 
			
		||||
@@ -730,7 +732,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
 | 
			
		||||
                                        <div class="events-container">
 | 
			
		||||
                                            {
 | 
			
		||||
                                                day_events.iter().enumerate().filter_map(|(event_idx, event)| {
 | 
			
		||||
                                                    let (start_pixels, duration_pixels, is_all_day) = calculate_event_position(event, *date, props.time_increment, props.print_pixels_per_hour);
 | 
			
		||||
                                                    let (start_pixels, duration_pixels, is_all_day) = calculate_event_position(event, *date, props.time_increment, props.print_pixels_per_hour, props.print_start_hour);
 | 
			
		||||
 | 
			
		||||
                                                    // Skip all-day events (they're rendered in the header)
 | 
			
		||||
                                                    if is_all_day {
 | 
			
		||||
@@ -760,6 +762,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
 | 
			
		||||
                                                        let date_for_drag = *date;
 | 
			
		||||
                                                        let time_increment = props.time_increment;
 | 
			
		||||
                                                        let print_pixels_per_hour = props.print_pixels_per_hour;
 | 
			
		||||
                                                        let print_start_hour = props.print_start_hour;
 | 
			
		||||
                                                        Callback::from(move |e: MouseEvent| {
 | 
			
		||||
                                                            e.stop_propagation(); // Prevent drag-to-create from starting on event clicks
 | 
			
		||||
 | 
			
		||||
@@ -773,7 +776,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
 | 
			
		||||
                                                            let click_y_relative = if click_y_relative > 0.0 { click_y_relative } else { e.offset_y() as f64 };
 | 
			
		||||
 | 
			
		||||
                                                            // Get event's current position in day column coordinates
 | 
			
		||||
                                                            let (event_start_pixels, _, _) = calculate_event_position(&event_for_drag, date_for_drag, time_increment, print_pixels_per_hour);
 | 
			
		||||
                                                            let (event_start_pixels, _, _) = calculate_event_position(&event_for_drag, date_for_drag, time_increment, print_pixels_per_hour, print_start_hour);
 | 
			
		||||
                                                            let event_start_pixels = event_start_pixels as f64;
 | 
			
		||||
 | 
			
		||||
                                                            // Convert click position to day column coordinates
 | 
			
		||||
@@ -1059,7 +1062,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
 | 
			
		||||
                                                            };
 | 
			
		||||
 | 
			
		||||
                                                            // Calculate positions for the preview
 | 
			
		||||
                                                            let (original_start_pixels, _, _) = calculate_event_position(event, drag.start_date, props.time_increment, props.print_pixels_per_hour);
 | 
			
		||||
                                                            let (original_start_pixels, _, _) = calculate_event_position(event, drag.start_date, props.time_increment, props.print_pixels_per_hour, props.print_start_hour);
 | 
			
		||||
                                                            let original_duration = original_end.signed_duration_since(event.dtstart.with_timezone(&chrono::Local).naive_local());
 | 
			
		||||
                                                            let original_end_pixels = original_start_pixels + (original_duration.num_minutes() as f32);
 | 
			
		||||
 | 
			
		||||
@@ -1089,7 +1092,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
 | 
			
		||||
                                                            let original_start = event.dtstart.with_timezone(&chrono::Local).naive_local();
 | 
			
		||||
 | 
			
		||||
                                                            // Calculate positions for the preview
 | 
			
		||||
                                                            let (original_start_pixels, _, _) = calculate_event_position(event, drag.start_date, props.time_increment, props.print_pixels_per_hour);
 | 
			
		||||
                                                            let (original_start_pixels, _, _) = calculate_event_position(event, drag.start_date, props.time_increment, props.print_pixels_per_hour, props.print_start_hour);
 | 
			
		||||
 | 
			
		||||
                                                            let new_end_pixels = drag.current_y;
 | 
			
		||||
                                                            let new_height = (new_end_pixels - original_start_pixels as f64).max(20.0);
 | 
			
		||||
@@ -1223,7 +1226,7 @@ fn pixels_to_time(pixels: f64, time_increment: u32) -> NaiveTime {
 | 
			
		||||
    NaiveTime::from_hms_opt(hours, minutes, 0).unwrap_or(NaiveTime::from_hms_opt(0, 0, 0).unwrap())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn calculate_event_position(event: &VEvent, date: NaiveDate, time_increment: u32, print_pixels_per_hour: Option<f64>) -> (f32, f32, bool) {
 | 
			
		||||
fn calculate_event_position(event: &VEvent, date: NaiveDate, time_increment: u32, print_pixels_per_hour: Option<f64>, print_start_hour: Option<u32>) -> (f32, f32, bool) {
 | 
			
		||||
    // Convert UTC times to local time for display
 | 
			
		||||
    let local_start = event.dtstart.with_timezone(&Local);
 | 
			
		||||
    let event_date = local_start.date_naive();
 | 
			
		||||
@@ -1243,15 +1246,23 @@ fn calculate_event_position(event: &VEvent, date: NaiveDate, time_increment: u32
 | 
			
		||||
        return (0.0, 30.0, true); // Position at top, 30px height, is_all_day = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate start position in pixels from midnight
 | 
			
		||||
    // Calculate start position in pixels
 | 
			
		||||
    let start_hour = local_start.hour() as f32;
 | 
			
		||||
    let start_minute = local_start.minute() as f32;
 | 
			
		||||
    let pixels_per_hour = if let Some(print_pph) = print_pixels_per_hour {
 | 
			
		||||
        print_pph as f32 // Use print mode calculation
 | 
			
		||||
        print_pph as f32 // Use the dynamic print mode calculation
 | 
			
		||||
    } else {
 | 
			
		||||
        if time_increment == 15 { 120.0 } else { 60.0 } // Default values
 | 
			
		||||
    };
 | 
			
		||||
    let start_pixels = (start_hour + start_minute / 60.0) * pixels_per_hour;
 | 
			
		||||
    
 | 
			
		||||
    // In print mode, offset by the start hour to show relative position within visible range
 | 
			
		||||
    let hour_offset = if let Some(print_start) = print_start_hour {
 | 
			
		||||
        print_start as f32
 | 
			
		||||
    } else {
 | 
			
		||||
        0.0 // No offset for normal view (starts at midnight)
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    let start_pixels = ((start_hour + start_minute / 60.0) - hour_offset) * pixels_per_hour;
 | 
			
		||||
 | 
			
		||||
    // Calculate duration and height
 | 
			
		||||
    let duration_pixels = if let Some(end) = event.dtend {
 | 
			
		||||
@@ -1260,19 +1271,19 @@ fn calculate_event_position(event: &VEvent, date: NaiveDate, time_increment: u32
 | 
			
		||||
 | 
			
		||||
        // Handle events that span multiple days by capping at midnight
 | 
			
		||||
        if end_date > date {
 | 
			
		||||
            // Event continues past midnight, cap at 24:00 
 | 
			
		||||
            let max_pixels = 24.0 * pixels_per_hour;
 | 
			
		||||
            max_pixels - start_pixels
 | 
			
		||||
            // Event continues past midnight, cap at end of visible range
 | 
			
		||||
            let max_hour = if let Some(_print_start) = print_start_hour { 24.0 } else { 24.0 };
 | 
			
		||||
            let max_pixels = (max_hour - hour_offset) * pixels_per_hour;
 | 
			
		||||
            (max_pixels - start_pixels).max(20.0)
 | 
			
		||||
        } else {
 | 
			
		||||
            let end_hour = local_end.hour() as f32;
 | 
			
		||||
            let end_minute = local_end.minute() as f32;
 | 
			
		||||
            let end_pixels = (end_hour + end_minute / 60.0) * pixels_per_hour;
 | 
			
		||||
            let end_pixels = ((end_hour + end_minute / 60.0) - hour_offset) * pixels_per_hour;
 | 
			
		||||
            (end_pixels - start_pixels).max(20.0) // Minimum 20px height
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        pixels_per_hour // Default 1 hour if no end time
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    (start_pixels, duration_pixels, false) // is_all_day = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1313,7 +1324,7 @@ fn calculate_event_layout(events: &[VEvent], date: NaiveDate, time_increment: u3
 | 
			
		||||
                return None;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            let (_, _, _) = calculate_event_position(event, date, time_increment, None);
 | 
			
		||||
            let (_, _, _) = calculate_event_position(event, date, time_increment, None, None);
 | 
			
		||||
            let local_start = event.dtstart.with_timezone(&Local);
 | 
			
		||||
            let event_date = local_start.date_naive();
 | 
			
		||||
            if event_date == date || 
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user