From 3d3ca9a81b1237e6b0f2cd14e6e22339bdf02196 Mon Sep 17 00:00:00 2001 From: Stanislav Pastushenko Date: Thu, 11 Dec 2025 19:32:38 +0100 Subject: [PATCH] start to clean up vibecode --- docker/linux-build.Dockerfile | 2 +- src/log_viewer_app.rs | 359 ++++++++++++++++++++++++++ src/main.rs | 463 +--------------------------------- src/ui/log_panel.rs | 152 +++++++++++ src/ui/mod.rs | 1 + src/ui/top_menu.rs | 15 +- 6 files changed, 527 insertions(+), 465 deletions(-) create mode 100644 src/log_viewer_app.rs create mode 100644 src/ui/log_panel.rs diff --git a/docker/linux-build.Dockerfile b/docker/linux-build.Dockerfile index 1d6ac36..babb05d 100644 --- a/docker/linux-build.Dockerfile +++ b/docker/linux-build.Dockerfile @@ -1,3 +1,3 @@ FROM rust:latest RUN apt update && \ - apt install nodejs npm -y \ No newline at end of file + apt install -y nodejs npm tar \ No newline at end of file diff --git a/src/log_viewer_app.rs b/src/log_viewer_app.rs new file mode 100644 index 0000000..d6794fa --- /dev/null +++ b/src/log_viewer_app.rs @@ -0,0 +1,359 @@ +use std::sync::Arc; +use eframe::egui; +use crate::config::AppConfig; +use crate::file_tab::FileTab; +use crate::highlight::HighlightManager; +use crate::search::{add_to_history, start_search, SearchParams, SearchState}; +use crate::tab_manager::{close_tab, open_file_dialog, IndexingState}; +use crate::ui::{render_highlight_editor, render_search_panel, render_tabs_panel, render_top_menu, SearchPanelState}; +use crate::ui::log_panel::{render_filter_panel, render_main_log_panel}; + +pub struct LogViewerApp { + tabs: Vec, + active_tab_index: usize, + search_panel_state: SearchPanelState, + indexing_state: IndexingState, + search_state: SearchState, + highlight_manager: HighlightManager, + first_frame: bool, +} + +struct KeyAction { + focus_search_input: bool, + execute_search: bool +} + +impl KeyAction { + + pub fn new() -> Self { + Self{ + focus_search_input: false, + execute_search: false, + } + } +} + +impl LogViewerApp { + pub fn new(config: AppConfig) -> Self { + Self { + tabs: Vec::new(), + active_tab_index: 0, + search_panel_state: SearchPanelState { + query: config.last_search_query, + case_sensitive: config.case_sensitive, + use_regex: config.use_regex, + history: config.search_history, + date_range_enabled: config.date_range_enabled, + date_format: config.date_format, + date_from: config.date_from, + date_to: config.date_to, + }, + indexing_state: IndexingState::new(), + search_state: SearchState::new(), + highlight_manager: HighlightManager::new(config.highlight_rules), + first_frame: true, + } + } + + fn active_tab(&self) -> Option<&FileTab> { + self.tabs.get(self.active_tab_index) + } + + fn active_tab_mut(&mut self) -> Option<&mut FileTab> { + self.tabs.get_mut(self.active_tab_index) + } + + fn save_config(&self) { + let config = AppConfig { + search_history: self.search_panel_state.history.clone(), + case_sensitive: self.search_panel_state.case_sensitive, + use_regex: self.search_panel_state.use_regex, + last_search_query: self.search_panel_state.query.clone(), + highlight_rules: self.highlight_manager.rules.clone(), + date_range_enabled: self.search_panel_state.date_range_enabled, + date_format: self.search_panel_state.date_format.clone(), + date_from: self.search_panel_state.date_from.clone(), + date_to: self.search_panel_state.date_to.clone(), + }; + config.save(); + } + + fn handle_open_file(&mut self, open_file_requested: bool) { + if open_file_requested && let Some(new_tab) = open_file_dialog(&self.indexing_state) { + self.tabs.push(new_tab); + self.active_tab_index = self.tabs.len() - 1; + } + } + + fn handle_close_tab(&mut self, index: usize) { + close_tab(&mut self.tabs, &mut self.active_tab_index, index); + } + + fn handle_search(&mut self) { + if let Some(tab) = self.active_tab() { + // Validate date format if date range is enabled + if self.search_panel_state.date_range_enabled { + use chrono::NaiveDateTime; + + // Validate format by trying to parse example dates + let test_date = "2025-01-01 00:00:00"; + if NaiveDateTime::parse_from_str(test_date, &self.search_panel_state.date_format).is_err() { + eprintln!("Invalid date format: {}", self.search_panel_state.date_format); + eprintln!("Expected format like: %Y-%m-%d %H:%M:%S"); + return; + } + + // Validate date_from and date_to can be parsed + if !self.search_panel_state.date_from.is_empty() { + if NaiveDateTime::parse_from_str(&self.search_panel_state.date_from, &self.search_panel_state.date_format).is_err() { + eprintln!("Invalid date_from format. Expected format: {}", self.search_panel_state.date_format); + return; + } + } + + if !self.search_panel_state.date_to.is_empty() { + if NaiveDateTime::parse_from_str(&self.search_panel_state.date_to, &self.search_panel_state.date_format).is_err() { + eprintln!("Invalid date_to format. Expected format: {}", self.search_panel_state.date_format); + return; + } + } + } + + let params = SearchParams { + query: self.search_panel_state.query.clone(), + case_sensitive: self.search_panel_state.case_sensitive, + use_regex: self.search_panel_state.use_regex, + date_range_enabled: self.search_panel_state.date_range_enabled, + date_format: self.search_panel_state.date_format.clone(), + date_from: self.search_panel_state.date_from.clone(), + date_to: self.search_panel_state.date_to.clone(), + }; + + let line_index = Arc::clone(&tab.line_index); + let file_path = tab.file_path.clone(); + + // Clear current results + if let Some(tab) = self.active_tab_mut() { + tab.filtered_lines.clear(); + } + + start_search(&self.search_state, params, line_index, file_path); + } + } + + fn handle_clear_search(&mut self) { + self.search_panel_state.query.clear(); + if let Some(tab) = self.active_tab_mut() { + tab.filtered_lines.clear(); + } + } + + fn handle_export_filtered(&mut self) { + use std::fs::File; + use std::io::Write; + use std::time::SystemTime; + + if let Some(tab) = self.active_tab() { + if tab.filtered_lines.is_empty() { + return; + } + + // Generate timestamped filename + let original_path = &tab.file_path; + let file_stem = original_path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("export"); + let extension = original_path + .extension() + .and_then(|s| s.to_str()) + .unwrap_or("log"); + + // Generate timestamp + let timestamp = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); + + // Create new filename: filename_timestamp.extension + let export_filename = format!("{}_{}.{}", file_stem, timestamp, extension); + let export_path = original_path.with_file_name(export_filename); + + // Write filtered lines to file + match File::create(&export_path) { + Ok(mut file) => { + // Get the line index and file handle + let line_index = Arc::clone(&tab.line_index); + let file_path = tab.file_path.clone(); + + // Open the original file for reading + if let Ok(original_file) = File::open(&file_path) { + let mut reader = std::io::BufReader::new(original_file); + + // Write each filtered line to the export file + for filtered_line in &tab.filtered_lines { + if let Some(content) = line_index.read_line(&mut reader, filtered_line.line_number) { + writeln!(file, "{}", content).ok(); + } + } + + // Open the exported file in a new tab + if let Ok(line_index) = crate::line_index::LineIndex::build(&export_path) { + if let Ok(exported_file) = File::open(&export_path) { + let new_tab = crate::file_tab::FileTab::new( + export_path, + line_index, + exported_file, + ); + self.tabs.push(new_tab); + self.active_tab_index = self.tabs.len() - 1; + } + } + } + } + Err(e) => { + eprintln!("Failed to create export file: {}", e); + } + } + } + } + + fn handle_keyboard_input(&mut self, ctx: &egui::Context) { + if let Some(tab) = self.active_tab_mut() { + tab.page_scroll_direction = ctx.input(|i| { + if i.key_pressed(egui::Key::PageDown) { + Some(1.0) + } else if i.key_pressed(egui::Key::PageUp) { + Some(-1.0) + } else { + None + } + }); + } + } + + fn update_search_results(&mut self) { + if let Some(filtered) = self.search_state.take_results() { + if let Some(tab) = self.active_tab_mut() { + tab.filtered_lines = filtered; + tab.filtered_scroll_offset = 0; + } + } + } + + fn handle_first_frame(&mut self, ctx: &egui::Context) { + if self.first_frame { + self.first_frame = false; + ctx.send_viewport_cmd(egui::ViewportCommand::Maximized(true)); + } + } +} + +fn handle_key_inputs(ctx: &egui::Context) -> KeyAction { + let mut key_action = KeyAction::new(); + + key_action.focus_search_input = ctx.input(|i| { + i.modifiers.ctrl && i.key_pressed(egui::Key::F) + }); + + // Check for Ctrl+Enter to execute search globally + key_action.execute_search = ctx.input(|i| { + i.modifiers.ctrl && i.key_pressed(egui::Key::Enter) + }); + + key_action +} + +impl eframe::App for LogViewerApp { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + + self.handle_first_frame(ctx); + + // Update search results if available + self.update_search_results(); + + // Render top menu + let top_menu_actions = render_top_menu( + ctx, + &mut self.highlight_manager, + &self.indexing_state + ); + + self.handle_open_file(top_menu_actions.open_file_requested); + + // Render highlight editor + let highlight_config_changed = render_highlight_editor(ctx, &mut self.highlight_manager); + if highlight_config_changed { + self.save_config(); + } + + // Render tabs + let mut close_tab_index = None; + render_tabs_panel( + ctx, + &self.tabs, + &mut self.active_tab_index, + &mut close_tab_index, + ); + + if let Some(index) = close_tab_index { + self.handle_close_tab(index); + } + + let keyboard_action = handle_key_inputs(ctx); + + // Render search panel + let match_count = self.active_tab().map(|t| t.filtered_lines.len()).unwrap_or(0); + let search_actions = render_search_panel( + ctx, + &mut self.search_panel_state, + &self.search_state, + match_count, + keyboard_action.focus_search_input, + ); + + if search_actions.execute_search || keyboard_action.focus_search_input { + add_to_history( + &mut self.search_panel_state.history, + &self.search_panel_state.query, + ); + self.handle_search(); + } + + if search_actions.clear_search { + self.handle_clear_search(); + } + + if search_actions.config_changed { + self.save_config(); + } + + // Render filtered view with improved styling + // Show filtered view if there's a query OR if date range is enabled + let show_filtered = !self.search_panel_state.query.is_empty() || self.search_panel_state.date_range_enabled; + let highlight_rules = self.highlight_manager.rules.clone(); + + let mut export_clicked = false; + if show_filtered { + if let Some(tab) = self.active_tab_mut() { + render_filter_panel(tab, ctx, &highlight_rules, &mut export_clicked); + } + } + + // Handle export after rendering the filtered view + if export_clicked { + self.handle_export_filtered(); + } + + // Handle keyboard input + self.handle_keyboard_input(ctx); + + // Render main view with improved styling + render_main_log_panel(ctx, &highlight_rules, self.active_tab_mut()); + + // Only request repaint if there are ongoing background operations + if self.indexing_state.is_indexing() || self.search_state.is_searching() { + ctx.request_repaint(); + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 2fb6014..c89fc9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,19 +10,12 @@ mod tab_manager; mod theme; mod types; mod ui; +mod log_viewer_app; use eframe::egui; -use std::sync::Arc; +use crate::log_viewer_app::LogViewerApp; use config::AppConfig; -use file_tab::FileTab; -use highlight::HighlightManager; -use search::{add_to_history, start_search, SearchParams, SearchState}; -use tab_manager::{close_tab, open_file_dialog, IndexingState}; -use ui::{ - render_highlight_editor, render_log_view, render_search_panel, render_tabs_panel, - render_top_menu, LogViewContext, SearchPanelState, -}; fn main() -> eframe::Result { let options = eframe::NativeOptions { @@ -40,462 +33,10 @@ fn main() -> eframe::Result { "RLogg", options, Box::new(move |cc| { - // Apply the modern dark purple theme theme::apply_theme(&cc.egui_ctx); Ok(Box::new(LogViewerApp::new(config))) }), ) } -struct LogViewerApp { - tabs: Vec, - active_tab_index: usize, - search_panel_state: SearchPanelState, - indexing_state: IndexingState, - search_state: SearchState, - highlight_manager: HighlightManager, - first_frame: bool, -} -impl LogViewerApp { - fn new(config: AppConfig) -> Self { - Self { - tabs: Vec::new(), - active_tab_index: 0, - search_panel_state: SearchPanelState { - query: config.last_search_query, - case_sensitive: config.case_sensitive, - use_regex: config.use_regex, - history: config.search_history, - date_range_enabled: config.date_range_enabled, - date_format: config.date_format, - date_from: config.date_from, - date_to: config.date_to, - }, - indexing_state: IndexingState::new(), - search_state: SearchState::new(), - highlight_manager: HighlightManager::new(config.highlight_rules), - first_frame: true, - } - } - - fn active_tab(&self) -> Option<&FileTab> { - self.tabs.get(self.active_tab_index) - } - - fn active_tab_mut(&mut self) -> Option<&mut FileTab> { - self.tabs.get_mut(self.active_tab_index) - } - - fn save_config(&self) { - let config = AppConfig { - search_history: self.search_panel_state.history.clone(), - case_sensitive: self.search_panel_state.case_sensitive, - use_regex: self.search_panel_state.use_regex, - last_search_query: self.search_panel_state.query.clone(), - highlight_rules: self.highlight_manager.rules.clone(), - date_range_enabled: self.search_panel_state.date_range_enabled, - date_format: self.search_panel_state.date_format.clone(), - date_from: self.search_panel_state.date_from.clone(), - date_to: self.search_panel_state.date_to.clone(), - }; - config.save(); - } - - fn handle_open_file(&mut self) { - if let Some(new_tab) = open_file_dialog(&self.indexing_state) { - self.tabs.push(new_tab); - self.active_tab_index = self.tabs.len() - 1; - } - } - - fn handle_close_tab(&mut self, index: usize) { - close_tab(&mut self.tabs, &mut self.active_tab_index, index); - } - - fn handle_search(&mut self) { - if let Some(tab) = self.active_tab() { - // Validate date format if date range is enabled - if self.search_panel_state.date_range_enabled { - use chrono::NaiveDateTime; - - // Validate format by trying to parse example dates - let test_date = "2025-01-01 00:00:00"; - if NaiveDateTime::parse_from_str(test_date, &self.search_panel_state.date_format).is_err() { - eprintln!("Invalid date format: {}", self.search_panel_state.date_format); - eprintln!("Expected format like: %Y-%m-%d %H:%M:%S"); - return; - } - - // Validate date_from and date_to can be parsed - if !self.search_panel_state.date_from.is_empty() { - if NaiveDateTime::parse_from_str(&self.search_panel_state.date_from, &self.search_panel_state.date_format).is_err() { - eprintln!("Invalid date_from format. Expected format: {}", self.search_panel_state.date_format); - return; - } - } - - if !self.search_panel_state.date_to.is_empty() { - if NaiveDateTime::parse_from_str(&self.search_panel_state.date_to, &self.search_panel_state.date_format).is_err() { - eprintln!("Invalid date_to format. Expected format: {}", self.search_panel_state.date_format); - return; - } - } - } - - let params = SearchParams { - query: self.search_panel_state.query.clone(), - case_sensitive: self.search_panel_state.case_sensitive, - use_regex: self.search_panel_state.use_regex, - date_range_enabled: self.search_panel_state.date_range_enabled, - date_format: self.search_panel_state.date_format.clone(), - date_from: self.search_panel_state.date_from.clone(), - date_to: self.search_panel_state.date_to.clone(), - }; - - let line_index = Arc::clone(&tab.line_index); - let file_path = tab.file_path.clone(); - - // Clear current results - if let Some(tab) = self.active_tab_mut() { - tab.filtered_lines.clear(); - } - - start_search(&self.search_state, params, line_index, file_path); - } - } - - fn handle_clear_search(&mut self) { - self.search_panel_state.query.clear(); - if let Some(tab) = self.active_tab_mut() { - tab.filtered_lines.clear(); - } - } - - fn handle_export_filtered(&mut self) { - use std::fs::File; - use std::io::Write; - use std::time::SystemTime; - - if let Some(tab) = self.active_tab() { - if tab.filtered_lines.is_empty() { - return; - } - - // Generate timestamped filename - let original_path = &tab.file_path; - let file_stem = original_path - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or("export"); - let extension = original_path - .extension() - .and_then(|s| s.to_str()) - .unwrap_or("log"); - - // Generate timestamp - let timestamp = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - - // Create new filename: filename_timestamp.extension - let export_filename = format!("{}_{}.{}", file_stem, timestamp, extension); - let export_path = original_path.with_file_name(export_filename); - - // Write filtered lines to file - match File::create(&export_path) { - Ok(mut file) => { - // Get the line index and file handle - let line_index = Arc::clone(&tab.line_index); - let file_path = tab.file_path.clone(); - - // Open the original file for reading - if let Ok(original_file) = File::open(&file_path) { - let mut reader = std::io::BufReader::new(original_file); - - // Write each filtered line to the export file - for filtered_line in &tab.filtered_lines { - if let Some(content) = line_index.read_line(&mut reader, filtered_line.line_number) { - writeln!(file, "{}", content).ok(); - } - } - - // Open the exported file in a new tab - if let Ok(line_index) = crate::line_index::LineIndex::build(&export_path) { - if let Ok(exported_file) = File::open(&export_path) { - let new_tab = crate::file_tab::FileTab::new( - export_path, - line_index, - exported_file, - ); - self.tabs.push(new_tab); - self.active_tab_index = self.tabs.len() - 1; - } - } - } - } - Err(e) => { - eprintln!("Failed to create export file: {}", e); - } - } - } - } - - fn handle_keyboard_input(&mut self, ctx: &egui::Context) { - if let Some(tab) = self.active_tab_mut() { - tab.page_scroll_direction = ctx.input(|i| { - if i.key_pressed(egui::Key::PageDown) { - Some(1.0) - } else if i.key_pressed(egui::Key::PageUp) { - Some(-1.0) - } else { - None - } - }); - } - } - - fn update_search_results(&mut self) { - if let Some(filtered) = self.search_state.take_results() { - if let Some(tab) = self.active_tab_mut() { - tab.filtered_lines = filtered; - tab.filtered_scroll_offset = 0; - } - } - } -} - -impl eframe::App for LogViewerApp { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - - if self.first_frame { - self.first_frame = false; - ctx.send_viewport_cmd(egui::ViewportCommand::Maximized(true)); - } - - // Update search results if available - self.update_search_results(); - - // Render top menu - let mut open_file_requested = false; - render_top_menu( - ctx, - &mut self.highlight_manager, - &self.indexing_state, - &mut open_file_requested, - ); - - if open_file_requested { - self.handle_open_file(); - } - - // Render highlight editor - let highlight_config_changed = render_highlight_editor(ctx, &mut self.highlight_manager); - if highlight_config_changed { - self.save_config(); - } - - // Render tabs - let mut close_tab_index = None; - render_tabs_panel( - ctx, - &self.tabs, - &mut self.active_tab_index, - &mut close_tab_index, - ); - - if let Some(index) = close_tab_index { - self.handle_close_tab(index); - } - - // Check for Ctrl+F keyboard shortcut - let ctrl_f_pressed = ctx.input(|i| { - i.modifiers.ctrl && i.key_pressed(egui::Key::F) - }); - - // Check for Ctrl+Enter to execute search globally - let ctrl_enter_pressed = ctx.input(|i| { - i.modifiers.ctrl && i.key_pressed(egui::Key::Enter) - }); - - // Render search panel - let match_count = self.active_tab().map(|t| t.filtered_lines.len()).unwrap_or(0); - let search_actions = render_search_panel( - ctx, - &mut self.search_panel_state, - &self.search_state, - match_count, - ctrl_f_pressed, - ); - - if search_actions.execute_search || ctrl_enter_pressed { - add_to_history( - &mut self.search_panel_state.history, - &self.search_panel_state.query, - ); - self.handle_search(); - } - - if search_actions.clear_search { - self.handle_clear_search(); - } - - if search_actions.config_changed { - self.save_config(); - } - - // Render filtered view with improved styling - // Show filtered view if there's a query OR if date range is enabled - let show_filtered = !self.search_panel_state.query.is_empty() || self.search_panel_state.date_range_enabled; - let highlight_rules = self.highlight_manager.rules.clone(); - - let mut export_clicked = false; - if show_filtered { - if let Some(tab) = self.active_tab_mut() { - if !tab.filtered_lines.is_empty() { - let palette = theme::get_palette(); - - egui::TopBottomPanel::bottom("filtered_view") - .resizable(true) - .default_height(250.0) - .min_height(150.0) - .show(ctx, |ui| { - // Styled header - let header_frame = egui::Frame::none() - .fill(palette.bg_secondary) - .inner_margin(egui::Margin::symmetric(12.0, 8.0)); - - header_frame.show(ui, |ui| { - ui.horizontal(|ui| { - let icon = egui::RichText::new("🔍") - .size(16.0); - ui.label(icon); - - let title = egui::RichText::new("Search Results") - .size(16.0) - .color(palette.text_primary) - .strong(); - ui.label(title); - - let count = egui::RichText::new(format!("({} matches)", tab.filtered_lines.len())) - .size(14.0) - .color(palette.accent_bright); - ui.label(count); - - ui.add_space(16.0); - - // Export button - if ui.button("📤 Export").clicked() { - export_clicked = true; - } - }); - }); - - ui.separator(); - - render_log_view( - ui, - LogViewContext { - tab, - highlight_rules: &highlight_rules, - show_all: false, - }, - ); - }); - } - } - } - - // Handle export after rendering the filtered view - if export_clicked { - self.handle_export_filtered(); - } - - // Handle keyboard input - self.handle_keyboard_input(ctx); - - // Render main view with improved styling - egui::CentralPanel::default().show(ctx, |ui| { - let palette = theme::get_palette(); - - if let Some(tab) = self.active_tab_mut() { - // Styled header - let header_frame = egui::Frame::none() - .fill(palette.bg_secondary) - .inner_margin(egui::Margin::symmetric(12.0, 8.0)); - - header_frame.show(ui, |ui| { - ui.horizontal(|ui| { - let icon = egui::RichText::new("📄") - .size(16.0); - ui.label(icon); - - let title = egui::RichText::new("Log View") - .size(16.0) - .color(palette.text_primary) - .strong(); - ui.label(title); - - // Show filename - if let Some(filename) = tab.file_path.file_name() { - ui.add_space(8.0); - let filename_text = egui::RichText::new(format!("• {}", filename.to_string_lossy())) - .size(14.0) - .color(palette.text_secondary); - ui.label(filename_text); - } - - // Show line count - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let line_count = egui::RichText::new(format!("{} lines", tab.line_index.total_lines)) - .size(13.0) - .color(palette.text_muted); - ui.label(line_count); - }); - }); - }); - - ui.separator(); - - render_log_view( - ui, - LogViewContext { - tab, - highlight_rules: &highlight_rules, - show_all: true, - }, - ); - } else { - ui.centered_and_justified(|ui| { - ui.vertical_centered(|ui| { - ui.add_space(40.0); - - let icon = egui::RichText::new("📂") - .size(48.0); - ui.label(icon); - - ui.add_space(16.0); - - let text = egui::RichText::new("No file loaded") - .size(18.0) - .color(palette.text_secondary); - ui.label(text); - - ui.add_space(8.0); - - let hint = egui::RichText::new("Click 'Open File' in the menu to get started") - .size(14.0) - .color(palette.text_muted); - ui.label(hint); - }); - }); - } - }); - - // Only request repaint if there are ongoing background operations - if self.indexing_state.is_indexing() || self.search_state.is_searching() { - ctx.request_repaint(); - } - } -} diff --git a/src/ui/log_panel.rs b/src/ui/log_panel.rs new file mode 100644 index 0000000..29822ca --- /dev/null +++ b/src/ui/log_panel.rs @@ -0,0 +1,152 @@ +use eframe::egui; +use crate::file_tab::FileTab; +use crate::theme; +use crate::theme::ColorPalette; +use crate::types::HighlightRule; +use crate::ui::{render_log_view, LogViewContext}; + +pub fn render_filter_panel(tab: &mut FileTab, ctx: &egui::Context, highlight_rules: &Vec, export_clicked: &mut bool){ + if !tab.filtered_lines.is_empty() { + let palette = theme::get_palette(); + + egui::TopBottomPanel::bottom("filtered_view") + .resizable(true) + .default_height(250.0) + .min_height(150.0) + .show(ctx, |ui| { + // Styled header + render_filter_panel_header(ui, &palette, tab, export_clicked); + + ui.separator(); + + render_log_view( + ui, + LogViewContext { + tab, + highlight_rules, + show_all: false, + }, + ); + }); + } +} + +pub fn render_main_log_panel(ctx: &egui::Context, highlight_rules: &Vec, active_tab: Option<&mut FileTab>) { + egui::CentralPanel::default().show(ctx, |ui| { + let palette = theme::get_palette(); + + if let Some(tab) = active_tab { + // Styled header + + render_file_header(ui, palette, tab); + + ui.separator(); + + render_log_view( + ui, + LogViewContext { + tab, + highlight_rules: &highlight_rules, + show_all: true, + }, + ); + } else { + render_no_file_opened_view(ui, palette); + } + }); +} + +fn render_filter_panel_header(ui: &mut egui::Ui, palette: &ColorPalette, tab: &FileTab, export_clicked: &mut bool){ + let header_frame = egui::Frame::none() + .fill(palette.bg_secondary) + .inner_margin(egui::Margin::symmetric(12.0, 8.0)); + + header_frame.show(ui, |ui| { + ui.horizontal(|ui| { + let icon = egui::RichText::new("🔍") + .size(16.0); + ui.label(icon); + + let title = egui::RichText::new("Search Results") + .size(16.0) + .color(palette.text_primary) + .strong(); + ui.label(title); + + let count = egui::RichText::new(format!("({} matches)", tab.filtered_lines.len())) + .size(14.0) + .color(palette.accent_bright); + ui.label(count); + + ui.add_space(16.0); + + // Export button + if ui.button("📤 Export").clicked() { + *export_clicked = true; + } + }); + }); +} + +fn render_file_header(ui: &mut egui::Ui, palette: ColorPalette, tab: &FileTab) { + let header_frame = egui::Frame::none() + .fill(palette.bg_secondary) + .inner_margin(egui::Margin::symmetric(12.0, 8.0)); + + header_frame.show(ui, |ui| { + ui.horizontal(|ui| { + let icon = egui::RichText::new("📄") + .size(16.0); + ui.label(icon); + + let title = egui::RichText::new("Log View") + .size(16.0) + .color(palette.text_primary) + .strong(); + ui.label(title); + + // Show filename + if let Some(filename) = tab.file_path.file_name() { + ui.add_space(8.0); + let filename_text = egui::RichText::new(format!("• {}", filename.to_string_lossy())) + .size(14.0) + .color(palette.text_secondary); + ui.label(filename_text); + } + + // Show line count + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let line_count = egui::RichText::new(format!("{} lines", tab.line_index.total_lines)) + .size(13.0) + .color(palette.text_muted); + ui.label(line_count); + }); + }); + }); +} + +fn render_no_file_opened_view(ui: &mut egui::Ui, palette: ColorPalette) { + ui.centered_and_justified(|ui| { + ui.vertical_centered(|ui| { + ui.add_space(40.0); + + let icon = egui::RichText::new("📂") + .size(48.0); + ui.label(icon); + + ui.add_space(16.0); + + let text = egui::RichText::new("No file loaded") + .size(18.0) + .color(palette.text_secondary); + ui.label(text); + + ui.add_space(8.0); + + let hint = egui::RichText::new("Click 'Open File' in the menu to get started") + .size(14.0) + .color(palette.text_muted); + ui.label(hint); + }); + }); +} \ No newline at end of file diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 42ba730..5f1fda9 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -3,6 +3,7 @@ pub mod log_view; pub mod search_panel; pub mod tabs_panel; pub mod top_menu; +pub mod log_panel; pub use highlight_editor::render_highlight_editor; pub use log_view::{render_log_view, LogViewContext}; diff --git a/src/ui/top_menu.rs b/src/ui/top_menu.rs index 5359077..1d752b2 100644 --- a/src/ui/top_menu.rs +++ b/src/ui/top_menu.rs @@ -3,16 +3,23 @@ use eframe::egui; use crate::highlight::HighlightManager; use crate::tab_manager::IndexingState; +pub struct TopMenuActions{ + pub open_file_requested: bool, +} + pub fn render_top_menu( ctx: &egui::Context, highlight_manager: &mut HighlightManager, indexing_state: &IndexingState, - on_open_file: &mut bool, -) { + +) -> TopMenuActions { + + let mut top_menu_actions = TopMenuActions{open_file_requested: false}; + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { ui.horizontal(|ui| { if ui.button("📂 Open File").clicked() { - *on_open_file = true; + top_menu_actions.open_file_requested = true; } if ui.button("🎨 Highlights").clicked() { @@ -46,4 +53,6 @@ pub fn render_top_menu( } }); }); + + top_menu_actions }