notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 94b97d247d204073727ca107ff5a433a520fed4c
parent 29da910534f4883de89dbc833bfbdbeea3295e0d
Author: William Casarin <jb55@jb55.com>
Date:   Wed, 13 Nov 2024 13:04:48 -0800

Merge Add External Notifications Column setting #395

kernelkind (2):
      init external notifs column
      use AcquireKeyState for AddColumn

Diffstat:
Msrc/account_manager.rs | 4++--
Msrc/column.rs | 4+++-
Msrc/key_parsing.rs | 24+++++++++++++-----------
Msrc/login_manager.rs | 54+++++++++++++++++++++++++++---------------------------
Msrc/nav.rs | 20+++++---------------
Msrc/route.rs | 26++++++++++++++++++--------
Msrc/ui/account_login_view.rs | 24++++++++++++------------
Msrc/ui/add_column.rs | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/ui/side_panel.rs | 6+++++-
Msrc/view_state.rs | 9++++++---
10 files changed, 279 insertions(+), 89 deletions(-)

diff --git a/src/account_manager.rs b/src/account_manager.rs @@ -6,7 +6,7 @@ use nostrdb::Ndb; use crate::{ column::Columns, imgcache::ImageCache, - login_manager::LoginState, + login_manager::AcquireKeyState, route::{Route, Router}, storage::{KeyStorageResponse, KeyStorageType}, ui::{ @@ -47,7 +47,7 @@ pub fn render_accounts_route( columns: &mut Columns, img_cache: &mut ImageCache, accounts: &mut AccountManager, - login_state: &mut LoginState, + login_state: &mut AcquireKeyState, route: AccountsRoute, ) { let router = columns.column_mut(col).router_mut(); diff --git a/src/column.rs b/src/column.rs @@ -61,7 +61,9 @@ impl Columns { } pub fn new_column_picker(&mut self) { - self.add_column(Column::new(vec![Route::AddColumn])); + self.add_column(Column::new(vec![Route::AddColumn( + crate::ui::add_column::AddColumnRoute::Base, + )])); } fn get_new_id() -> u32 { diff --git a/src/key_parsing.rs b/src/key_parsing.rs @@ -8,21 +8,23 @@ use reqwest::{Request, Response}; use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq)] -pub enum LoginError { +pub enum AcquireKeyError { InvalidKey, Nip05Failed(String), } -impl std::fmt::Display for LoginError { +impl std::fmt::Display for AcquireKeyError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - LoginError::InvalidKey => write!(f, "The inputted key is invalid."), - LoginError::Nip05Failed(e) => write!(f, "Failed to get pubkey from Nip05 address: {e}"), + AcquireKeyError::InvalidKey => write!(f, "The inputted key is invalid."), + AcquireKeyError::Nip05Failed(e) => { + write!(f, "Failed to get pubkey from Nip05 address: {e}") + } } } } -impl std::error::Error for LoginError {} +impl std::error::Error for AcquireKeyError {} #[derive(Deserialize, Serialize)] pub struct Nip05Result { @@ -95,9 +97,9 @@ fn retrieving_nip05_pubkey(key: &str) -> bool { key.contains('@') } -pub fn perform_key_retrieval(key: &str) -> Promise<Result<Keypair, LoginError>> { +pub fn perform_key_retrieval(key: &str) -> Promise<Result<Keypair, AcquireKeyError>> { let key_string = String::from(key); - Promise::spawn_async(async move { get_login_key(&key_string).await }) + Promise::spawn_async(async move { get_key(&key_string).await }) } /// Attempts to turn a string slice key from the user into a Nostr-Sdk Keypair object. @@ -108,7 +110,7 @@ pub fn perform_key_retrieval(key: &str) -> Promise<Result<Keypair, LoginError>> /// - Private hex key: "5dab..." /// - NIP-05 address: "example@nostr.com" /// -pub async fn get_login_key(key: &str) -> Result<Keypair, LoginError> { +pub async fn get_key(key: &str) -> Result<Keypair, AcquireKeyError> { let tmp_key: &str = if let Some(stripped) = key.strip_prefix('@') { stripped } else { @@ -118,7 +120,7 @@ pub async fn get_login_key(key: &str) -> Result<Keypair, LoginError> { if retrieving_nip05_pubkey(tmp_key) { match get_nip05_pubkey(tmp_key).await { Ok(pubkey) => Ok(Keypair::only_pubkey(pubkey)), - Err(e) => Err(LoginError::Nip05Failed(e.to_string())), + Err(e) => Err(AcquireKeyError::Nip05Failed(e.to_string())), } } else if let Ok(pubkey) = Pubkey::try_from_bech32_string(tmp_key, true) { Ok(Keypair::only_pubkey(pubkey)) @@ -127,7 +129,7 @@ pub async fn get_login_key(key: &str) -> Result<Keypair, LoginError> { } else if let Ok(secret_key) = SecretKey::from_str(tmp_key) { Ok(Keypair::from_secret(secret_key)) } else { - Err(LoginError::InvalidKey) + Err(AcquireKeyError::InvalidKey) } } @@ -141,7 +143,7 @@ mod tests { let pubkey_str = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"; let expected_pubkey = Pubkey::try_from_bech32_string(pubkey_str, false).expect("Should not have errored."); - let login_key_result = get_login_key(pubkey_str).await; + let login_key_result = get_key(pubkey_str).await; assert_eq!(Ok(Keypair::only_pubkey(expected_pubkey)), login_key_result); } diff --git a/src/login_manager.rs b/src/login_manager.rs @@ -1,47 +1,47 @@ use crate::key_parsing::perform_key_retrieval; -use crate::key_parsing::LoginError; +use crate::key_parsing::AcquireKeyError; use egui::{TextBuffer, TextEdit}; use enostr::Keypair; use poll_promise::Promise; -/// The UI view interface to log in to a nostr account. +/// The state data for acquiring a nostr key #[derive(Default)] -pub struct LoginState { - login_key: String, - promise_query: Option<(String, Promise<Result<Keypair, LoginError>>)>, - error: Option<LoginError>, +pub struct AcquireKeyState { + desired_key: String, + promise_query: Option<(String, Promise<Result<Keypair, AcquireKeyError>>)>, + error: Option<AcquireKeyError>, key_on_error: Option<String>, should_create_new: bool, } -impl<'a> LoginState { +impl<'a> AcquireKeyState { pub fn new() -> Self { - LoginState::default() + AcquireKeyState::default() } - /// Get the textedit for the login UI without exposing the key variable - pub fn get_login_textedit( + /// Get the textedit for the UI without exposing the key variable + pub fn get_acquire_textedit( &'a mut self, textedit_closure: fn(&'a mut dyn TextBuffer) -> TextEdit<'a>, ) -> TextEdit<'a> { - textedit_closure(&mut self.login_key) + textedit_closure(&mut self.desired_key) } - /// User pressed the 'login' button - pub fn apply_login(&'a mut self) { + /// User pressed the 'acquire' button + pub fn apply_acquire(&'a mut self) { let new_promise = match &self.promise_query { Some((query, _)) => { - if query != &self.login_key { - Some(perform_key_retrieval(&self.login_key)) + if query != &self.desired_key { + Some(perform_key_retrieval(&self.desired_key)) } else { None } } - None => Some(perform_key_retrieval(&self.login_key)), + None => Some(perform_key_retrieval(&self.desired_key)), }; if let Some(new_promise) = new_promise { - self.promise_query = Some((self.login_key.clone(), new_promise)); + self.promise_query = Some((self.desired_key.clone(), new_promise)); } } @@ -51,9 +51,9 @@ impl<'a> LoginState { } /// Whether to indicate to the user that a login error occured - pub fn check_for_error(&'a mut self) -> Option<&'a LoginError> { + pub fn check_for_error(&'a mut self) -> Option<&'a AcquireKeyError> { if let Some(error_key) = &self.key_on_error { - if self.login_key != *error_key { + if self.desired_key != *error_key { self.error = None; self.key_on_error = None; } @@ -73,7 +73,7 @@ impl<'a> LoginState { } Err(e) => { self.error = Some(e); - self.key_on_error = Some(self.login_key.clone()); + self.key_on_error = Some(self.desired_key.clone()); } }; } @@ -100,7 +100,7 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_retrieve_key() { - let mut manager = LoginState::new(); + let mut manager = AcquireKeyState::new(); let expected_str = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"; let expected_key = Keypair::only_pubkey(Pubkey::from_hex(expected_str).unwrap()); @@ -110,21 +110,21 @@ mod tests { let cur_time = start_time.elapsed(); if cur_time < Duration::from_millis(10u64) { - let _ = manager.get_login_textedit(|text| { + let _ = manager.get_acquire_textedit(|text| { text.clear(); text.insert_text("test", 0); egui::TextEdit::singleline(text) }); - manager.apply_login(); + manager.apply_acquire(); } else if cur_time < Duration::from_millis(30u64) { - let _ = manager.get_login_textedit(|text| { + let _ = manager.get_acquire_textedit(|text| { text.clear(); text.insert_text("test2", 0); egui::TextEdit::singleline(text) }); - manager.apply_login(); + manager.apply_acquire(); } else { - let _ = manager.get_login_textedit(|text| { + let _ = manager.get_acquire_textedit(|text| { text.clear(); text.insert_text( "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", @@ -132,7 +132,7 @@ mod tests { ); egui::TextEdit::singleline(text) }); - manager.apply_login(); + manager.apply_acquire(); } if let Some(key) = manager.check_for_successful_login() { diff --git a/src/nav.rs b/src/nav.rs @@ -13,7 +13,7 @@ use crate::{ }, ui::{ self, - add_column::{AddColumnResponse, AddColumnView}, + add_column::render_add_column_routes, anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE}, note::PostAction, support::SupportView, @@ -101,19 +101,9 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) { None } - Route::AddColumn => { - let resp = - AddColumnView::new(&app.ndb, app.accounts.get_selected_account()).ui(ui); - - if let Some(resp) = resp { - match resp { - AddColumnResponse::Timeline(timeline) => { - let id = timeline.id; - app.columns_mut().add_timeline_to_column(col, timeline); - app.subscribe_new_timeline(id); - } - }; - } + Route::AddColumn(route) => { + render_add_column_routes(ui, app, col, route); + None } @@ -205,7 +195,7 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) { let cur_router = app.columns_mut().column_mut(col).router_mut(); cur_router.navigating = false; if cur_router.is_replacing() { - cur_router.remove_previous_route(); + cur_router.remove_previous_routes(); } } diff --git a/src/route.rs b/src/route.rs @@ -6,7 +6,10 @@ use crate::{ account_manager::AccountsRoute, column::Columns, timeline::{TimelineId, TimelineRoute}, - ui::profile::preview::{get_note_users_displayname_string, get_profile_displayname_string}, + ui::{ + add_column::AddColumnRoute, + profile::preview::{get_note_users_displayname_string, get_profile_displayname_string}, + }, }; /// App routing. These describe different places you can go inside Notedeck. @@ -16,7 +19,7 @@ pub enum Route { Accounts(AccountsRoute), Relays, ComposeNote, - AddColumn, + AddColumn(AddColumnRoute), Profile(Pubkey), Support, } @@ -97,7 +100,13 @@ impl Route { AccountsRoute::AddAccount => "Add Account".to_owned(), }, Route::ComposeNote => "Compose Note".to_owned(), - Route::AddColumn => "Add Column".to_owned(), + Route::AddColumn(c) => match c { + AddColumnRoute::Base => "Add Column".to_owned(), + AddColumnRoute::UndecidedNotification => "Add Notifications Column".to_owned(), + AddColumnRoute::ExternalNotification => { + "Add External Notifications Column".to_owned() + } + }, Route::Profile(pubkey) => { format!("{}'s Profile", get_profile_displayname_string(ndb, pubkey)) } @@ -142,7 +151,7 @@ impl<R: Clone> Router<R> { self.routes.push(route); } - // Route to R. Then when it is successfully placed, should call `remove_previous_route` + // Route to R. Then when it is successfully placed, should call `remove_previous_routes` to remove all previous routes pub fn route_to_replaced(&mut self, route: R) { self.navigating = true; self.replacing = true; @@ -167,14 +176,15 @@ impl<R: Clone> Router<R> { self.routes.pop() } - pub fn remove_previous_route(&mut self) -> Option<R> { + pub fn remove_previous_routes(&mut self) { let num_routes = self.routes.len(); if num_routes <= 1 { - return None; + return; } + self.returning = false; self.replacing = false; - Some(self.routes.remove(num_routes - 2)) + self.routes.drain(..num_routes - 1); } pub fn is_replacing(&self) -> bool { @@ -208,7 +218,7 @@ impl fmt::Display for Route { }, Route::ComposeNote => write!(f, "Compose Note"), - Route::AddColumn => write!(f, "Add Column"), + Route::AddColumn(_) => write!(f, "Add Column"), Route::Profile(_) => write!(f, "Profile"), Route::Support => write!(f, "Support"), } diff --git a/src/ui/account_login_view.rs b/src/ui/account_login_view.rs @@ -1,13 +1,13 @@ use crate::app_style::NotedeckTextStyle; -use crate::key_parsing::LoginError; -use crate::login_manager::LoginState; +use crate::key_parsing::AcquireKeyError; +use crate::login_manager::AcquireKeyState; use crate::ui::{Preview, PreviewConfig, View}; use egui::TextEdit; use egui::{Align, Button, Color32, Frame, InnerResponse, Margin, RichText, Vec2}; use enostr::Keypair; pub struct AccountLoginView<'a> { - manager: &'a mut LoginState, + manager: &'a mut AcquireKeyState, } pub enum AccountLoginResponse { @@ -16,7 +16,7 @@ pub enum AccountLoginResponse { } impl<'a> AccountLoginView<'a> { - pub fn new(state: &'a mut LoginState) -> Self { + pub fn new(state: &'a mut AcquireKeyState) -> Self { AccountLoginView { manager: state } } @@ -43,7 +43,7 @@ impl<'a> AccountLoginView<'a> { self.loading_and_error(ui); if ui.add(login_button()).clicked() { - self.manager.apply_login(); + self.manager.apply_acquire(); } }); @@ -90,13 +90,13 @@ impl<'a> AccountLoginView<'a> { } } -fn show_error(ui: &mut egui::Ui, err: &LoginError) { +fn show_error(ui: &mut egui::Ui, err: &AcquireKeyError) { ui.horizontal(|ui| { let error_label = match err { - LoginError::InvalidKey => { + AcquireKeyError::InvalidKey => { egui::Label::new(RichText::new("Invalid key.").color(ui.visuals().error_fg_color)) } - LoginError::Nip05Failed(e) => { + AcquireKeyError::Nip05Failed(e) => { egui::Label::new(RichText::new(e).color(ui.visuals().error_fg_color)) } }; @@ -126,8 +126,8 @@ fn login_button() -> Button<'static> { .min_size(Vec2::new(0.0, 40.0)) } -fn login_textedit(manager: &mut LoginState) -> TextEdit { - manager.get_login_textedit(|text| { +fn login_textedit(manager: &mut AcquireKeyState) -> TextEdit { + manager.get_acquire_textedit(|text| { egui::TextEdit::singleline(text) .hint_text( RichText::new("Enter your public key (npub, nip05), or private key (nsec) here...") @@ -143,7 +143,7 @@ mod preview { use super::*; pub struct AccountLoginPreview { - manager: LoginState, + manager: AcquireKeyState, } impl View for AccountLoginPreview { @@ -157,7 +157,7 @@ mod preview { fn preview(cfg: PreviewConfig) -> Self::Prev { let _ = cfg; - let manager = LoginState::new(); + let manager = AcquireKeyState::new(); AccountLoginPreview { manager } } } diff --git a/src/ui/add_column.rs b/src/ui/add_column.rs @@ -1,26 +1,51 @@ -use egui::{pos2, vec2, Color32, FontId, ImageSource, Pos2, Rect, Separator, Ui}; +use core::f32; +use std::collections::HashMap; + +use egui::{ + pos2, vec2, Align, Color32, FontId, Id, ImageSource, Margin, Pos2, Rect, RichText, Separator, + Ui, Vec2, +}; use nostrdb::Ndb; +use tracing::error; use crate::{ app_style::{get_font_size, NotedeckTextStyle}, + login_manager::AcquireKeyState, timeline::{PubkeySource, Timeline, TimelineKind}, ui::anim::ICON_EXPANSION_MULTIPLE, user_account::UserAccount, + Damus, }; -use super::anim::AnimationHelper; +use super::{anim::AnimationHelper, padding}; pub enum AddColumnResponse { Timeline(Timeline), + UndecidedNotification, + ExternalNotification, +} + +pub enum NotificationColumnType { + Home, + External, } #[derive(Clone, Debug)] enum AddColumnOption { Universe, + UndecidedNotification, + ExternalNotification, Notification(PubkeySource), Home(PubkeySource), } +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum AddColumnRoute { + Base, + UndecidedNotification, + ExternalNotification, +} + impl AddColumnOption { pub fn take_as_response( self, @@ -34,28 +59,41 @@ impl AddColumnOption { AddColumnOption::Notification(pubkey) => TimelineKind::Notifications(pubkey) .into_timeline(ndb, cur_account.map(|a| a.pubkey.bytes())) .map(AddColumnResponse::Timeline), + AddColumnOption::UndecidedNotification => { + Some(AddColumnResponse::UndecidedNotification) + } AddColumnOption::Home(pubkey) => { let tlk = TimelineKind::contact_list(pubkey); tlk.into_timeline(ndb, cur_account.map(|a| a.pubkey.bytes())) .map(AddColumnResponse::Timeline) } + AddColumnOption::ExternalNotification => Some(AddColumnResponse::ExternalNotification), } } } pub struct AddColumnView<'a> { + key_state_map: &'a mut HashMap<Id, AcquireKeyState>, ndb: &'a Ndb, cur_account: Option<&'a UserAccount>, } impl<'a> AddColumnView<'a> { - pub fn new(ndb: &'a Ndb, cur_account: Option<&'a UserAccount>) -> Self { - Self { ndb, cur_account } + pub fn new( + key_state_map: &'a mut HashMap<Id, AcquireKeyState>, + ndb: &'a Ndb, + cur_account: Option<&'a UserAccount>, + ) -> Self { + Self { + key_state_map, + ndb, + cur_account, + } } pub fn ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> { let mut selected_option: Option<AddColumnResponse> = None; - for column_option_data in self.get_column_options() { + for column_option_data in self.get_base_options() { let option = column_option_data.option.clone(); if self.column_option_ui(ui, column_option_data).clicked() { selected_option = option.take_as_response(self.ndb, self.cur_account); @@ -67,6 +105,66 @@ impl<'a> AddColumnView<'a> { selected_option } + fn notifications_ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> { + let mut selected_option: Option<AddColumnResponse> = None; + for column_option_data in self.get_notifications_options() { + let option = column_option_data.option.clone(); + if self.column_option_ui(ui, column_option_data).clicked() { + selected_option = option.take_as_response(self.ndb, self.cur_account); + } + + ui.add(Separator::default().spacing(0.0)); + } + + selected_option + } + + fn external_notification_ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> { + padding(16.0, ui, |ui| { + let id = ui.id().with("external_notif"); + let key_state = self.key_state_map.entry(id).or_default(); + + let text_edit = key_state.get_acquire_textedit(|text| { + egui::TextEdit::singleline(text) + .hint_text( + RichText::new("Enter the user's key (npub, hex, nip05) here...") + .text_style(NotedeckTextStyle::Body.text_style()), + ) + .vertical_align(Align::Center) + .desired_width(f32::INFINITY) + .min_size(Vec2::new(0.0, 40.0)) + .margin(Margin::same(12.0)) + }); + + ui.add(text_edit); + + if ui.button("Add").clicked() { + key_state.apply_acquire(); + } + + if key_state.is_awaiting_network() { + ui.spinner(); + } + + if let Some(error) = key_state.check_for_error() { + error!("acquire key error: {}", error); + ui.colored_label( + Color32::RED, + "Please enter a valid npub, public hex key or nip05", + ); + } + + if let Some(keypair) = key_state.check_for_successful_login() { + key_state.should_create_new(); + AddColumnOption::Notification(PubkeySource::Explicit(keypair.pubkey)) + .take_as_response(self.ndb, self.cur_account) + } else { + None + } + }) + .inner + } + fn column_option_ui(&mut self, ui: &mut Ui, data: ColumnOptionData) -> egui::Response { let icon_padding = 8.0; let min_icon_width = 32.0; @@ -168,7 +266,7 @@ impl<'a> AddColumnView<'a> { helper.take_animation_response() } - fn get_column_options(&self) -> Vec<ColumnOptionData> { + fn get_base_options(&self) -> Vec<ColumnOptionData> { let mut vec = Vec::new(); vec.push(ColumnOptionData { title: "Universe", @@ -190,14 +288,42 @@ impl<'a> AddColumnView<'a> { icon: egui::include_image!("../../assets/icons/home_icon_dark_4x.png"), option: AddColumnOption::Home(source.clone()), }); + } + vec.push(ColumnOptionData { + title: "Notifications", + description: "Stay up to date with notifications and mentions", + icon: egui::include_image!("../../assets/icons/notifications_icon_dark_4x.png"), + option: AddColumnOption::UndecidedNotification, + }); + + vec + } + + fn get_notifications_options(&self) -> Vec<ColumnOptionData> { + let mut vec = Vec::new(); + + if let Some(acc) = self.cur_account { + let source = if acc.secret_key.is_some() { + PubkeySource::DeckAuthor + } else { + PubkeySource::Explicit(acc.pubkey) + }; + vec.push(ColumnOptionData { - title: "Notifications", - description: "Stay up to date with notifications and mentions", + title: "Your Notifications", + description: "Stay up to date with your notifications and mentions", icon: egui::include_image!("../../assets/icons/notifications_icon_dark_4x.png"), option: AddColumnOption::Notification(source), }); } + vec.push(ColumnOptionData { + title: "Someone else's Notifications", + description: "Stay up to date with someone else's notifications and mentions", + icon: egui::include_image!("../../assets/icons/notifications_icon_dark_4x.png"), + option: AddColumnOption::ExternalNotification, + }); + vec } } @@ -209,6 +335,54 @@ struct ColumnOptionData { option: AddColumnOption, } +pub fn render_add_column_routes( + ui: &mut egui::Ui, + app: &mut Damus, + col: usize, + route: &AddColumnRoute, +) { + let resp = match route { + AddColumnRoute::Base => AddColumnView::new( + &mut app.view_state.id_state_map, + &app.ndb, + app.accounts.get_selected_account(), + ) + .ui(ui), + AddColumnRoute::UndecidedNotification => AddColumnView::new( + &mut app.view_state.id_state_map, + &app.ndb, + app.accounts.get_selected_account(), + ) + .notifications_ui(ui), + AddColumnRoute::ExternalNotification => AddColumnView::new( + &mut app.view_state.id_state_map, + &app.ndb, + app.accounts.get_selected_account(), + ) + .external_notification_ui(ui), + }; + + if let Some(resp) = resp { + match resp { + AddColumnResponse::Timeline(timeline) => { + let id = timeline.id; + app.columns_mut().add_timeline_to_column(col, timeline); + app.subscribe_new_timeline(id); + } + AddColumnResponse::UndecidedNotification => { + app.columns_mut().column_mut(col).router_mut().route_to( + crate::route::Route::AddColumn(AddColumnRoute::UndecidedNotification), + ); + } + AddColumnResponse::ExternalNotification => { + app.columns_mut().column_mut(col).router_mut().route_to( + crate::route::Route::AddColumn(AddColumnRoute::ExternalNotification), + ); + } + }; + } +} + mod preview { use crate::{ test_data, @@ -232,7 +406,12 @@ mod preview { impl View for AddColumnPreview { fn ui(&mut self, ui: &mut egui::Ui) { - AddColumnView::new(&self.app.ndb, self.app.accounts.get_selected_account()).ui(ui); + AddColumnView::new( + &mut self.app.view_state.id_state_map, + &self.app.ndb, + self.app.accounts.get_selected_account(), + ) + .ui(ui); } } diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs @@ -196,7 +196,11 @@ impl<'a> DesktopSidePanel<'a> { } } SidePanelAction::Columns => { - if router.routes().iter().any(|&r| r == Route::AddColumn) { + if router + .routes() + .iter() + .any(|&r| matches!(r, Route::AddColumn(_))) + { router.go_back(); } else { columns.new_column_picker(); diff --git a/src/view_state.rs b/src/view_state.rs @@ -1,13 +1,16 @@ -use crate::login_manager::LoginState; +use std::collections::HashMap; + +use crate::login_manager::AcquireKeyState; /// Various state for views #[derive(Default)] pub struct ViewState { - pub login: LoginState, + pub login: AcquireKeyState, + pub id_state_map: HashMap<egui::Id, AcquireKeyState>, } impl ViewState { - pub fn login_mut(&mut self) -> &mut LoginState { + pub fn login_mut(&mut self) -> &mut AcquireKeyState { &mut self.login } }