use iced::widget::text::Shaping; use iced::widget::{button, column, container, row, scrollable, text}; use iced::{Background, Color, Element, Length, Theme}; use bds_core::i18n::UiLocale; use crate::app::Message; use crate::components::inputs; use crate::i18n::t; #[derive(Debug, Clone, Default)] pub struct SiteValidationState { pub has_run: bool, pub is_running: bool, pub is_applying: bool, pub missing_files: Vec, pub extra_files: Vec, pub stale_files: Vec, pub error_message: Option, } pub fn view<'a>(state: &'a SiteValidationState, locale: UiLocale) -> Element<'a, Message> { let run_button = if state.is_running { button( text(t(locale, "siteValidation.running")) .size(13) .shaping(Shaping::Advanced), ) .style(inputs::primary_button) .padding([6, 16]) } else { button( text(t(locale, "siteValidation.run")) .size(13) .shaping(Shaping::Advanced), ) .on_press(Message::RunSiteValidation) .style(inputs::primary_button) .padding([6, 16]) }; let has_issues = !state.missing_files.is_empty() || !state.extra_files.is_empty() || !state.stale_files.is_empty(); let apply_button = if state.is_applying { button( text(t(locale, "siteValidation.applying")) .size(13) .shaping(Shaping::Advanced), ) .style(inputs::secondary_button) .padding([6, 16]) } else if !state.is_running && state.error_message.is_none() && has_issues { button( text(t(locale, "siteValidation.apply")) .size(13) .shaping(Shaping::Advanced), ) .on_press(Message::ApplySiteValidation) .style(inputs::primary_button) .padding([6, 16]) } else { button( text(t(locale, "siteValidation.apply")) .size(13) .shaping(Shaping::Advanced), ) .style(inputs::secondary_button) .padding([6, 16]) }; let mut content = column![ row![ text(t(locale, "tabBar.siteValidation")) .size(24) .shaping(Shaping::Advanced) .color(Color::from_rgb(0.88, 0.88, 0.92)), row![run_button, apply_button].spacing(12), ] .spacing(16), ] .spacing(16); if state.is_running { content = content.push(help_text(t(locale, "siteValidation.running"))); } else if let Some(error) = &state.error_message { content = content.push(section( t(locale, "siteValidation.error"), std::slice::from_ref(error), Color::from_rgb(0.75, 0.28, 0.28), )); } else if !state.has_run { content = content.push(help_text(t(locale, "siteValidation.idle"))); } else if state.missing_files.is_empty() && state.extra_files.is_empty() && state.stale_files.is_empty() { content = content.push(help_text(t(locale, "siteValidation.clean"))); } else { if !state.missing_files.is_empty() { content = content.push(section( format!( "{} ({})", t(locale, "siteValidation.missing"), state.missing_files.len() ), &state.missing_files, Color::from_rgb(0.70, 0.25, 0.25), )); } if !state.extra_files.is_empty() { content = content.push(section( format!( "{} ({})", t(locale, "siteValidation.extra"), state.extra_files.len() ), &state.extra_files, Color::from_rgb(0.68, 0.48, 0.18), )); } if !state.stale_files.is_empty() { content = content.push(section( format!( "{} ({})", t(locale, "siteValidation.stale"), state.stale_files.len() ), &state.stale_files, Color::from_rgb(0.62, 0.32, 0.18), )); } } container( scrollable(content) .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) .style(inputs::scrollable_style), ) .width(Length::Fill) .height(Length::Fill) .padding(24) .style(|_theme: &Theme| container::Style { background: Some(Background::Color(Color::from_rgb(0.11, 0.11, 0.14))), ..container::Style::default() }) .into() } fn help_text<'a>(value: String) -> Element<'a, Message> { inputs::card( text(value) .size(14) .shaping(Shaping::Advanced) .color(Color::from_rgb(0.66, 0.66, 0.72)), ) .into() } fn section<'a>(title: String, entries: &'a [String], accent: Color) -> Element<'a, Message> { let items = entries.iter().fold(column!().spacing(6), |column, entry| { column.push( text(entry) .size(13) .shaping(Shaping::Advanced) .color(Color::from_rgb(0.82, 0.82, 0.88)), ) }); inputs::card( column![ text(title.to_string()) .size(16) .shaping(Shaping::Advanced) .color(accent), items, ] .spacing(10), ) .into() }