Implement dynamic base unit calculation for print preview scaling
- Add dynamic height calculation system based on selected hour range and time increment - Replace hardcoded CSS heights with CSS variables (--print-base-unit, --print-pixels-per-hour) - Update WeekView component with print mode support and dynamic event positioning - Optimize week header for print: reduced to 50px height with smaller fonts - Account for all borders and spacing in calculation (660px available content height) - Remove debug styling (blue borders, yellow backgrounds) - Ensure time slots, time labels, and events scale together proportionally - Perfect fit within 720px content area regardless of hour selection 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -315,27 +315,51 @@
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* Scale time slots appropriately in preview */
|
||||
/* Dynamic time slot heights based on calculated base unit */
|
||||
.print-preview-paper .time-slot {
|
||||
height: 60px !important; /* Default for 30-minute mode: 2 × 30px = 60px */
|
||||
min-height: 60px !important;
|
||||
max-height: 60px !important;
|
||||
height: calc(var(--print-pixels-per-hour) * 1px) !important;
|
||||
min-height: calc(var(--print-pixels-per-hour) * 1px) !important;
|
||||
max-height: calc(var(--print-pixels-per-hour) * 1px) !important;
|
||||
border-bottom: 1px solid #ddd !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* 15-minute mode (quarter-mode) gets more height */
|
||||
.print-preview-paper .time-slot.quarter-mode {
|
||||
height: 120px !important; /* 15-minute mode: 4 slots × 30px = 120px */
|
||||
min-height: 120px !important;
|
||||
max-height: 120px !important;
|
||||
/* Dynamic time label heights - should match time slots */
|
||||
.print-preview-paper .time-label {
|
||||
height: calc(var(--print-pixels-per-hour) * 1px) !important;
|
||||
min-height: calc(var(--print-pixels-per-hour) * 1px) !important;
|
||||
max-height: calc(var(--print-pixels-per-hour) * 1px) !important;
|
||||
}
|
||||
|
||||
/* Sub-hour time slot styling */
|
||||
/* Fixed week header height for print - smaller to maximize content */
|
||||
.print-preview-paper .week-day-header {
|
||||
height: 50px !important;
|
||||
min-height: 50px !important;
|
||||
max-height: 50px !important;
|
||||
padding: 0.25rem !important; /* Reduce padding to fit content */
|
||||
}
|
||||
|
||||
.print-preview-paper .weekday-name {
|
||||
font-size: 0.75rem !important; /* Slightly smaller font */
|
||||
margin-bottom: 0.125rem !important;
|
||||
}
|
||||
|
||||
.print-preview-paper .week-day-header .day-number {
|
||||
font-size: 1.25rem !important; /* Slightly smaller day number */
|
||||
}
|
||||
|
||||
/* 15-minute and 30-minute modes use the same calculated height */
|
||||
.print-preview-paper .time-slot.quarter-mode {
|
||||
height: calc(var(--print-pixels-per-hour) * 1px) !important;
|
||||
min-height: calc(var(--print-pixels-per-hour) * 1px) !important;
|
||||
max-height: calc(var(--print-pixels-per-hour) * 1px) !important;
|
||||
}
|
||||
|
||||
/* Sub-hour time slot styling - use dynamic base unit */
|
||||
.print-preview-paper .time-slot-half {
|
||||
height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
max-height: 30px !important;
|
||||
height: calc(var(--print-base-unit) * 1px) !important;
|
||||
min-height: calc(var(--print-base-unit) * 1px) !important;
|
||||
max-height: calc(var(--print-base-unit) * 1px) !important;
|
||||
border-bottom: 1px dotted #ddd !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
@@ -345,9 +369,9 @@
|
||||
}
|
||||
|
||||
.print-preview-paper .time-slot-quarter {
|
||||
height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
max-height: 30px !important;
|
||||
height: calc(var(--print-base-unit) * 1px) !important;
|
||||
min-height: calc(var(--print-base-unit) * 1px) !important;
|
||||
max-height: calc(var(--print-base-unit) * 1px) !important;
|
||||
border-bottom: 1px dotted #eee !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
@@ -356,23 +380,7 @@
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* Debug: Test if data attributes are working at all */
|
||||
.print-preview-paper .time-slot[data-hour] {
|
||||
border-left: 3px solid blue !important;
|
||||
}
|
||||
|
||||
.print-preview-paper .time-label[data-hour] {
|
||||
background-color: yellow !important;
|
||||
}
|
||||
|
||||
/* Test specific hour targeting */
|
||||
.print-preview-paper .time-slot[data-hour="0"] {
|
||||
border-left: 3px solid red !important;
|
||||
}
|
||||
|
||||
.print-preview-paper .time-label[data-hour="0"] {
|
||||
background-color: red !important;
|
||||
}
|
||||
/* Debug styles removed */
|
||||
|
||||
/* Use data attributes for precise hour targeting */
|
||||
/* Hide hours before start hour - both time slots AND time labels */
|
||||
@@ -1089,7 +1097,6 @@
|
||||
.print-preview-paper[data-start-hour][data-end-hour] .time-grid,
|
||||
.print-preview-paper[data-start-hour][data-end-hour] .week-day-column {
|
||||
min-height: 400px !important;
|
||||
border: 2px solid red !important; /* Debug border to see if this rule applies */
|
||||
}
|
||||
|
||||
/* Height adjustments based on visible hours = end_hour - start_hour */
|
||||
|
||||
@@ -122,6 +122,24 @@ pub fn PrintPreviewModal(props: &PrintPreviewModalProps) -> Html {
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate dynamic base unit for print preview
|
||||
let calculate_print_dimensions = |start_hour: u32, end_hour: u32, time_increment: u32| -> (f64, f64, f64) {
|
||||
let visible_hours = (end_hour - start_hour) as f64;
|
||||
let slots_per_hour = if time_increment == 15 { 4.0 } else { 2.0 };
|
||||
let header_height = 50.0; // Fixed week header height in print preview
|
||||
let header_border = 2.0; // Week header bottom border (2px solid)
|
||||
let container_spacing = 8.0; // Additional container spacing/margins
|
||||
let total_overhead = header_height + header_border + container_spacing;
|
||||
let available_height = 720.0 - total_overhead; // Available for time content
|
||||
let base_unit = available_height / (visible_hours * slots_per_hour);
|
||||
let pixels_per_hour = base_unit * slots_per_hour;
|
||||
|
||||
(base_unit, pixels_per_hour, available_height)
|
||||
};
|
||||
|
||||
// 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">
|
||||
@@ -200,9 +218,12 @@ pub fn PrintPreviewModal(props: &PrintPreviewModalProps) -> Html {
|
||||
})
|
||||
}}>
|
||||
<div class="print-preview-paper"
|
||||
data-start-hour={start_hour.to_string()}
|
||||
data-end-hour={end_hour.to_string()}
|
||||
style={format!("--print-start-hour: {}; --print-end-hour: {}; transform: scale({}); transform-origin: top center;", *start_hour, *end_hour, *zoom_level)}>
|
||||
data-start-hour={start_hour.to_string()}
|
||||
data-end-hour={end_hour.to_string()}
|
||||
style={format!(
|
||||
"--print-start-hour: {}; --print-end-hour: {}; --print-base-unit: {:.2}; --print-pixels-per-hour: {:.2}; transform: scale({}); transform-origin: top center;",
|
||||
*start_hour, *end_hour, base_unit, pixels_per_hour, *zoom_level
|
||||
)}>
|
||||
<div class="print-preview-content">
|
||||
{
|
||||
match props.view_mode {
|
||||
@@ -216,6 +237,8 @@ pub fn PrintPreviewModal(props: &PrintPreviewModalProps) -> Html {
|
||||
user_info={props.user_info.clone()}
|
||||
external_calendars={props.external_calendars.clone()}
|
||||
time_increment={props.time_increment}
|
||||
print_mode={true}
|
||||
print_pixels_per_hour={Some(pixels_per_hour)}
|
||||
/>
|
||||
},
|
||||
ViewMode::Month => html! {
|
||||
|
||||
@@ -42,6 +42,10 @@ pub struct WeekViewProps {
|
||||
pub context_menus_open: bool,
|
||||
#[prop_or_default]
|
||||
pub time_increment: u32,
|
||||
#[prop_or_default]
|
||||
pub print_mode: bool,
|
||||
#[prop_or_default]
|
||||
pub print_pixels_per_hour: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
@@ -726,7 +730,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);
|
||||
let (start_pixels, duration_pixels, is_all_day) = calculate_event_position(event, *date, props.time_increment, props.print_pixels_per_hour);
|
||||
|
||||
// Skip all-day events (they're rendered in the header)
|
||||
if is_all_day {
|
||||
@@ -755,6 +759,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
let event_for_drag = event.clone();
|
||||
let date_for_drag = *date;
|
||||
let time_increment = props.time_increment;
|
||||
let print_pixels_per_hour = props.print_pixels_per_hour;
|
||||
Callback::from(move |e: MouseEvent| {
|
||||
e.stop_propagation(); // Prevent drag-to-create from starting on event clicks
|
||||
|
||||
@@ -768,7 +773,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);
|
||||
let (event_start_pixels, _, _) = calculate_event_position(&event_for_drag, date_for_drag, time_increment, print_pixels_per_hour);
|
||||
let event_start_pixels = event_start_pixels as f64;
|
||||
|
||||
// Convert click position to day column coordinates
|
||||
@@ -1054,7 +1059,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);
|
||||
let (original_start_pixels, _, _) = calculate_event_position(event, drag.start_date, props.time_increment, props.print_pixels_per_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);
|
||||
|
||||
@@ -1084,7 +1089,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);
|
||||
let (original_start_pixels, _, _) = calculate_event_position(event, drag.start_date, props.time_increment, props.print_pixels_per_hour);
|
||||
|
||||
let new_end_pixels = drag.current_y;
|
||||
let new_height = (new_end_pixels - original_start_pixels as f64).max(20.0);
|
||||
@@ -1218,7 +1223,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) -> (f32, f32, bool) {
|
||||
fn calculate_event_position(event: &VEvent, date: NaiveDate, time_increment: u32, print_pixels_per_hour: Option<f64>) -> (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();
|
||||
@@ -1241,7 +1246,11 @@ fn calculate_event_position(event: &VEvent, date: NaiveDate, time_increment: u32
|
||||
// Calculate start position in pixels from midnight
|
||||
let start_hour = local_start.hour() as f32;
|
||||
let start_minute = local_start.minute() as f32;
|
||||
let pixels_per_hour = if time_increment == 15 { 120.0 } else { 60.0 };
|
||||
let pixels_per_hour = if let Some(print_pph) = print_pixels_per_hour {
|
||||
print_pph as f32 // Use 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;
|
||||
|
||||
// Calculate duration and height
|
||||
@@ -1304,7 +1313,7 @@ fn calculate_event_layout(events: &[VEvent], date: NaiveDate, time_increment: u3
|
||||
return None;
|
||||
}
|
||||
|
||||
let (_, _, _) = calculate_event_position(event, date, time_increment);
|
||||
let (_, _, _) = calculate_event_position(event, date, time_increment, 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