Skip to main content

modde_ui/views/
data_tab.rs

1use crate::views::selectable_text::text;
2use iced::widget::{checkbox, column, container, row, scrollable, text_input};
3use iced::{Alignment, Element, Length};
4
5use crate::app::Message;
6
7/// State for the data tab view.
8#[derive(Debug, Clone, Default)]
9pub struct DataTabState {
10    pub filter: String,
11    pub show_conflicts_only: bool,
12    pub missing_store_mod_count: usize,
13}
14
15/// Render the data tab view showing file-level conflict details.
16pub fn view<'a>(
17    state: &'a DataTabState,
18    conflict_files: &'a [(String, Vec<String>)],
19) -> Element<'a, Message> {
20    let title_bar = row![
21        text("Data Files").size(20),
22        iced::widget::space::horizontal(),
23    ]
24    .align_y(Alignment::Center);
25
26    let toolbar = row![
27        text_input("Filter files...", &state.filter)
28            .on_input(Message::DataTabFilterChanged)
29            .padding(6)
30            .width(Length::Fill),
31        checkbox(state.show_conflicts_only).on_toggle(Message::DataTabToggleConflicts),
32        text("Conflicts only").size(13),
33    ]
34    .spacing(8)
35    .align_y(Alignment::Center);
36
37    let filter_lower = state.filter.to_lowercase();
38    let filtered: Vec<&(String, Vec<String>)> = conflict_files
39        .iter()
40        .filter(|(file, _)| {
41            if !filter_lower.is_empty() && !file.to_lowercase().contains(&filter_lower) {
42                return false;
43            }
44            if state.show_conflicts_only {
45                return true; // all entries in conflict_files are conflicts
46            }
47            true
48        })
49        .collect();
50
51    let file_count = filtered.len();
52
53    let file_rows: Element<Message> = if filtered.is_empty() && state.missing_store_mod_count > 0 {
54        container(
55            text(format!(
56                "{} enabled mod(s) are missing from the store; conflict data is incomplete.",
57                state.missing_store_mod_count
58            ))
59            .size(14),
60        )
61        .padding(20)
62        .width(Length::Fill)
63        .center_x(Length::Fill)
64        .into()
65    } else if filtered.is_empty() {
66        container(text("No data files to display.").size(14))
67            .padding(20)
68            .width(Length::Fill)
69            .center_x(Length::Fill)
70            .into()
71    } else {
72        let rows = filtered
73            .into_iter()
74            .fold(column![].spacing(4), |col, (file, providers)| {
75                let provider_list = providers.join(", ");
76                let file_row = row![
77                    text(file).size(13).width(Length::Fill),
78                    text(provider_list).size(12).width(Length::Fixed(300.0)),
79                ]
80                .spacing(8)
81                .padding([4, 8]);
82                col.push(file_row)
83            });
84
85        scrollable(rows).height(Length::Fill).into()
86    };
87
88    let header = row![
89        text("File Path").size(12).width(Length::Fill),
90        text("Providing Mods").size(12).width(Length::Fixed(300.0)),
91    ]
92    .spacing(8)
93    .padding([4, 8]);
94
95    let status = text(format!("{file_count} file(s) shown")).size(12);
96
97    column![
98        title_bar,
99        toolbar,
100        header,
101        iced::widget::rule::horizontal(1),
102        file_rows,
103        status,
104    ]
105    .spacing(8)
106    .padding(16)
107    .width(Length::Fill)
108    .height(Length::Fill)
109    .into()
110}