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
35type 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 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, ..style.visuals.widgets.inactive
163 },
164 hovered: egui::style::WidgetVisuals {
165 weak_bg_fill: THEME.surface2, ..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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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, ¤cy.symbol);
986 });
987
988 if let Some(page_text) = page_text {
989 self.currencies.as_mut().unwrap().page_text = page_text;
990 }
991
992 self.main_show = main_show;
994
995 {
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 {
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 {
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 {
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(®ion.name).striped(true).num_columns(2).show(ui, |ui| {
1174 data_value(ui, "Name:", Some(®ion.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 {
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 {
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(¤cy.name).striped(true).num_columns(2).show(ui, |ui| {
1283 data_value(ui, "Name:", Some(¤cy.name));
1284 data_value(ui, "ISO:", currency.iso.as_deref());
1285 data_value(ui, "Symbol:", Some(¤cy.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 {
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 {
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 {
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 {
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 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
1519fn 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