world_tables_gui/
app.rs

1
2use anyhow::{Context, Result};
3use egui_extras::{Size, StripBuilder};
4use egui_extras::{Column, TableBuilder};
5use enum_map::{enum_map, EnumMap};
6use lazy_static::lazy_static;
7use log::debug;
8use reqwest::blocking::Client;
9use std::{
10    cell::RefCell,
11    collections::{hash_map::Entry, HashMap},
12    net::SocketAddr,
13    sync::mpsc::{channel, Receiver, Sender},
14    time::Duration,
15    thread,
16};
17
18use world_tables_base::{
19    Tag, Tagged, Keyed, Label, Country, State, City,
20    WorldRegion, WorldSubregion, Currency, UrlBuilder, Metadata
21};
22
23use crate::types::*;
24
25const RETRY_DELAY: f64 = 10.0;
26const PAGE_LIMIT: usize = 100;
27const NONE: &str = "None";
28
29lazy_static! {
30    static ref LAYOUT_LABEL: egui::Layout = egui::Layout::right_to_left(egui::Align::Center);
31    static ref LAYOUT_VALUE: egui::Layout = egui::Layout::left_to_right(egui::Align::Center).with_main_justify(true);
32    static ref LAYOUT_BUTTON: egui::Layout = egui::Layout::centered_and_justified(egui::Direction::TopDown);
33}
34
35//<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
36//<<>><=========================  APP  ==============================><<>>//
37//<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
38
39type ResponseChannels = (Sender<Result<DataResponse>>, Receiver<Result<DataResponse>>);
40
41pub struct App {
42    client: Client,
43    url: UrlBuilder,
44
45    metadata: ServerData<Metadata>,
46    channels: EnumMap<DataKind, ResponseChannels>,
47    main_show: EnumMap<MainList, bool>,
48
49    countries: Option<TableData<Country>>,
50    states: Option<TableData<State>>,
51    cities: Option<TableData<City>>,
52    regions: Option<TableData<WorldRegion>>,
53    subregions: Option<TableData<WorldSubregion>>,
54    currencies: Option<TableData<Currency>>,
55
56    country_windows: HashMap<String, ObjectData<Country>>,
57    state_windows: HashMap<String, ObjectData<State>>,
58    city_windows: HashMap<String, ObjectData<City>>,
59    region_windows: HashMap<String, ObjectData<WorldRegion>>,
60    subregion_windows: HashMap<String, ObjectData<WorldSubregion>>,
61    currency_windows: HashMap<String, ObjectData<Currency>>,
62
63    countries_by_region_windows: RefCell<HashMap<String, FilteredTableData<Country>>>,
64    countries_by_subregion_windows: RefCell<HashMap<String, FilteredTableData<Country>>>,
65    countries_by_currency_windows: RefCell<HashMap<String, FilteredTableData<Country>>>,
66    states_by_country_windows: RefCell<HashMap<String, FilteredTableData<State>>>,
67    cities_by_country_windows: RefCell<HashMap<String, FilteredTableData<City>>>,
68    cities_by_state_windows: RefCell<HashMap<String, FilteredTableData<City>>>,
69    subregions_by_region_windows: RefCell<HashMap<String, FilteredTableData<WorldSubregion>>>,
70
71    errors: Vec<String>,
72}
73
74impl Default for App {
75    fn default() -> Self {
76        Self {
77            client: Client::builder()
78                .timeout(Duration::from_secs(15))
79                .build()
80                .unwrap(),
81            url: UrlBuilder::new(),
82            metadata: ServerData::Empty,
83
84            channels: enum_map! {
85                _ => channel(),
86            },
87
88            main_show: enum_map! {
89                _ => false,
90            },
91
92            countries: None,
93            states: None,
94            cities: None,
95            regions: None,
96            subregions: None,
97            currencies: None,
98
99            country_windows: HashMap::new(),
100            state_windows: HashMap::new(),
101            city_windows: HashMap::new(),
102            region_windows: HashMap::new(),
103            subregion_windows: HashMap::new(),
104            currency_windows: HashMap::new(),
105
106            countries_by_region_windows: RefCell::new(HashMap::new()),
107            countries_by_subregion_windows: RefCell::new(HashMap::new()),
108            countries_by_currency_windows: RefCell::new(HashMap::new()),
109            states_by_country_windows: RefCell::new(HashMap::new()),
110            cities_by_country_windows: RefCell::new(HashMap::new()),
111            cities_by_state_windows: RefCell::new(HashMap::new()),
112            subregions_by_region_windows: RefCell::new(HashMap::new()),
113
114            errors: Vec::new(),
115        }
116    }
117}
118
119impl App {
120    /// Called once before the first frame.
121    pub fn new(cc: &eframe::CreationContext<'_>, addr: SocketAddr) -> Self {
122        use catppuccin_egui::FRAPPE as THEME;
123        catppuccin_egui::set_theme(&cc.egui_ctx, THEME);
124
125        let mut style = (*cc.egui_ctx.style()).clone();
126
127        style.spacing.window_margin = egui::style::Margin {
128            left: 15.0,
129            right: 15.0,
130            top: 10.0,
131            bottom: 10.0,
132        };
133
134        style.spacing.button_padding = egui::vec2(8.0, 1.0);
135        style.spacing.icon_spacing = 3.0;
136        style.spacing.indent_ends_with_horizontal_line = true;
137        style.spacing.item_spacing = egui::vec2(4.0, 5.0);
138
139        style.visuals = egui::style::Visuals {
140            dark_mode: true,
141            window_rounding: egui::Rounding::same(2.5),
142            window_stroke: egui::Stroke::new(0.1, THEME.blue),
143            window_shadow: epaint::Shadow { extrusion: 5.0, color: THEME.blue },
144            popup_shadow: epaint::Shadow { extrusion: 5.0, color: THEME.blue },
145            collapsing_header_frame: true,
146            widgets: egui::style::Widgets {
147                noninteractive: egui::style::WidgetVisuals {
148                    bg_stroke: egui::Stroke {
149                        width: 1.0,
150                        ..style.visuals.widgets.noninteractive.bg_stroke
151                    },
152                    rounding: egui::Rounding::same(2.5),
153                    fg_stroke: egui::Stroke {
154                        width: 1.0,
155                        ..style.visuals.widgets.noninteractive.fg_stroke
156                    },
157                    expansion: 0.0,
158                    ..style.visuals.widgets.noninteractive
159                },
160                inactive: egui::style::WidgetVisuals {
161                    weak_bg_fill: THEME.surface1, // darker than default
162                    ..style.visuals.widgets.inactive
163                },
164                hovered: egui::style::WidgetVisuals {
165                    weak_bg_fill: THEME.surface2, // fix to remove
166                    ..style.visuals.widgets.hovered
167                },
168                ..style.visuals.widgets
169            },
170            ..style.visuals
171        };
172
173        cc.egui_ctx.set_style(style);
174
175        Self {
176            url: UrlBuilder::with_addr(addr).unwrap(),
177            ..Default::default()
178        }
179    }
180
181    fn request(&self, url: &UrlBuilder, data_kind: DataKind, ctx: Option<&egui::Context>) {
182        let tx = &self.channels[data_kind].0;
183        App::send_request(&self.client, url, data_kind, tx, ctx);
184    }
185
186    fn send_request(client: &Client, url: &UrlBuilder, data_kind: DataKind, tx: &Sender<Result<DataResponse>>, ctx: Option<&egui::Context>) {
187        let tx = tx.clone();
188        let ctx = ctx.cloned();
189        let client = client.clone();
190        let url = url.clone();
191
192        let get_result = move || -> Result<DataResponse> {
193            debug!("{}", url.as_str());
194
195            let response = client
196                .get(url.as_str())
197                .send()
198                .context("Failed fetching countries from server")?;
199
200            let pagination = match data_kind {
201                DataKind::Metadata | DataKind::Country | DataKind::State |
202                DataKind::City | DataKind::Region | DataKind::Subregion | DataKind::Currency => None,
203                _ => Some(Pagination::with_headers(response.headers())?),
204            };
205
206            let counts = match data_kind {
207                DataKind::Country => Some(Counts::with_country_headers(response.headers())?),
208                DataKind::State => Some(Counts::with_state_headers(response.headers())?),
209                DataKind::Region => Some(Counts::with_region_headers(response.headers())?),
210                DataKind::Subregion => Some(Counts::with_subregion_headers(response.headers())?),
211                DataKind::Currency => Some(Counts::with_currency_headers(response.headers())?),
212                _ => None,
213            };
214
215            Ok(DataResponse {
216                response,
217                page_text: pagination
218                    .map(|pagination| pagination.page.to_string())
219                    .unwrap_or("1".to_string()),
220                pagination,
221                counts,
222            })
223        };
224
225        thread::spawn(move || {
226            let result = get_result();
227            tx.send(result).unwrap();
228            if let Some(ctx) = ctx { ctx.request_repaint() }
229        });
230    }
231
232    fn recv_response(&mut self) {
233        for (data_kind, (_, rx)) in &self.channels {
234            if let Ok(result) = rx.try_recv() {
235                match result {
236                    Err(e) => self.errors.push(format!("{e:#}")),
237                    Ok(data_response) => {
238                        match data_kind {
239                            DataKind::Metadata => unreachable!(),
240                            DataKind::Countries => self.countries = data_response.into(),
241                            DataKind::States => self.states = data_response.into(),
242                            DataKind::Cities => self.cities = data_response.into(),
243                            DataKind::Regions => self.regions = data_response.into(),
244                            DataKind::Subregions => self.subregions = data_response.into(),
245                            DataKind::Currencies => self.currencies = data_response.into(),
246                            DataKind::Country => {
247                                let counts = data_response.counts;
248                                let opt_country: Option<Country> = data_response.into();
249                                if let Some(country) = &opt_country {
250                                    let key = country.iso2.to_string();
251                                    if let Some(object_data) = self.country_windows.get_mut(&key) {
252                                        object_data.data = opt_country;
253                                        object_data.counts = counts;
254                                    }
255                                }
256                            },
257                            DataKind::State => {
258                                let counts = data_response.counts;
259                                let opt_state: Option<State> = data_response.into();
260                                if let Some(state) = &opt_state {
261                                    let key = state.id.to_string();
262                                    if let Some(object_data) = self.state_windows.get_mut(&key) {
263                                        object_data.data = opt_state;
264                                        object_data.counts = counts;
265                                    }
266                                }
267                            },
268                            DataKind::City => {
269                                let counts = data_response.counts;
270                                let opt_city: Option<City> = data_response.into();
271                                if let Some(city) = &opt_city {
272                                    let key = city.id.to_string();
273                                    if let Some(object_data) = self.city_windows.get_mut(&key) {
274                                        object_data.data = opt_city;
275                                        object_data.counts = counts;
276                                    }
277                                }
278                            },
279                            DataKind::Region => {
280                                let counts = data_response.counts;
281                                let opt_region: Option<WorldRegion> = data_response.into();
282                                if let Some(region) = &opt_region {
283                                    let key = region.id.to_string();
284                                    if let Some(object_data) = self.region_windows.get_mut(&key) {
285                                        object_data.data = opt_region;
286                                        object_data.counts = counts;
287                                    }
288                                }
289                            },
290                            DataKind::Subregion => {
291                                let counts = data_response.counts;
292                                let opt_subregion: Option<WorldSubregion> = data_response.into();
293                                if let Some(subregion) = &opt_subregion {
294                                    let key = subregion.id.to_string();
295                                    if let Some(object_data) = self.subregion_windows.get_mut(&key) {
296                                        object_data.data = opt_subregion;
297                                        object_data.counts = counts;
298                                    }
299                                }
300                            },
301                            DataKind::Currency => {
302                                let counts = data_response.counts;
303                                let opt_currency: Option<Currency> = data_response.into();
304                                if let Some(currency) = &opt_currency {
305                                    let key = currency.iso.to_string();
306                                    if let Some(object_data) = self.currency_windows.get_mut(&key) {
307                                        object_data.data = opt_currency;
308                                        object_data.counts = counts;
309                                    }
310                                }
311                            },
312                            DataKind::CountriesByRegion => {
313                                let objects: Option<TableData<Country>> = data_response.into();
314                                let key: String = {
315                                    if let Some(table_data) = &objects {
316                                        // should not panic here if the countries button is properly disabled
317                                        table_data.data[0].region.key().unwrap().to_string()
318                                    } else {
319                                        panic!("Region id not found on list of countries from the API");
320                                    }
321                                };
322
323                                if let Some(filtered_table_data) = self.countries_by_region_windows.borrow_mut().get_mut(&key) {
324                                    filtered_table_data.data = objects;
325                                }
326                            },
327                            DataKind::CountriesBySubregion => {
328                                let objects: Option<TableData<Country>> = data_response.into();
329                                let key: String = {
330                                    if let Some(table_data) = &objects {
331                                        // should not panic here if the countries button is properly disabled
332                                        table_data.data[0].subregion.key().unwrap().to_string()
333                                    } else {
334                                        panic!("Subregion id not found on list of countries from the API");
335                                    }
336                                };
337
338                                if let Some(filtered_table_data) = self.countries_by_subregion_windows.borrow_mut().get_mut(&key) {
339                                    filtered_table_data.data = objects;
340                                }
341                            },
342                            DataKind::CountriesByCurrency => {
343                                let objects: Option<TableData<Country>> = data_response.into();
344                                let key: String = {
345                                    if let Some(table_data) = &objects {
346                                        // should not panic here if the countries button is properly disabled
347                                        table_data.data[0].currency.key().unwrap().to_string()
348                                    } else {
349                                        panic!("Currency id not found on list of countries from the API");
350                                    }
351                                };
352
353                                if let Some(filtered_table_data) = self.countries_by_currency_windows.borrow_mut().get_mut(&key) {
354                                    filtered_table_data.data = objects;
355                                }
356                            },
357                            DataKind::StatesByCountry => {
358                                let objects: Option<TableData<State>> = data_response.into();
359                                let key: String = {
360                                    if let Some(table_data) = &objects {
361                                        // should not panic here if the states button is properly disabled
362                                        table_data.data[0].country.key().unwrap().to_string()
363                                    } else {
364                                        panic!("Country id not found on list of states from the API");
365                                    }
366                                };
367
368                                if let Some(filtered_table_data) = self.states_by_country_windows.borrow_mut().get_mut(&key) {
369                                    filtered_table_data.data = objects;
370                                }
371                            },
372                            DataKind::CitiesByCountry => {
373                                let objects: Option<TableData<City>> = data_response.into();
374                                let key: String = {
375                                    if let Some(table_data) = &objects {
376                                        // should not panic here if the cities button is properly disabled
377                                        table_data.data[0].country.key().unwrap().to_string()
378                                    } else {
379                                        panic!("Country id not found on list of states from the API");
380                                    }
381                                };
382
383                                if let Some(filtered_table_data) = self.cities_by_country_windows.borrow_mut().get_mut(&key) {
384                                    filtered_table_data.data = objects;
385                                }
386                            },
387                            DataKind::CitiesByState => {
388                                let objects: Option<TableData<City>> = data_response.into();
389                                let key: String = {
390                                    if let Some(table_data) = &objects {
391                                        // should not panic here if the cities button is properly disabled
392                                        table_data.data[0].state.key().unwrap().to_string()
393                                    } else {
394                                        panic!("State id not found on list of states from the API");
395                                    }
396                                };
397
398                                if let Some(filtered_table_data) = self.cities_by_state_windows.borrow_mut().get_mut(&key) {
399                                    filtered_table_data.data = objects;
400                                }
401                            },
402                            DataKind::SubregionsByRegion => {
403                                let objects: Option<TableData<WorldSubregion>> = data_response.into();
404                                let key: String = {
405                                    if let Some(table_data) = &objects {
406                                        // should not panic here if the cities button is properly disabled
407                                        table_data.data[0].region.key().unwrap().to_string()
408                                    } else {
409                                        panic!("Region id not found on list of states from the API");
410                                    }
411                                };
412
413                                if let Some(filtered_table_data) = self.subregions_by_region_windows.borrow_mut().get_mut(&key) {
414                                    filtered_table_data.data = objects;
415                                }
416                            },
417                        }
418                    }
419                }
420            }
421        }
422    }
423
424    #[allow(clippy::too_many_arguments)]
425    fn window_table<F>(
426        &self,
427        ctx: &egui::Context,
428        show: &mut bool,
429        url: &UrlBuilder,
430        data_kind: DataKind,
431        list_data: MainListData,
432        page_text: Option<String>,
433        add_row_content: F
434    ) -> Option<String>
435    where
436        F: FnMut(usize, egui_extras::TableRow<'_, '_>),
437    {
438        let mut result = None;
439        let column_headers = list_data.column_headers();
440        let (title, pagination) = list_data.data();
441
442        egui::Window::new(title)
443            .open(show)
444            .default_size(egui::vec2(column_headers.len() as f32 * 146.0, 300.0))
445            .resizable(true)
446            .show(ctx, |ui| {
447                // remove button frame for table entries
448                ui.visuals_mut().button_frame = false;
449                ui.add_space(10.0);
450
451                if let Some(pagination) = pagination {
452                    StripBuilder::new(ui)
453                        .size(Size::remainder())
454                        .size(Size::initial(40.0))
455                        .vertical(|mut strip| {
456                            strip.cell(|ui| {
457                                App::data_table(ui, pagination.count, column_headers, add_row_content);
458                            });
459
460                            let metadata = self.metadata.unwrap_ref();
461
462                            let count_max = match list_data {
463                                MainListData::Countries(..) => metadata.countries,
464                                MainListData::States(..) => metadata.states,
465                                MainListData::Cities(..) => metadata.cities,
466                                MainListData::Regions(..) => metadata.regions,
467                                MainListData::Subregions(..) => metadata.subregions,
468                                MainListData::Currencies(..) =>  metadata.currencies,
469                            };
470                            result = self.pagination_strip(ctx, &mut strip, url, data_kind, pagination, page_text.unwrap(), count_max);
471                        });
472                } else {
473                    spinner(ui);
474                }
475            });
476
477        result
478    }
479
480    fn data_table<F>(ui: &mut egui::Ui, rows_count: usize, headers: &[&str], mut add_row_content: F)
481    where
482        F: FnMut(usize, egui_extras::TableRow<'_, '_>),
483    {
484        ui.group(|ui| {
485            egui::ScrollArea::horizontal().show(ui, |ui| {
486                TableBuilder::new(ui)
487                    .striped(true)
488                    .cell_layout(egui::Layout::left_to_right(egui::Align::Center))
489                    .min_scrolled_height(0.0)
490                    .resizable(true)
491                    .columns(Column::initial(130.0).clip(true), headers.len())
492                    .header(20.0, |mut header| {
493                        for title in headers {
494                            header.col(|ui| {
495                                ui.vertical_centered(|ui| {
496                                    ui.strong(*title);
497                                });
498                            });
499                        }
500                    })
501                    .body(|body| {
502                        body.rows(20.0, rows_count, |index, row| {
503                            add_row_content(index, row);
504                        });
505                    });
506            });
507        });
508    }
509
510    #[allow(clippy::too_many_arguments)]
511    fn pagination_strip(
512        &self,
513        ctx: &egui::Context,
514        strip: &mut egui_extras::Strip,
515        url: &UrlBuilder,
516        data_kind: DataKind,
517        pagination: Pagination,
518        mut page_text: String,
519        count_max: usize,
520    ) -> Option<String>
521    {
522        let mut result = None;
523
524        strip.strip(|builder| {
525            builder
526                .size(Size::initial(100.0).at_least(100.0))
527                .size(Size::remainder())
528                .horizontal(|mut strip| {
529                    strip.cell(|ui| {
530                        ui.add_space(6.5);
531                        ui.vertical(|ui| {
532                            ui.small(format!("Page {} of {}", pagination.page, pagination.total_pages));
533                            ui.small(format!("{} of {}", pagination.total_count, count_max));
534                        });
535                    });
536
537                    strip.cell(|ui| {
538                        if pagination.total_pages != 1 {
539                            ui.visuals_mut().button_frame = true;
540                            ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
541                                let page = pagination.page;
542
543                                if ui.add_enabled(page < pagination.total_pages, egui::Button::new("Next")).clicked() {
544                                    let url = url.clone().with_pagination(page + 1, PAGE_LIMIT);
545                                    self.request(&url, data_kind, Some(ctx));
546                                }
547
548                                let page_response = ui.add(egui::TextEdit::singleline(&mut page_text).desired_width(25.0));
549
550                                if page_response.changed() {
551                                    let page_num: usize = page_text.parse().unwrap_or_default();
552
553                                    if page_num > 0 {
554                                        page_text = page_num.min(pagination.total_pages).to_string();
555                                    } else {
556                                        page_text = Default::default();
557                                    }
558                                }
559
560                                if page_response.lost_focus() {
561                                    let page_num: usize = page_text.parse().unwrap_or_default();
562
563                                    if page_num > 0 {
564                                        let url = url.clone().with_pagination(page_num.min(pagination.total_pages), PAGE_LIMIT);
565                                        self.request(&url, data_kind, Some(ctx));
566                                    }
567                                }
568
569                                if ui.add_enabled(page > 1, egui::Button::new("Back")).clicked() {
570                                    let url = url.clone().with_pagination(page - 1, PAGE_LIMIT);
571                                    self.request(&url, data_kind, Some(ctx));
572                                }
573
574                                result = Some(page_text);
575                            });
576                        }
577                    });
578                });
579        });
580
581        result
582    }
583
584    fn handle_selection<T>(
585        ctx: &egui::Context,
586        client: &Client,
587        url: &UrlBuilder,
588        channels: &EnumMap<DataKind, ResponseChannels>,
589        data_kind: DataKind,
590        selection: Option<Tag>,
591        windows_map: &mut HashMap<String, ObjectData<T>>)
592    {
593        if let Some(Tag { key, label }) = selection {
594            let skey = key.clone();
595            if App::new_window(key, label, windows_map) {
596                let tx = &channels[data_kind].0;
597                App::send_request(client, &App::object_url(url, data_kind, &skey).unwrap(), data_kind, tx, Some(ctx));
598            }
599        }
600    }
601
602    fn object_url(url: &UrlBuilder, data_kind: DataKind, key: &str) -> Option<UrlBuilder> {
603        match data_kind {
604            DataKind::Country => Some(url.for_country(key)),
605            DataKind::State => Some(url.for_state(key)),
606            DataKind::City => Some(url.for_city(key)),
607            DataKind::Region => Some(url.for_world_region(key)),
608            DataKind::Subregion => Some(url.for_world_subregion(key)),
609            DataKind::Currency => Some(url.for_currency(key)),
610            _ => None,
611        }
612    }
613
614    fn new_window<T>(
615        key: String,
616        label: String,
617        windows_map: &mut HashMap<String, ObjectData<T>>) -> bool
618    {
619        if let Entry::Vacant(e) = windows_map.entry(key) {
620            e.insert(
621                ObjectData {
622                    title: label,
623                    ..Default::default()
624                }
625            );
626            return true;
627        }
628
629        false
630    }
631
632    fn handle_filtered_selection<T>(
633        &self,
634        ctx: &egui::Context,
635        data_kind: DataKind,
636        selection: Option<Tag>,
637        windows_map: &mut HashMap<String, FilteredTableData<T>>)
638    {
639        if let Some(Tag { key, label }) = selection {
640            if let Entry::Vacant(e) = windows_map.entry(key.clone()) {
641                let (title, url) = match data_kind {
642                    DataKind::CountriesByRegion => (
643                        "Countries",
644                        self.url.for_countries_from_region(&key).with_pagination(1, PAGE_LIMIT),
645                    ),
646                    DataKind::CountriesBySubregion => (
647                        "Countries",
648                        self.url.for_countries_from_subregion(&key).with_pagination(1, PAGE_LIMIT),
649                    ),
650                    DataKind::CountriesByCurrency => (
651                        "Countries",
652                        self.url.for_countries_from_currency(&key).with_pagination(1, PAGE_LIMIT),
653                    ),
654                    DataKind::StatesByCountry => (
655                        "States",
656                        self.url.for_states_from_country(&key).with_pagination(1, PAGE_LIMIT),
657                    ),
658                    DataKind::CitiesByCountry => (
659                        "Cities",
660                        self.url.for_cities_from_country(&key).with_pagination(1, PAGE_LIMIT),
661                    ),
662                    DataKind::CitiesByState => (
663                        "Cities",
664                        self.url.for_cities_from_state(&key).with_pagination(1, PAGE_LIMIT),
665                    ),
666                    DataKind::SubregionsByRegion => (
667                        "Subregions",
668                        self.url.for_subregions_from_region(&key).with_pagination(1, PAGE_LIMIT),
669                    ),
670                    _ => panic!("Data kind not supported for filtered listing"),
671                };
672
673                e.insert(
674                    FilteredTableData {
675                        data: None,
676                        show: true,
677                        title: format!("{} from {}", title, &label),
678                    }
679                );
680                self.request(&url, data_kind, Some(ctx));
681            }
682        }
683    }
684
685    fn errors_window(&mut self, ctx: &egui::Context) {
686        if !self.errors.is_empty() {
687            egui::Window::new("Errors")
688                .default_size((200.0, 200.0))
689                .show(ctx, |ui| {
690                    egui::ScrollArea::vertical().show(ui, |ui| {
691                        for error in &self.errors {
692                            ui.label(error);
693                        }
694                    });
695                    ui.add_space(10.0);
696                    ui.vertical_centered(|ui| {
697                        if ui.button("Clear").clicked() {
698                            self.errors.clear();
699                        }
700                    });
701                });
702        }
703    }
704}
705
706impl eframe::App for App {
707
708    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
709
710        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
711        //<<>><========================  METADATA  ==========================><<>>//
712        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
713
714        if !self.metadata.is_ok() {
715            match &self.metadata {
716                ServerData::Empty => {
717                    self.request(&self.url.for_metadata(), DataKind::Metadata, Some(ctx));
718                    self.metadata = ServerData::Loading;
719                },
720                ServerData::Loading => {
721                    if let Ok(result) = self.channels[DataKind::Metadata].1.try_recv() {
722                        let handle_error = |e| -> ServerData<Metadata> {
723                            debug!("{:?}", e);
724                            ServerData::Failed(format!("{e:#}"), ctx.input(|i| i.time))
725                        };
726
727                        self.metadata = result
728                            .and_then(|data_response| Ok(data_response.response.json()?))
729                            .map_or_else(handle_error, ServerData::Ok);
730                    }
731                },
732                ServerData::Failed(message, time) => {
733                    if ctx.input(|i| i.time) >= time + RETRY_DELAY {
734                        self.metadata = ServerData::Empty;
735                    } else if !message.is_empty() {
736                        self.errors.push(message.clone());
737                        self.metadata = ServerData::Failed(Default::default(), *time);
738                    }
739                },
740                _ => unreachable!(),
741            }
742
743            egui::CentralPanel::default()
744                .show(ctx, |ui| {
745                    egui::warn_if_debug_build(ui);
746                    ui.centered_and_justified(|ui| {
747                        ui.add(egui::Spinner::new().size(50.0));
748                    });
749                });
750
751            self.errors_window(ctx);
752
753            return;
754        }
755
756        let mut main_show = self.main_show;
757
758        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
759        //<<>><======================  SIDE PANEL  ==========================><<>>//
760        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
761
762        egui::SidePanel::left("side_panel")
763            .exact_width(130.0)
764            .resizable(false)
765            .show(ctx, |ui| {
766                ui.vertical_centered(|ui| {
767                    ui.heading("World Tables");
768                    ui.separator();
769                });
770
771                StripBuilder::new(ui)
772                    .sizes(Size::remainder(), 3)
773                    .vertical(|mut strip| {
774                        strip.empty();
775                        strip.cell(|ui| {
776                            ui.vertical_centered_justified(|ui| {
777                                ui.group(|ui| {
778                                    let meta = self.metadata.unwrap_ref();
779                                    ui.spacing_mut().item_spacing.y = 4.0;
780                                    if ui.toggle_value(&mut main_show[MainList::Countries], format!("Countries ({})", meta.countries)).changed() &&
781                                        main_show[MainList::Countries]
782                                    {
783                                        let url = self.url.for_countries().with_pagination(1, PAGE_LIMIT);
784                                        self.request(&url, DataKind::Countries, Some(ctx));
785                                    }
786                                    if ui.toggle_value(&mut main_show[MainList::States], format!("States ({})", meta.states)).changed() &&
787                                        main_show[MainList::States]
788                                    {
789                                        let url = self.url.for_states().with_pagination(1, PAGE_LIMIT);
790                                        self.request(&url, DataKind::States, Some(ctx));
791                                    }
792                                    if ui.toggle_value(&mut main_show[MainList::Cities], format!("Cities ({})", meta.cities)).changed() &&
793                                        main_show[MainList::Cities]
794                                    {
795                                        let url = self.url.for_cities().with_pagination(1, PAGE_LIMIT);
796                                        self.request(&url, DataKind::Cities, Some(ctx));
797                                    }
798                                    if ui.toggle_value(&mut main_show[MainList::Regions], format!("Regions ({})", meta.regions)).changed() &&
799                                        main_show[MainList::Regions]
800                                    {
801                                        let url = self.url.for_world_regions().with_pagination(1, PAGE_LIMIT);
802                                        self.request(&url, DataKind::Regions, Some(ctx));
803                                    }
804                                    if ui.toggle_value(&mut main_show[MainList::Subregions], format!("Subregions ({})", meta.subregions)).changed() &&
805                                        main_show[MainList::Subregions]
806                                    {
807                                        let url = self.url.for_world_subregions().with_pagination(1, PAGE_LIMIT);
808                                        self.request(&url, DataKind::Subregions, Some(ctx));
809                                    }
810                                    if ui.toggle_value(&mut main_show[MainList::Currencies], format!("Currencies ({})", meta.currencies)).changed() &&
811                                        main_show[MainList::Currencies]
812                                    {
813                                        let url = self.url.for_currencies().with_pagination(1, PAGE_LIMIT);
814                                        self.request(&url, DataKind::Currencies, Some(ctx));
815                                    }
816                                });
817                            });
818                        });
819                        strip.empty();
820                    });
821            });
822
823        egui::CentralPanel::default()
824            .show(ctx, |ui| {
825                egui::warn_if_debug_build(ui);
826            });
827
828        // needed because toggled_value 'changed' response doesn't trigger on
829        // window close button
830        if !main_show[MainList::Countries] && self.countries.is_some() {
831            self.countries = None
832        }
833        if !main_show[MainList::States] && self.states.is_some() {
834            self.states = None
835        }
836        if !main_show[MainList::Cities] && self.cities.is_some() {
837            self.cities = None
838        }
839        if !main_show[MainList::Regions] && self.regions.is_some() {
840            self.regions = None
841        }
842        if !main_show[MainList::Subregions] && self.subregions.is_some() {
843            self.subregions = None
844        }
845        if !main_show[MainList::Currencies] && self.currencies.is_some() {
846            self.currencies = None
847        }
848
849        self.recv_response();
850
851        let mut country_selected: Option<Tag> = None;
852        let mut state_selected: Option<Tag> = None;
853        let mut city_selected: Option<Tag> = None;
854        let mut region_selected: Option<Tag> = None;
855        let mut subregion_selected: Option<Tag> = None;
856        let mut currency_selected: Option<Tag> = None;
857
858        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
859        //<<>><=======================  COUNTRIES  ==========================><<>>//
860        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
861
862        let page_text = self.window_table(
863            ctx,
864            &mut main_show[MainList::Countries],
865            &self.url.for_countries(),
866            DataKind::Countries,
867            MainListData::Countries("Countries", self.countries.as_ref().map(|d| d.pagination)),
868            self.countries.as_ref().map(|d| d.page_text.clone()),
869            |index, mut row|
870        {
871            let country = &self.countries.as_ref().unwrap().data[index];
872            col_button(&mut row, country, &mut country_selected);
873            col_button(&mut row, &country.region, &mut region_selected);
874            col_button(&mut row, &country.subregion, &mut subregion_selected);
875        });
876
877        if let Some(page_text) = page_text {
878            self.countries.as_mut().unwrap().page_text = page_text;
879        }
880
881        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
882        //<<>><=========================  STATES  ===========================><<>>//
883        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
884
885        let page_text = self.window_table(
886            ctx,
887            &mut main_show[MainList::States],
888            &self.url.for_states(),
889            DataKind::States,
890            MainListData::States("States", self.states.as_ref().map(|d| d.pagination)),
891            self.states.as_ref().map(|d| d.page_text.clone()),
892            |index, mut row|
893        {
894            let state = &self.states.as_ref().unwrap().data[index];
895            col_button(&mut row, state, &mut state_selected);
896            col_button(&mut row, &state.country, &mut country_selected);
897        });
898
899        if let Some(page_text) = page_text {
900            self.states.as_mut().unwrap().page_text = page_text;
901        }
902
903        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
904        //<<>><=========================  CITIES  ===========================><<>>//
905        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
906
907        let page_text = self.window_table(
908            ctx,
909            &mut main_show[MainList::Cities],
910            &self.url.for_cities(),
911            DataKind::Cities,
912            MainListData::Cities("Cities", self.cities.as_ref().map(|d| d.pagination)),
913            self.cities.as_ref().map(|d| d.page_text.clone()),
914            |index, mut row|
915        {
916            let city = &self.cities.as_ref().unwrap().data[index];
917            col_button(&mut row, city, &mut city_selected);
918            col_button(&mut row, &city.state, &mut state_selected);
919            col_button(&mut row, &city.country, &mut country_selected);
920        });
921
922        if let Some(page_text) = page_text {
923            self.cities.as_mut().unwrap().page_text = page_text;
924        }
925
926        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
927        //<<>><=========================  REGIONS  ==========================><<>>//
928        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
929
930        let page_text = self.window_table(
931            ctx,
932            &mut main_show[MainList::Regions],
933            &self.url.for_world_regions(),
934            DataKind::Regions,
935            MainListData::Regions("Regions", self.regions.as_ref().map(|d| d.pagination)),
936            self.regions.as_ref().map(|d| d.page_text.clone()),
937            |index, mut row|
938        {
939            let region = &self.regions.as_ref().unwrap().data[index];
940            col_button(&mut row, region, &mut region_selected);
941        });
942
943        if let Some(page_text) = page_text {
944            self.regions.as_mut().unwrap().page_text = page_text;
945        }
946
947        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
948        //<<>><=======================  SUBREGIONS  =========================><<>>//
949        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
950
951        let page_text = self.window_table(
952            ctx,
953            &mut main_show[MainList::Subregions],
954            &self.url.for_world_subregions(),
955            DataKind::Subregions,
956            MainListData::Subregions("Subregions", self.subregions.as_ref().map(|d| d.pagination)),
957            self.subregions.as_ref().map(|d| d.page_text.clone()),
958            |index, mut row|
959        {
960            let subregion = &self.subregions.as_ref().unwrap().data[index];
961            col_button(&mut row, subregion, &mut subregion_selected);
962            col_button(&mut row, &subregion.region, &mut region_selected);
963        });
964
965        if let Some(page_text) = page_text {
966            self.subregions.as_mut().unwrap().page_text = page_text;
967        }
968
969        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
970        //<<>><=======================  CURRENCIES  =========================><<>>//
971        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
972
973        let page_text = self.window_table(
974            ctx,
975            &mut main_show[MainList::Currencies],
976            &self.url.for_currencies(),
977            DataKind::Currencies,
978            MainListData::Currencies("Currencies", self.currencies.as_ref().map(|d| d.pagination)),
979            self.currencies.as_ref().map(|d| d.page_text.clone()),
980            |index, mut row|
981        {
982            let currency = &self.currencies.as_ref().unwrap().data[index];
983            col_button(&mut row, currency, &mut currency_selected);
984            col_label(&mut row, currency.iso.as_deref().unwrap());
985            col_label(&mut row, &currency.symbol);
986        });
987
988        if let Some(page_text) = page_text {
989            self.currencies.as_mut().unwrap().page_text = page_text;
990        }
991
992        // persist the state of shows
993        self.main_show = main_show;
994
995        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
996        //<<>><===================  COUNTRY WINDOWS  ========================><<>>//
997        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
998
999        {
1000            let mut garbage: Option<String> = None;
1001            let mut states_by_country_selected: Option<Tag> = None;
1002            let mut cities_by_country_selected: Option<Tag> = None;
1003
1004            for (key, object) in self.country_windows.iter_mut() {
1005                egui::Window::new(&object.title)
1006                    .id(format!("country:{}", &object.title).into())
1007                    .open(&mut object.show)
1008                    .default_size(egui::vec2(50.0, 50.0))
1009                    .resizable(false)
1010                    .show(ctx, |ui| {
1011                        if let Some(country) = &object.data {
1012                            ui.group(|ui| {
1013                                egui::Grid::new(&country.name).striped(true).num_columns(2).show(ui, |ui| {
1014                                    data_value(ui, "ISO 2:", country.iso2.as_deref());
1015                                    data_value(ui, "ISO 3:", Some(&country.iso3));
1016                                    data_value(ui, "Code:", Some(&country.code.to_string()));
1017                                    data_value(ui, "TLD:", Some(&country.tld));
1018                                    data_value(ui, "Native:", Some(&country.native));
1019                                    data_value(ui, "Latitude:", Some(&format!("{:.8}", country.latitude)));
1020                                    data_value(ui, "Longitude:", Some(&format!("{:.8}", country.longitude)));
1021                                    data_button(ui, "Capital:", &country.capital, &mut city_selected);
1022                                    data_button(ui, "Currency:", &country.currency, &mut currency_selected);
1023                                    data_button(ui, "Region:", &country.region, &mut region_selected);
1024                                    data_button(ui, "Subregion:", &country.subregion, &mut subregion_selected);
1025                                });
1026                            });
1027
1028                            ui.group(|ui| {
1029                                if let Some(Counts::Country { states, cities }) = object.counts {
1030                                    ui.columns(2, |columns| {
1031                                        filtered_button(&mut columns[0], "States", states, country, &mut states_by_country_selected);
1032                                        filtered_button(&mut columns[1], "Cities", cities, country, &mut cities_by_country_selected);
1033                                    });
1034                                }
1035                            });
1036                        } else {
1037                            spinner(ui);
1038                        }
1039                    });
1040
1041                if !object.show {
1042                    garbage = Some(key.clone());
1043                }
1044            }
1045
1046            if let Some(key) = garbage {
1047                self.country_windows.remove(&key);
1048            }
1049
1050            self.handle_filtered_selection(
1051                ctx,
1052                DataKind::StatesByCountry,
1053                states_by_country_selected,
1054                &mut *self.states_by_country_windows.borrow_mut()
1055            );
1056
1057            self.handle_filtered_selection(
1058                ctx,
1059                DataKind::CitiesByCountry,
1060                cities_by_country_selected,
1061                &mut *self.cities_by_country_windows.borrow_mut()
1062            );
1063        }
1064
1065        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1066        //<<>><=====================  STATE WINDOWS  ========================><<>>//
1067        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1068
1069        {
1070            let mut garbage: Option<String> = None;
1071            let mut cities_by_state_selected: Option<Tag> = None;
1072
1073            for (key, object) in self.state_windows.iter_mut() {
1074                egui::Window::new(&object.title)
1075                    .id(format!("state:{}", &object.title).into())
1076                    .open(&mut object.show)
1077                    .default_size(egui::vec2(50.0, 50.0))
1078                    .resizable(false)
1079                    .show(ctx, |ui| {
1080                        if let Some(state) = &object.data {
1081                            ui.group(|ui| {
1082                                egui::Grid::new(&state.name).striped(true).num_columns(2).show(ui, |ui| {
1083                                    data_value(ui, "Code:", Some(&state.code.to_string()));
1084                                    data_value(ui, "Latitude:", state.latitude.map(|v| format!("{v:.8}")).as_deref());
1085                                    data_value(ui, "Longitude:", state.longitude.map(|v| format!("{v:.8}")).as_deref());
1086                                    data_button(ui, "Country:", &state.country, &mut country_selected);
1087                                });
1088                            });
1089
1090                            ui.group(|ui| {
1091                                if let Some(Counts::State { cities }) = object.counts {
1092                                    filtered_button(ui, "Cities", cities, state, &mut cities_by_state_selected);
1093                                }
1094                            });
1095                        } else {
1096                            spinner(ui);
1097                        }
1098                    });
1099
1100                if !object.show {
1101                    garbage = Some(key.clone());
1102                }
1103            }
1104
1105            if let Some(key) = garbage {
1106                self.state_windows.remove(&key);
1107            }
1108
1109            self.handle_filtered_selection(
1110                ctx,
1111                DataKind::CitiesByState,
1112                cities_by_state_selected,
1113                &mut *self.cities_by_state_windows.borrow_mut()
1114            );
1115        }
1116
1117        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1118        //<<>><======================  CITY WINDOWS  ========================><<>>//
1119        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1120
1121        {
1122            let mut garbage: Option<String> = None;
1123
1124            for (key, object) in self.city_windows.iter_mut() {
1125                egui::Window::new(&object.title)
1126                    .id(format!("city:{}", &object.title).into())
1127                    .open(&mut object.show)
1128                    .default_size(egui::vec2(50.0, 50.0))
1129                    .resizable(false)
1130                    .show(ctx, |ui| {
1131                        if let Some(city) = &object.data {
1132                            ui.group(|ui| {
1133                                egui::Grid::new(&city.name).striped(true).num_columns(2).show(ui, |ui| {
1134                                    data_value(ui, "Latitude:", city.latitude.map(|v| format!("{v:.8}")).as_deref());
1135                                    data_value(ui, "Longitude:", city.longitude.map(|v| format!("{v:.8}")).as_deref());
1136                                    data_button(ui, "State:", &city.state, &mut state_selected);
1137                                    data_button(ui, "Country:", &city.country, &mut country_selected);
1138                                });
1139                            });
1140                        } else {
1141                            spinner(ui);
1142                        }
1143                    });
1144
1145                if !object.show {
1146                    garbage = Some(key.clone());
1147                }
1148            }
1149
1150            if let Some(key) = garbage {
1151                self.city_windows.remove(&key);
1152            }
1153        }
1154
1155        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1156        //<<>><====================  REGION WINDOWS  ========================><<>>//
1157        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1158
1159        {
1160            let mut garbage: Option<String> = None;
1161            let mut countries_by_region_selected: Option<Tag> = None;
1162            let mut subregions_by_region_selected: Option<Tag> = None;
1163
1164            for (key, object) in self.region_windows.iter_mut() {
1165                egui::Window::new(&object.title)
1166                    .id(format!("region:{}", &object.title).into())
1167                    .open(&mut object.show)
1168                    .default_size(egui::vec2(50.0, 50.0))
1169                    .resizable(false)
1170                    .show(ctx, |ui| {
1171                        if let Some(region) = &object.data {
1172                            ui.group(|ui| {
1173                                egui::Grid::new(&region.name).striped(true).num_columns(2).show(ui, |ui| {
1174                                    data_value(ui, "Name:", Some(&region.name));
1175                                });
1176                            });
1177
1178                            ui.group(|ui| {
1179                                if let Some(Counts::Region { countries, subregions }) = object.counts {
1180                                    ui.columns(2, |columns| {
1181                                        filtered_button(&mut columns[0], "Countries", countries, region, &mut countries_by_region_selected);
1182                                        filtered_button(&mut columns[1], "Subregions", subregions, region, &mut subregions_by_region_selected);
1183                                    });
1184                                }
1185                            });
1186                        } else {
1187                            spinner(ui);
1188                        }
1189                    });
1190
1191                if !object.show {
1192                    garbage = Some(key.clone());
1193                }
1194            }
1195
1196            if let Some(key) = garbage {
1197                self.region_windows.remove(&key);
1198            }
1199
1200            self.handle_filtered_selection(
1201                ctx,
1202                DataKind::CountriesByRegion,
1203                countries_by_region_selected,
1204                &mut *self.countries_by_region_windows.borrow_mut()
1205            );
1206
1207            self.handle_filtered_selection(
1208                ctx,
1209                DataKind::SubregionsByRegion,
1210                subregions_by_region_selected,
1211                &mut *self.subregions_by_region_windows.borrow_mut()
1212            );
1213        }
1214
1215        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1216        //<<>><===================  SUBREGION WINDOWS  ======================><<>>//
1217        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1218
1219        {
1220            let mut garbage: Option<String> = None;
1221            let mut countries_by_subregion_selected: Option<Tag> = None;
1222
1223            for (key, object) in self.subregion_windows.iter_mut() {
1224                egui::Window::new(&object.title)
1225                    .id(format!("subregion:{}", &object.title).into())
1226                    .open(&mut object.show)
1227                    .default_size(egui::vec2(50.0, 50.0))
1228                    .resizable(false)
1229                    .show(ctx, |ui| {
1230                        if let Some(subregion) = &object.data {
1231                            ui.group(|ui| {
1232                                egui::Grid::new(&subregion.name).striped(true).num_columns(2).show(ui, |ui| {
1233                                    data_value(ui, "Name:", Some(&subregion.name));
1234                                    data_button(ui, "Region:", &subregion.region, &mut region_selected);
1235                                });
1236                            });
1237
1238                            ui.group(|ui| {
1239                                if let Some(Counts::Subregion { countries }) = object.counts {
1240                                    filtered_button(ui, "Countries", countries, subregion, &mut countries_by_subregion_selected);
1241                                }
1242                            });
1243                        } else {
1244                            spinner(ui);
1245                        }
1246                    });
1247
1248                if !object.show {
1249                    garbage = Some(key.clone());
1250                }
1251            }
1252
1253            if let Some(key) = garbage {
1254                self.subregion_windows.remove(&key);
1255            }
1256
1257            self.handle_filtered_selection(
1258                ctx,
1259                DataKind::CountriesBySubregion,
1260                countries_by_subregion_selected,
1261                &mut *self.countries_by_subregion_windows.borrow_mut()
1262            );
1263        }
1264
1265        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1266        //<<>><====================  CURRENCY WINDOWS  ======================><<>>//
1267        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1268
1269        {
1270            let mut garbage: Option<String> = None;
1271            let mut countries_by_currency_selected: Option<Tag> = None;
1272
1273            for (key, object) in self.currency_windows.iter_mut() {
1274                egui::Window::new(&object.title)
1275                    .id(format!("currency:{}", &object.title).into())
1276                    .open(&mut object.show)
1277                    .default_size(egui::vec2(50.0, 50.0))
1278                    .resizable(false)
1279                    .show(ctx, |ui| {
1280                        if let Some(currency) = &object.data {
1281                            ui.group(|ui| {
1282                                egui::Grid::new(&currency.name).striped(true).num_columns(2).show(ui, |ui| {
1283                                    data_value(ui, "Name:", Some(&currency.name));
1284                                    data_value(ui, "ISO:", currency.iso.as_deref());
1285                                    data_value(ui, "Symbol:", Some(&currency.symbol));
1286                                });
1287                            });
1288
1289                            ui.group(|ui| {
1290                                if let Some(Counts::Currency { countries }) = object.counts {
1291                                    filtered_button(ui, "Countries", countries, currency, &mut countries_by_currency_selected);
1292                                }
1293                            });
1294                        } else {
1295                            spinner(ui);
1296                        }
1297                    });
1298
1299                if !object.show {
1300                    garbage = Some(key.clone());
1301                }
1302            }
1303
1304            if let Some(key) = garbage {
1305                self.currency_windows.remove(&key);
1306            }
1307
1308            self.handle_filtered_selection(
1309                ctx,
1310                DataKind::CountriesByCurrency,
1311                countries_by_currency_selected,
1312                &mut *self.countries_by_currency_windows.borrow_mut()
1313            );
1314        }
1315
1316        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1317        //<<>><====================  FILTERED COUNTRIES =====================><<>>//
1318        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1319
1320        {
1321            let windows: [(DataKind, &mut HashMap<String, FilteredTableData<Country>>); 3] = [
1322                (
1323                    DataKind::CountriesByRegion,
1324                    &mut *self.countries_by_region_windows.borrow_mut(),
1325                ),
1326                (
1327                    DataKind::CountriesBySubregion,
1328                    &mut *self.countries_by_subregion_windows.borrow_mut(),
1329                ),
1330                (
1331                    DataKind::CountriesByCurrency,
1332                    &mut *self.countries_by_currency_windows.borrow_mut(),
1333                ),
1334            ];
1335
1336            for (data_kind, countries_windows) in windows {
1337                let mut garbage: Option<String> = None;
1338
1339                let url_builder: Box<dyn Fn(&str) -> UrlBuilder> = match data_kind {
1340                    DataKind::CountriesByRegion => Box::new(|key| self.url.for_countries_from_region(key)),
1341                    DataKind::CountriesBySubregion => Box::new(|key| self.url.for_countries_from_subregion(key)),
1342                    DataKind::CountriesByCurrency => Box::new(|key| self.url.for_countries_from_currency(key)),
1343                    _ => unreachable!(),
1344                };
1345
1346                for (key, filtered_table_data) in &mut *countries_windows {
1347                    let page_text = self.window_table(
1348                        ctx,
1349                        &mut filtered_table_data.show,
1350                        &url_builder(key),
1351                        data_kind,
1352                        MainListData::Countries(&filtered_table_data.title, filtered_table_data.data.as_ref().map(|d| d.pagination)),
1353                        filtered_table_data.data.as_ref().map(|d| d.page_text.clone()),
1354                        |index, mut row| {
1355                            let country = &filtered_table_data.data.as_ref().unwrap().data[index];
1356                            col_button(&mut row, country, &mut country_selected);
1357                            col_button(&mut row, &country.region, &mut region_selected);
1358                            col_button(&mut row, &country.subregion, &mut subregion_selected);
1359                        });
1360
1361                    if let Some(page_text) = page_text {
1362                        filtered_table_data.data.as_mut().unwrap().page_text = page_text;
1363                    }
1364
1365                    if !filtered_table_data.show {
1366                        garbage = Some(key.clone());
1367                    }
1368                }
1369
1370                if let Some(key) = garbage {
1371                    countries_windows.remove(&key);
1372                }
1373            }
1374        }
1375
1376        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1377        //<<>><=====================  FILTERED STATES  ======================><<>>//
1378        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1379
1380        {
1381            let mut garbage: Option<String> = None;
1382            let mut states_windows = self.states_by_country_windows.borrow_mut();
1383
1384            for (key, filtered_table_data) in &mut *states_windows {
1385                let page_text = self.window_table(
1386                    ctx,
1387                    &mut filtered_table_data.show,
1388                    &self.url.for_states_from_country(key),
1389                    DataKind::StatesByCountry,
1390                    MainListData::States(&filtered_table_data.title, filtered_table_data.data.as_ref().map(|d| d.pagination)),
1391                    filtered_table_data.data.as_ref().map(|d| d.page_text.clone()),
1392                    |index, mut row| {
1393                        let state = &filtered_table_data.data.as_ref().unwrap().data[index];
1394                        col_button(&mut row, state, &mut state_selected);
1395                        col_button(&mut row, &state.country, &mut country_selected);
1396                    });
1397
1398                if let Some(page_text) = page_text {
1399                    filtered_table_data.data.as_mut().unwrap().page_text = page_text;
1400                }
1401
1402                if !filtered_table_data.show {
1403                    garbage = Some(key.clone());
1404                }
1405            }
1406
1407            if let Some(key) = garbage {
1408                states_windows.remove(&key);
1409            }
1410        }
1411
1412        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1413        //<<>><======================  FILTERED CITIES ======================><<>>//
1414        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1415
1416        {
1417            let windows: [(DataKind, &mut HashMap<String, FilteredTableData<City>>); 2] = [
1418                (
1419                    DataKind::CitiesByCountry,
1420                    &mut *self.cities_by_country_windows.borrow_mut(),
1421                ),
1422                (
1423                    DataKind::CitiesByState,
1424                    &mut *self.cities_by_state_windows.borrow_mut(),
1425                ),
1426            ];
1427
1428            for (data_kind, cities_windows) in windows {
1429                let mut garbage: Option<String> = None;
1430
1431                let url_builder: Box<dyn Fn(&str) -> UrlBuilder> = match data_kind {
1432                    DataKind::CitiesByCountry => Box::new(|key| self.url.for_cities_from_country(key)),
1433                    DataKind::CitiesByState => Box::new(|key| self.url.for_cities_from_state(key)),
1434                    _ => unreachable!(),
1435                };
1436
1437                for (key, filtered_table_data) in &mut *cities_windows {
1438                    let page_text = self.window_table(
1439                        ctx,
1440                        &mut filtered_table_data.show,
1441                        &url_builder(key),
1442                        data_kind,
1443                        MainListData::Cities(&filtered_table_data.title, filtered_table_data.data.as_ref().map(|d| d.pagination)),
1444                        filtered_table_data.data.as_ref().map(|d| d.page_text.clone()),
1445                        |index, mut row| {
1446                            let city = &filtered_table_data.data.as_ref().unwrap().data[index];
1447                            col_button(&mut row, city, &mut city_selected);
1448                            col_button(&mut row, &city.state, &mut state_selected);
1449                            col_button(&mut row, &city.country, &mut country_selected);
1450                        });
1451
1452                    if let Some(page_text) = page_text {
1453                        filtered_table_data.data.as_mut().unwrap().page_text = page_text;
1454                    }
1455
1456                    if !filtered_table_data.show {
1457                        garbage = Some(key.clone());
1458                    }
1459                }
1460
1461                if let Some(key) = garbage {
1462                    cities_windows.remove(&key);
1463                }
1464            }
1465        }
1466
1467        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1468        //<<>><==================  FILTERED SUBREGIONS  =====================><<>>//
1469        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1470
1471        {
1472            let mut garbage: Option<String> = None;
1473            let mut subregions_windows = self.subregions_by_region_windows.borrow_mut();
1474
1475            for (key, filtered_table_data) in &mut *subregions_windows {
1476                let page_text = self.window_table(
1477                    ctx,
1478                    &mut filtered_table_data.show,
1479                    &self.url.for_subregions_from_region(key),
1480                    DataKind::SubregionsByRegion,
1481                    MainListData::Subregions(&filtered_table_data.title, filtered_table_data.data.as_ref().map(|d| d.pagination)),
1482                    filtered_table_data.data.as_ref().map(|d| d.page_text.clone()),
1483                    |index, mut row| {
1484                        let subregion = &filtered_table_data.data.as_ref().unwrap().data[index];
1485                        col_button(&mut row, subregion, &mut subregion_selected);
1486                        col_button(&mut row, &subregion.region, &mut region_selected);
1487                    });
1488
1489                if let Some(page_text) = page_text {
1490                    filtered_table_data.data.as_mut().unwrap().page_text = page_text;
1491                }
1492
1493                if !filtered_table_data.show {
1494                    garbage = Some(key.clone());
1495                }
1496            }
1497
1498            if let Some(key) = garbage {
1499                subregions_windows.remove(&key);
1500            }
1501        }
1502
1503        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1504        //<<>><=======================  SELECTION  ==========================><<>>//
1505        //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1506
1507        App::handle_selection(ctx, &self.client, &self.url, &self.channels, DataKind::Country, country_selected, &mut self.country_windows);
1508        App::handle_selection(ctx, &self.client, &self.url, &self.channels, DataKind::State, state_selected, &mut self.state_windows);
1509        App::handle_selection(ctx, &self.client, &self.url, &self.channels, DataKind::City, city_selected, &mut self.city_windows);
1510        App::handle_selection(ctx, &self.client, &self.url, &self.channels, DataKind::Region, region_selected, &mut self.region_windows);
1511        App::handle_selection(ctx, &self.client, &self.url, &self.channels, DataKind::Subregion, subregion_selected, &mut self.subregion_windows);
1512        App::handle_selection(ctx, &self.client, &self.url, &self.channels, DataKind::Currency, currency_selected, &mut self.currency_windows);
1513
1514        self.errors_window(ctx);
1515    }
1516}
1517
1518
1519//<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1520//<<>><======================  GUI COMPONENTS  ======================><<>>//
1521//<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>//
1522
1523fn data_value(ui: &mut egui::Ui, label: &str, value: Option<&str>) {
1524    ui.with_layout(*LAYOUT_LABEL, |ui| {
1525        ui.strong(label);
1526    });
1527    ui.with_layout(*LAYOUT_VALUE, |ui| {
1528        if let Some(value) = value {
1529            ui.label(value);
1530        } else {
1531            ui.weak(NONE);
1532        }
1533    });
1534    ui.end_row();
1535}
1536
1537fn data_button<T>(ui: &mut egui::Ui, label: &str, data: &T, selection: &mut Option<Tag>)
1538where
1539    T: Tagged + Label<LabelType = String>,
1540{
1541    ui.with_layout(*LAYOUT_LABEL, |ui| {
1542        ui.strong(label);
1543    });
1544    ui.with_layout(*LAYOUT_VALUE, |ui| {
1545        let _ = data.label().map(|value| {
1546            if value.is_empty() {
1547                ui.weak(NONE);
1548            } else if ui.button(value).clicked() {
1549                *selection = data.tag().ok();
1550            }
1551        });
1552    });
1553    ui.end_row();
1554}
1555
1556fn filtered_button<T>(ui: &mut egui::Ui, label: &str, count: usize, data: &T, selection: &mut Option<Tag>)
1557where
1558    T: Tagged,
1559{
1560    ui.with_layout(*LAYOUT_BUTTON, |ui| {
1561        if ui.add_enabled(count > 0, egui::Button::new(format!("{label} ({count})")).wrap(false)).clicked() {
1562            *selection = data.tag().ok();
1563        }
1564    });
1565}
1566
1567fn col_label(row: &mut egui_extras::TableRow, label: &str) {
1568    row.col(|ui| {
1569        ui.label(label).on_hover_text(label);
1570    });
1571}
1572
1573fn col_button<T>(row: &mut egui_extras::TableRow, data: &T, selection: &mut Option<Tag>)
1574where
1575    T: Tagged + Label<LabelType = String>,
1576{
1577    row.col(|ui| {
1578        let _ = data.label().map(|label| {
1579            if label.is_empty() {
1580                ui.add_enabled(false, egui::Button::new(NONE));
1581            } else if ui.button(label).on_hover_text(label).clicked() {
1582                *selection = data.tag().ok();
1583            }
1584        });
1585    });
1586}
1587
1588fn spinner(ui: &mut egui::Ui) {
1589    ui.centered_and_justified(|ui| {
1590        ui.spinner();
1591    });
1592}
1593