nf_rated/render/components/
admin.rs1use tui::{
2 backend::Backend,
3 layout::Constraint,
4 layout::Direction,
5 layout::Layout,
6 layout::Rect,
7 style::{Color, Style},
8 text::Span,
9 widgets::Block,
10 widgets::BorderType,
11 widgets::Borders,
12 widgets::Paragraph,
13 widgets::Row,
14 widgets::Table,
15 Frame,
16};
17
18use crate::{data::ItemType, render::App, render::QueryField};
19
20pub fn query_offset(query_field: &QueryField) -> u16 {
21 3 * match query_field {
22 QueryField::Genre => 0,
23 QueryField::Title => 1,
24 QueryField::Cast => 2,
25 QueryField::Country => 3,
26 QueryField::Language => 4,
27 QueryField::Plot => 5,
28 } + 2
29}
30
31pub fn render_admin<B>(f: &mut Frame<B>, app: &App, container: Rect)
32where
33 B: Backend,
34{
35 let chunks = Layout::default()
36 .direction(Direction::Vertical)
37 .constraints([Constraint::Percentage(100)].as_ref())
38 .split(container);
39 let config_container = chunks[0];
40
41 f.set_cursor(
42 config_container.x + app.get_query().len() as u16 + 1,
44 config_container.y + query_offset(&app.query_field),
46 );
47
48 render_config(f, &app, config_container);
49}
50
51pub fn render_config<B>(f: &mut Frame<B>, app: &App, container: Rect)
52where
53 B: Backend,
54{
55 let chunks = Layout::default()
56 .direction(Direction::Vertical)
57 .constraints(
58 [
59 Constraint::Length(1),
60 Constraint::Length(3),
61 Constraint::Length(3),
62 Constraint::Length(3),
63 Constraint::Length(3),
64 Constraint::Length(3),
65 Constraint::Length(3),
66 Constraint::Length(10),
67 Constraint::Length(10),
68 ]
69 .as_ref(),
70 )
71 .split(container);
72
73 let item_type_container = chunks[0];
74 let genre_query_container = chunks[1];
75 let title_query_container = chunks[2];
76 let cast_query_container = chunks[3];
77 let country_query_container = chunks[4];
78 let language_query_container = chunks[5];
79 let plot_query_container = chunks[6];
80 let keyboard_shortcuts_container = chunks[7];
81 let queries_container = chunks[8];
82
83 let item_type_ui = render_item_type(&app.item_type);
84 f.render_widget(item_type_ui, item_type_container);
85
86 let genre_query_ui = render_query(
87 "Genre",
88 &app.genre_query,
89 &app.query_field == &QueryField::Genre,
90 );
91 f.render_widget(genre_query_ui, genre_query_container);
92
93 let title_query_ui = render_query(
94 "Title",
95 &app.title_query,
96 &app.query_field == &QueryField::Title,
97 );
98 f.render_widget(title_query_ui, title_query_container);
99
100 let cast_query_ui = render_query(
101 "Cast",
102 &app.cast_query,
103 &app.query_field == &QueryField::Cast,
104 );
105 f.render_widget(cast_query_ui, cast_query_container);
106
107 let country_query_ui = render_query(
108 "Country",
109 &app.country_query,
110 &app.query_field == &QueryField::Country,
111 );
112 f.render_widget(country_query_ui, country_query_container);
113
114 let language_query_ui = render_query(
115 "Language",
116 &app.language_query,
117 &app.query_field == &QueryField::Language,
118 );
119 f.render_widget(language_query_ui, language_query_container);
120
121 let plot_query_ui = render_query(
122 "Plot",
123 &app.plot_query,
124 &app.query_field == &QueryField::Plot,
125 );
126 f.render_widget(plot_query_ui, plot_query_container);
127
128 render_keyboard_shortcuts(f, keyboard_shortcuts_container);
129 render_queries_legend(f, queries_container);
130}
131
132fn render_keyboard_shortcuts<B>(f: &mut Frame<B>, container: Rect)
133where
134 B: Backend,
135{
136 let shortcut_style = Style::default().fg(Color::Gray);
137 let table_style = Style::default().fg(Color::DarkGray);
138
139 let header = ["Shortcut", "Description"];
140 let items = vec![
141 vec!["<Tab>", "Cycle Filters"],
142 vec!["↓/↑ | Ctrl-N/Ctrl-P", "Select Next/Previous Show"],
143 vec!["Ctrl-D/Ctrl-U", "Select Next/Previous Page"],
144 vec!["Ctrl-E", "Erase all Filters"],
145 vec!["Ctrl-O", "Change Type of Show"],
146 ];
147
148 let rows = items
149 .iter()
150 .map(|i| Row::StyledData(i.iter(), shortcut_style));
151 let t = Table::new(header.iter(), rows)
152 .style(table_style)
153 .block(Block::default().borders(Borders::ALL))
154 .widths(&[Constraint::Percentage(30), Constraint::Percentage(70)]);
155
156 f.render_widget(t, container);
157}
158
159fn render_queries_legend<B>(f: &mut Frame<B>, container: Rect)
160where
161 B: Backend,
162{
163 let shortcut_style = Style::default().fg(Color::Gray);
164 let table_style = Style::default().fg(Color::DarkGray);
165
166 let header = ["Query", "Description"];
167 let items = vec![
168 vec![
169 "drama adventure",
170 "Match all that has 'drama' or 'adventure'",
171 ],
172 vec!["!comedy", "Match all that does not have 'comedy'"],
173 ];
174
175 let rows = items
176 .iter()
177 .map(|i| Row::StyledData(i.iter(), shortcut_style));
178 let t = Table::new(header.iter(), rows)
179 .style(table_style)
180 .block(Block::default().borders(Borders::ALL))
181 .widths(&[Constraint::Percentage(30), Constraint::Percentage(70)]);
182
183 f.render_widget(t, container);
184}
185
186fn render_item_type(item_type: &ItemType) -> Paragraph {
187 let value = match item_type {
188 ItemType::Movie => "Movies",
189 ItemType::Series => "Series",
190 ItemType::Both => "Movies and Series",
191 };
192 let value_style = Style::default().fg(Color::LightBlue);
193 let value_span = Span::styled(value, value_style);
194
195 Paragraph::new(value_span)
196}
197
198fn render_query<'a>(label: &'a str, query: &'a str, selected: bool) -> Paragraph<'a> {
199 let label_style = Style::default().fg(Color::Gray);
200 let border_style = if selected {
201 Style::default().fg(Color::Yellow)
202 } else {
203 Style::default().fg(Color::White)
204 };
205
206 let query_style = Style::default().fg(Color::White);
207 let query_span = Span::styled(query, query_style);
208
209 Paragraph::new(query_span).block(
210 Block::default()
211 .borders(Borders::ALL)
212 .border_style(border_style)
213 .border_type(BorderType::Rounded)
214 .style(label_style)
215 .title(label),
216 )
217}