notedeck

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

commit 56a8ba30f33169ea519d882d377fbef00a24b1d4
parent 845a983592e536ec2393eabd5e955ccc4189ed2b
Author: kernelkind <kernelkind@gmail.com>
Date:   Thu,  5 Dec 2024 20:01:27 -0500

deck actions

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Msrc/accounts/mod.rs | 84++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/accounts/route.rs | 6++++++
Msrc/app.rs | 15+++++++++------
Msrc/column.rs | 5+++++
Msrc/decks.rs | 8++++----
Msrc/nav.rs | 128++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/route.rs | 6++++++
Msrc/ui/column/header.rs | 2++
Msrc/ui/side_panel.rs | 28+++++++++++++++++++++++++++-
Msrc/view_state.rs | 2++
10 files changed, 230 insertions(+), 54 deletions(-)

diff --git a/src/accounts/mod.rs b/src/accounts/mod.rs @@ -28,7 +28,7 @@ use tracing::{debug, error, info}; mod route; -pub use route::{AccountsRoute, AccountsRouteResponse}; +pub use route::{AccountsAction, AccountsRoute, AccountsRouteResponse}; pub struct AccountRelayData { filter: Filter, @@ -225,6 +225,22 @@ pub struct Accounts { needs_relay_config: bool, } +#[must_use = "You must call process_login_action on this to handle unknown ids"] +pub struct RenderAccountAction { + pub accounts_action: Option<AccountsAction>, + pub unk_id_action: SingleUnkIdAction, +} + +impl RenderAccountAction { + // Simple wrapper around processing the unknown action to expose too + // much internal logic. This allows us to have a must_use on our + // LoginAction type, otherwise the SingleUnkIdAction's must_use will + // be lost when returned in the login action + pub fn process_action(&mut self, ids: &mut UnknownIds, ndb: &Ndb, txn: &Transaction) { + self.unk_id_action.process_action(ids, ndb, txn); + } +} + /// Render account management views from a route #[allow(clippy::too_many_arguments)] pub fn render_accounts_route( @@ -236,7 +252,7 @@ pub fn render_accounts_route( decks: &mut DecksCache, login_state: &mut AcquireKeyState, route: AccountsRoute, -) -> SingleUnkIdAction { +) -> RenderAccountAction { let resp = match route { AccountsRoute::Accounts => AccountsView::new(ndb, accounts, img_cache) .ui(ui) @@ -252,8 +268,11 @@ pub fn render_accounts_route( if let Some(resp) = resp { match resp { AccountsRouteResponse::Accounts(response) => { - process_accounts_view_response(accounts, decks, col, response); - SingleUnkIdAction::no_action() + let action = process_accounts_view_response(accounts, decks, col, response); + RenderAccountAction { + accounts_action: action, + unk_id_action: SingleUnkIdAction::no_action(), + } } AccountsRouteResponse::AddAccount(response) => { let action = process_login_view_response(accounts, decks, response); @@ -266,7 +285,10 @@ pub fn render_accounts_route( } } } else { - SingleUnkIdAction::no_action() + RenderAccountAction { + accounts_action: None, + unk_id_action: SingleUnkIdAction::no_action(), + } } } @@ -275,21 +297,28 @@ pub fn process_accounts_view_response( decks: &mut DecksCache, col: usize, response: AccountsViewResponse, -) { +) -> Option<AccountsAction> { let router = get_active_columns_mut(accounts, decks) .column_mut(col) .router_mut(); + let mut selection = None; match response { AccountsViewResponse::RemoveAccount(index) => { - accounts.remove_account(index); + let acc_sel = AccountsAction::Remove(index); + info!("account selection: {:?}", acc_sel); + selection = Some(acc_sel); } AccountsViewResponse::SelectAccount(index) => { - accounts.select_account(index); + let acc_sel = AccountsAction::Switch(index); + info!("account selection: {:?}", acc_sel); + selection = Some(acc_sel); } AccountsViewResponse::RouteToLogin => { router.route_to(Route::add_account()); } } + + selection } impl Accounts { @@ -382,7 +411,7 @@ impl Accounts { } #[must_use = "UnknownIdAction's must be handled. Use .process_unknown_id_action()"] - pub fn add_account(&mut self, account: Keypair) -> LoginAction { + pub fn add_account(&mut self, account: Keypair) -> RenderAccountAction { let pubkey = account.pubkey; let switch_to_index = if let Some(contains_acc) = self.contains_account(pubkey.bytes()) { if account.secret_key.is_some() && !contains_acc.has_nsec { @@ -404,9 +433,9 @@ impl Accounts { self.accounts.len() - 1 }; - LoginAction { - unk: SingleUnkIdAction::pubkey(pubkey), - switch_to_index, + RenderAccountAction { + accounts_action: Some(AccountsAction::Switch(switch_to_index)), + unk_id_action: SingleUnkIdAction::pubkey(pubkey), } } @@ -628,33 +657,22 @@ pub fn process_login_view_response( manager: &mut Accounts, decks: &mut DecksCache, response: AccountLoginResponse, -) -> SingleUnkIdAction { - let (pubkey, login_action) = match response { +) -> RenderAccountAction { + let (r, pubkey) = match response { AccountLoginResponse::CreateNew => { let kp = FullKeypair::generate().to_keypair(); - (kp.pubkey, manager.add_account(kp)) + let pubkey = kp.pubkey; + (manager.add_account(kp), pubkey) + } + AccountLoginResponse::LoginWith(keypair) => { + let pubkey = keypair.pubkey; + (manager.add_account(keypair), pubkey) } - AccountLoginResponse::LoginWith(keypair) => (keypair.pubkey, manager.add_account(keypair)), }; - manager.select_account(login_action.switch_to_index); - decks.add_deck_default(pubkey); - login_action.unk -} -#[must_use = "You must call process_login_action on this to handle unknown ids"] -pub struct LoginAction { - unk: SingleUnkIdAction, - pub switch_to_index: usize, -} + decks.add_deck_default(pubkey); -impl LoginAction { - // Simple wrapper around processing the unknown action to expose too - // much internal logic. This allows us to have a must_use on our - // LoginAction type, otherwise the SingleUnkIdAction's must_use will - // be lost when returned in the login action - pub fn process_action(&mut self, ids: &mut UnknownIds, ndb: &Ndb, txn: &Transaction) { - self.unk.process_action(ids, ndb, txn); - } + r } #[derive(Default)] diff --git a/src/accounts/route.rs b/src/accounts/route.rs @@ -11,3 +11,9 @@ pub enum AccountsRoute { Accounts, AddAccount, } + +#[derive(Debug)] +pub enum AccountsAction { + Switch(usize), + Remove(usize), +} diff --git a/src/app.rs b/src/app.rs @@ -459,10 +459,6 @@ impl Damus { Columns::new() } } else { - info!( - "Using columns from command line arguments: {:?}", - parsed_args.columns - ); let mut columns: Columns = Columns::new(); for col in parsed_args.columns { if let Some(timeline) = col.into_timeline(&ndb, account) { @@ -719,6 +715,7 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) { ) .clip(true) .horizontal(|mut strip| { + let mut side_panel_action: Option<nav::SwitchingAction> = None; strip.cell(|ui| { let rect = ui.available_rect_before_wrap(); let side_panel = DesktopSidePanel::new( @@ -730,12 +727,14 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) { .show(ui); if side_panel.response.clicked() || side_panel.response.secondary_clicked() { - DesktopSidePanel::perform_action( + if let Some(action) = DesktopSidePanel::perform_action( &mut app.decks_cache, &app.accounts, &mut app.support, side_panel.action, - ); + ) { + side_panel_action = Some(action); + } } // vertical sidebar line @@ -765,6 +764,10 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) { } let mut save_cols = false; + if let Some(action) = side_panel_action { + save_cols = save_cols || action.process(app); + } + for response in responses { let save = response.process_render_nav_response(app); save_cols = save_cols || save; diff --git a/src/column.rs b/src/column.rs @@ -293,3 +293,8 @@ pub enum IntermediaryRoute { Timeline(Timeline), Route(Route), } + +pub enum ColumnsAction { + // Switch(usize), TODO: could use for keyboard selection + Remove(usize), +} diff --git a/src/decks.rs b/src/decks.rs @@ -16,10 +16,10 @@ pub static FALLBACK_PUBKEY: fn() -> Pubkey = || { Pubkey::from_hex("aa733081e4f0f79dd43023d8983265593f2b41a988671cfcef3f489b91ad93fe").unwrap() }; -//pub enum DecksAction { -// Switch(usize), -// Removing(usize), -//} +pub enum DecksAction { + Switch(usize), + Removing(usize), +} pub struct DecksCache { account_to_decks: HashMap<Pubkey, Decks>, diff --git a/src/nav.rs b/src/nav.rs @@ -1,7 +1,10 @@ use crate::{ - accounts::render_accounts_route, + accounts::{render_accounts_route, AccountsAction}, actionbar::NoteAction, - app::{get_active_columns, get_active_columns_mut}, + app::{get_active_columns, get_active_columns_mut, get_decks_mut}, + column::ColumnsAction, + deck_state::DeckState, + decks::{Deck, DecksAction}, notes_holder::NotesHolder, profile::Profile, relay_pool_manager::RelayPoolManager, @@ -15,6 +18,8 @@ use crate::{ self, add_column::render_add_column_routes, column::NavTitle, + configure_deck::ConfigureDeckView, + edit_deck::{EditDeckResponse, EditDeckView}, note::{PostAction, PostType}, support::SupportView, RelayView, View, @@ -26,11 +31,45 @@ use egui_nav::{Nav, NavAction, NavResponse, NavUiType}; use nostrdb::{Ndb, Transaction}; use tracing::{error, info}; +#[allow(clippy::enum_variant_names)] pub enum RenderNavAction { Back, RemoveColumn, PostAction(PostAction), NoteAction(NoteAction), + SwitchingAction(SwitchingAction), +} + +pub enum SwitchingAction { + Accounts(AccountsAction), + Columns(ColumnsAction), + Decks(crate::decks::DecksAction), +} + +impl SwitchingAction { + /// process the action, and return whether switching occured + pub fn process(&self, app: &mut Damus) -> bool { + match &self { + SwitchingAction::Accounts(account_action) => match *account_action { + AccountsAction::Switch(index) => app.accounts.select_account(index), + AccountsAction::Remove(index) => app.accounts.remove_account(index), + }, + SwitchingAction::Columns(columns_action) => match *columns_action { + ColumnsAction::Remove(index) => { + get_active_columns_mut(&app.accounts, &mut app.decks_cache).delete_column(index) + } + }, + SwitchingAction::Decks(decks_action) => match *decks_action { + DecksAction::Switch(index) => { + get_decks_mut(&app.accounts, &mut app.decks_cache).set_active(index) + } + DecksAction::Removing(index) => { + get_decks_mut(&app.accounts, &mut app.decks_cache).remove_deck(index) + } + }, + } + true + } } impl From<PostAction> for RenderNavAction { @@ -60,7 +99,7 @@ impl RenderNavResponse { #[must_use = "Make sure to save columns if result is true"] pub fn process_render_nav_response(&self, app: &mut Damus) -> bool { - let mut col_changed: bool = false; + let mut switching_occured: bool = false; let col = self.column; if let Some(action) = self @@ -82,7 +121,7 @@ impl RenderNavResponse { } app.columns_mut().delete_column(col); - col_changed = true; + switching_occured = true; } RenderNavAction::PostAction(post_action) => { @@ -109,6 +148,10 @@ impl RenderNavResponse { &app.accounts.mutefun(), ); } + + RenderNavAction::SwitchingAction(switching_action) => { + switching_occured = switching_action.process(app); + } } } @@ -148,7 +191,12 @@ impl RenderNavResponse { &app.accounts.mutefun(), ); } - col_changed = true; + + if let Some(Route::EditDeck(index)) = r { + SwitchingAction::Decks(DecksAction::Removing(index)).process(app); + } + + switching_occured = true; } NavAction::Navigated => { @@ -157,7 +205,7 @@ impl RenderNavResponse { if cur_router.is_replacing() { cur_router.remove_previous_routes(); } - col_changed = true; + switching_occured = true; } NavAction::Dragging => {} @@ -167,7 +215,7 @@ impl RenderNavResponse { } } - col_changed + switching_occured } } @@ -194,7 +242,7 @@ fn render_nav_body( ui, ), Route::Accounts(amr) => { - let action = render_accounts_route( + let mut action = render_accounts_route( ui, &app.ndb, col, @@ -206,7 +254,9 @@ fn render_nav_body( ); let txn = Transaction::new(&app.ndb).expect("txn"); action.process_action(&mut app.unknown_ids, &app.ndb, &txn); - None + action + .accounts_action + .map(|f| RenderNavAction::SwitchingAction(SwitchingAction::Accounts(f))) } Route::Relays => { let manager = RelayPoolManager::new(app.pool_mut()); @@ -235,11 +285,69 @@ fn render_nav_body( None } - Route::Support => { SupportView::new(&mut app.support).show(ui); None } + Route::NewDeck => { + let id = ui.id().with("new-deck"); + let new_deck_state = app.view_state.id_to_deck_state.entry(id).or_default(); + let mut resp = None; + if let Some(config_resp) = ConfigureDeckView::new(new_deck_state).ui(ui) { + if let Some(cur_acc) = app.accounts.get_selected_account() { + app.decks_cache.add_deck( + cur_acc.pubkey, + Deck::new(config_resp.icon, config_resp.name), + ); + + // set new deck as active + let cur_index = get_decks_mut(&app.accounts, &mut app.decks_cache) + .decks() + .len() + - 1; + resp = Some(RenderNavAction::SwitchingAction(SwitchingAction::Decks( + DecksAction::Switch(cur_index), + ))); + } + + new_deck_state.clear(); + get_active_columns_mut(&app.accounts, &mut app.decks_cache) + .get_first_router() + .go_back(); + } + resp + } + Route::EditDeck(index) => { + let cur_deck = get_decks_mut(&app.accounts, &mut app.decks_cache) + .decks_mut() + .get_mut(*index) + .expect("index wasn't valid"); + let id = ui.id().with(( + "edit-deck", + app.accounts.get_selected_account().map(|k| k.pubkey), + index, + )); + let deck_state = app + .view_state + .id_to_deck_state + .entry(id) + .or_insert_with(|| DeckState::from_deck(cur_deck)); + if let Some(resp) = EditDeckView::new(deck_state).ui(ui) { + match resp { + EditDeckResponse::Edit(configure_deck_response) => { + cur_deck.edit(configure_deck_response); + } + EditDeckResponse::Delete => { + deck_state.deleting = true; + } + } + get_active_columns_mut(&app.accounts, &mut app.decks_cache) + .get_first_router() + .go_back(); + } + + None + } } } diff --git a/src/route.rs b/src/route.rs @@ -21,6 +21,8 @@ pub enum Route { ComposeNote, AddColumn(AddColumnRoute), Support, + NewDeck, + EditDeck(usize), } impl Route { @@ -95,6 +97,8 @@ impl Route { AddColumnRoute::Hashtag => Cow::Borrowed("Add Hashtag Column"), }, Route::Support => Cow::Borrowed("Damus Support"), + Route::NewDeck => Cow::Borrowed("Add Deck"), + Route::EditDeck(_) => Cow::Borrowed("Edit Deck"), } } } @@ -204,6 +208,8 @@ impl fmt::Display for Route { Route::AddColumn(_) => write!(f, "Add Column"), Route::Support => write!(f, "Support"), + Route::NewDeck => write!(f, "Add Deck"), + Route::EditDeck(_) => write!(f, "Edit Deck"), } } } diff --git a/src/ui/column/header.rs b/src/ui/column/header.rs @@ -205,6 +205,8 @@ impl<'a> NavTitle<'a> { Route::AddColumn(_add_col_route) => {} Route::Support => {} Route::Relays => {} + Route::NewDeck => {} + Route::EditDeck(_) => {} } } diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs @@ -8,8 +8,10 @@ use crate::{ app::get_active_columns_mut, app_style, colors, column::Column, + decks::DecksAction, decks::DecksCache, imgcache::ImageCache, + nav::SwitchingAction, route::Route, support::Support, user_account::UserAccount, @@ -48,6 +50,9 @@ pub enum SidePanelAction { Search, ExpandSidePanel, Support, + NewDeck, + SwitchDeck(usize), + EditDeck(usize), } pub struct SidePanelResponse { @@ -210,8 +215,9 @@ impl<'a> DesktopSidePanel<'a> { accounts: &Accounts, support: &mut Support, action: SidePanelAction, - ) { + ) -> Option<SwitchingAction> { let router = get_active_columns_mut(accounts, decks_cache).get_first_router(); + let mut switching_response = None; match action { SidePanelAction::Panel => {} // TODO SidePanelAction::Account => { @@ -268,7 +274,27 @@ impl<'a> DesktopSidePanel<'a> { router.route_to(Route::Support); } } + SidePanelAction::NewDeck => { + if router.routes().iter().any(|&r| r == Route::NewDeck) { + router.go_back(); + } else { + router.route_to(Route::NewDeck); + } + } + SidePanelAction::SwitchDeck(index) => { + switching_response = Some(crate::nav::SwitchingAction::Decks(DecksAction::Switch( + index, + ))) + } + SidePanelAction::EditDeck(index) => { + if router.routes().iter().any(|&r| r == Route::EditDeck(index)) { + router.go_back(); + } else { + router.route_to(Route::EditDeck(index)); + } + } } + switching_response } } diff --git a/src/view_state.rs b/src/view_state.rs @@ -1,11 +1,13 @@ use std::collections::HashMap; +use crate::deck_state::DeckState; use crate::login_manager::AcquireKeyState; /// Various state for views #[derive(Default)] pub struct ViewState { pub login: AcquireKeyState, + pub id_to_deck_state: HashMap<egui::Id, DeckState>, pub id_state_map: HashMap<egui::Id, AcquireKeyState>, pub id_string_map: HashMap<egui::Id, String>, }