notedeck

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

commit 94598bedf5e87bc78fdc416adbc6c94eedaa981c
parent deb08a5a9dd1053dd0f081240eb11ff133bf03c2
Author: kernelkind <kernelkind@gmail.com>
Date:   Thu,  5 Dec 2024 18:42:39 -0500

introduce decks_cache

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

Diffstat:
Msrc/accounts/mod.rs | 36+++++++++++++++++++++++-------------
Msrc/app.rs | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/args.rs | 8++++----
Msrc/nav.rs | 16++++++++++------
Msrc/timeline/mod.rs | 13+++++++++----
Msrc/ui/accounts.rs | 13+++++++------
Msrc/ui/side_panel.rs | 28+++++++++++++++++++++-------
7 files changed, 178 insertions(+), 85 deletions(-)

diff --git a/src/accounts/mod.rs b/src/accounts/mod.rs @@ -8,12 +8,13 @@ use uuid::Uuid; use enostr::{ClientMessage, FilledKeypair, FullKeypair, Keypair, RelayPool}; use nostrdb::{Filter, Ndb, Note, NoteKey, Subscription, Transaction}; +use crate::app::get_active_columns_mut; +use crate::decks::DecksCache; use crate::{ - column::Columns, imgcache::ImageCache, login_manager::AcquireKeyState, muted::Muted, - route::{Route, Router}, + route::Route, storage::{KeyStorageResponse, KeyStorageType}, ui::{ account_login_view::{AccountLoginResponse, AccountLoginView}, @@ -230,13 +231,12 @@ pub fn render_accounts_route( ui: &mut egui::Ui, ndb: &Ndb, col: usize, - columns: &mut Columns, img_cache: &mut ImageCache, accounts: &mut Accounts, + decks: &mut DecksCache, login_state: &mut AcquireKeyState, route: AccountsRoute, ) -> SingleUnkIdAction { - let router = columns.column_mut(col).router_mut(); let resp = match route { AccountsRoute::Accounts => AccountsView::new(ndb, accounts, img_cache) .ui(ui) @@ -252,12 +252,15 @@ pub fn render_accounts_route( if let Some(resp) = resp { match resp { AccountsRouteResponse::Accounts(response) => { - process_accounts_view_response(accounts, response, router); + process_accounts_view_response(accounts, decks, col, response); SingleUnkIdAction::no_action() } AccountsRouteResponse::AddAccount(response) => { - let action = process_login_view_response(accounts, response); + let action = process_login_view_response(accounts, decks, response); *login_state = Default::default(); + let router = get_active_columns_mut(accounts, decks) + .column_mut(col) + .router_mut(); router.go_back(); action } @@ -268,16 +271,20 @@ pub fn render_accounts_route( } pub fn process_accounts_view_response( - manager: &mut Accounts, + accounts: &mut Accounts, + decks: &mut DecksCache, + col: usize, response: AccountsViewResponse, - router: &mut Router<Route>, ) { + let router = get_active_columns_mut(accounts, decks) + .column_mut(col) + .router_mut(); match response { AccountsViewResponse::RemoveAccount(index) => { - manager.remove_account(index); + accounts.remove_account(index); } AccountsViewResponse::SelectAccount(index) => { - manager.select_account(index); + accounts.select_account(index); } AccountsViewResponse::RouteToLogin => { router.route_to(Route::add_account()); @@ -619,15 +626,18 @@ fn get_selected_index(accounts: &[UserAccount], keystore: &KeyStorageType) -> Op pub fn process_login_view_response( manager: &mut Accounts, + decks: &mut DecksCache, response: AccountLoginResponse, ) -> SingleUnkIdAction { - let login_action = match response { + let (pubkey, login_action) = match response { AccountLoginResponse::CreateNew => { - manager.add_account(FullKeypair::generate().to_keypair()) + let kp = FullKeypair::generate().to_keypair(); + (kp.pubkey, manager.add_account(kp)) } - AccountLoginResponse::LoginWith(keypair) => manager.add_account(keypair), + 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 } diff --git a/src/app.rs b/src/app.rs @@ -3,7 +3,8 @@ use crate::{ app_creation::setup_cc, app_size_handler::AppSizeHandler, args::Args, - column::{Column, Columns}, + column::Columns, + decks::{Decks, DecksCache}, draft::Drafts, filter::FilterState, frame_history::FrameHistory, @@ -12,13 +13,12 @@ use crate::{ notecache::NoteCache, notes_holder::NotesHolderStorage, profile::Profile, - route::Route, storage::{self, DataPath, DataPathType, Directory, FileKeyStorage, KeyStorageType}, subscriptions::{SubKind, Subscriptions}, support::Support, thread::Thread, - timeline::{self, Timeline, TimelineKind}, - ui::{self, add_column::AddColumnRoute, DesktopSidePanel}, + timeline::{self, Timeline}, + ui::{self, DesktopSidePanel}, unknowns::UnknownIds, view_state::ViewState, Result, @@ -30,7 +30,7 @@ use uuid::Uuid; use egui::{Context, Frame, Style}; use egui_extras::{Size, StripBuilder}; -use nostrdb::{Config, Filter, Ndb, Transaction}; +use nostrdb::{Config, Ndb, Transaction}; use std::collections::HashMap; use std::path::Path; @@ -49,7 +49,7 @@ pub struct Damus { pub note_cache: NoteCache, pub pool: RelayPool, - pub columns: Columns, + pub decks_cache: DecksCache, pub ndb: Ndb, pub view_state: ViewState, pub unknown_ids: UnknownIds, @@ -98,7 +98,8 @@ fn handle_key_events(input: &egui::InputState, _pixels_per_point: f32, columns: fn try_process_event(damus: &mut Damus, ctx: &egui::Context) -> Result<()> { let ppp = ctx.pixels_per_point(); - ctx.input(|i| handle_key_events(i, ppp, &mut damus.columns)); + let current_columns = get_active_columns_mut(&damus.accounts, &mut damus.decks_cache); + ctx.input(|i| handle_key_events(i, ppp, current_columns)); let ctx2 = ctx.clone(); let wakeup = move || { @@ -124,7 +125,7 @@ fn try_process_event(damus: &mut Damus, ctx: &egui::Context) -> Result<()> { timeline::send_initial_timeline_filters( &damus.ndb, damus.since_optimize, - &mut damus.columns, + get_active_columns_mut(&damus.accounts, &mut damus.decks_cache), &mut damus.subscriptions, &mut damus.pool, &ev.relay, @@ -138,10 +139,11 @@ fn try_process_event(damus: &mut Damus, ctx: &egui::Context) -> Result<()> { } } - let n_timelines = damus.columns.timelines().len(); + let current_columns = get_active_columns_mut(&damus.accounts, &mut damus.decks_cache); + let n_timelines = current_columns.timelines().len(); for timeline_ind in 0..n_timelines { let is_ready = { - let timeline = &mut damus.columns.timelines[timeline_ind]; + let timeline = &mut current_columns.timelines[timeline_ind]; timeline::is_timeline_ready( &damus.ndb, &mut damus.pool, @@ -156,7 +158,7 @@ fn try_process_event(damus: &mut Damus, ctx: &egui::Context) -> Result<()> { if let Err(err) = Timeline::poll_notes_into_view( timeline_ind, - damus.columns.timelines_mut(), + current_columns.timelines_mut(), &damus.ndb, &txn, &mut damus.unknown_ids, @@ -209,7 +211,7 @@ fn update_damus(damus: &mut Damus, ctx: &egui::Context) { if let Err(err) = timeline::setup_initial_nostrdb_subs( &damus.ndb, &mut damus.note_cache, - &mut damus.columns, + &mut damus.decks_cache, &damus.accounts.mutefun(), ) { warn!("update_damus init: {err}"); @@ -257,7 +259,7 @@ fn handle_eose(damus: &mut Damus, subid: &str, relay_url: &str) -> Result<()> { UnknownIds::update( &txn, &mut damus.unknown_ids, - &damus.columns, + get_active_columns(&damus.accounts, &damus.decks_cache), &damus.ndb, &mut damus.note_cache, ); @@ -274,7 +276,10 @@ fn handle_eose(damus: &mut Damus, subid: &str, relay_url: &str) -> Result<()> { } SubKind::FetchingContactList(timeline_uid) => { - let timeline = if let Some(tl) = damus.columns.find_timeline_mut(timeline_uid) { + let timeline = if let Some(tl) = + get_active_columns_mut(&damus.accounts, &mut damus.decks_cache) + .find_timeline_mut(timeline_uid) + { tl } else { error!( @@ -445,7 +450,7 @@ impl Damus { .as_ref() .map(|a| a.pubkey.bytes()); - let mut columns = if parsed_args.columns.is_empty() { + let columns = if parsed_args.columns.is_empty() { if let Some(serializable_columns) = storage::load_columns(&path) { info!("Using columns from disk"); serializable_columns.into_columns(&ndb, account) @@ -468,13 +473,35 @@ impl Damus { columns }; + let mut decks_cache = { + let mut decks_cache = DecksCache::default(); + + let mut decks = Decks::default(); + *decks.active_mut().columns_mut() = columns; + + if let Some(acc) = account { + decks_cache.add_decks(Pubkey::new(*acc), decks); + } + + decks_cache + }; + let debug = parsed_args.debug; - if columns.columns().is_empty() { + if get_active_columns(&accounts, &decks_cache) + .columns() + .is_empty() + { if accounts.get_accounts().is_empty() { - set_demo(&path, &ndb, &mut accounts, &mut columns, &mut unknown_ids); + set_demo( + &path, + &ndb, + &mut accounts, + &mut decks_cache, + &mut unknown_ids, + ); } else { - columns.new_column_picker(); + get_active_columns_mut(&accounts, &mut decks_cache).new_column_picker(); } } @@ -493,7 +520,6 @@ impl Damus { state: DamusState::Initializing, img_cache: ImageCache::new(imgcache_dir), note_cache: NoteCache::default(), - columns, textmode: parsed_args.textmode, ndb, accounts, @@ -502,6 +528,7 @@ impl Damus { path, app_rect_handler, support, + decks_cache, } } @@ -534,11 +561,11 @@ impl Damus { } pub fn columns_mut(&mut self) -> &mut Columns { - &mut self.columns + get_active_columns_mut(&self.accounts, &mut self.decks_cache) } pub fn columns(&self) -> &Columns { - &self.columns + get_active_columns(&self.accounts, &self.decks_cache) } pub fn gen_subid(&self, kind: &SubKind) -> String { @@ -550,12 +577,7 @@ impl Damus { } pub fn mock<P: AsRef<Path>>(data_path: P) -> Self { - let mut columns = Columns::new(); - let filter = Filter::from_json(include_str!("../queries/global.json")).unwrap(); - - let timeline = Timeline::new(TimelineKind::Universe, FilterState::ready(vec![filter])); - - columns.add_new_timeline_column(timeline); + let decks_cache = DecksCache::default(); let path = DataPath::new(&data_path); let imgcache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir()); @@ -579,7 +601,6 @@ impl Damus { pool: RelayPool::new(), img_cache: ImageCache::new(imgcache_dir), note_cache: NoteCache::default(), - columns, textmode: false, ndb: Ndb::new( path.path(DataPathType::Db) @@ -591,10 +612,10 @@ impl Damus { accounts: Accounts::new(KeyStorageType::None, vec![]), frame_history: FrameHistory::default(), view_state: ViewState::default(), - path, app_rect_handler, support, + decks_cache, } } @@ -639,7 +660,7 @@ fn render_damus_mobile(ctx: &egui::Context, app: &mut Damus) { //let routes = app.timelines[0].routes.clone(); main_panel(&ctx.style(), ui::is_narrow(ctx)).show(ctx, |ui| { - if !app.columns.columns().is_empty() + if !app.columns().columns().is_empty() && nav::render_nav(0, app, ui).process_render_nav_response(app) { storage::save_columns(&app.path, app.columns().as_serializable_columns()); @@ -666,7 +687,9 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) { puffin::profile_function!(); let screen_size = ctx.screen_rect().width(); - let calc_panel_width = (screen_size / app.columns.num_columns() as f32) - 30.0; + let calc_panel_width = (screen_size + / get_active_columns(&app.accounts, &app.decks_cache).num_columns() as f32) + - 30.0; let min_width = 320.0; let need_scroll = calc_panel_width < min_width; let panel_sizes = if need_scroll { @@ -690,7 +713,10 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) { fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) { StripBuilder::new(ui) .size(Size::exact(ui::side_panel::SIDE_PANEL_WIDTH)) - .sizes(sizes, app.columns.num_columns()) + .sizes( + sizes, + get_active_columns(&app.accounts, &app.decks_cache).num_columns(), + ) .clip(true) .horizontal(|mut strip| { strip.cell(|ui| { @@ -699,12 +725,14 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) { &app.ndb, &mut app.img_cache, app.accounts.get_selected_account(), + &app.decks_cache, ) .show(ui); - if side_panel.response.clicked() { + if side_panel.response.clicked() || side_panel.response.secondary_clicked() { DesktopSidePanel::perform_action( - &mut app.columns, + &mut app.decks_cache, + &app.accounts, &mut app.support, side_panel.action, ); @@ -718,8 +746,9 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) { ); }); - let mut responses = Vec::with_capacity(app.columns.num_columns()); - for col_index in 0..app.columns.num_columns() { + let num_cols = app.columns().num_columns(); + let mut responses = Vec::with_capacity(num_cols); + for col_index in 0..num_cols { strip.cell(|ui| { let rect = ui.available_rect_before_wrap(); responses.push(nav::render_nav(col_index, app, ui)); @@ -766,16 +795,46 @@ impl eframe::App for Damus { } } -fn set_demo( +pub fn get_active_columns<'a>(accounts: &Accounts, decks_cache: &'a DecksCache) -> &'a Columns { + get_decks(accounts, decks_cache).active().columns() +} + +pub fn get_decks<'a>(accounts: &Accounts, decks_cache: &'a DecksCache) -> &'a Decks { + let key = if let Some(acc) = accounts.get_selected_account() { + &acc.pubkey + } else { + decks_cache.get_fallback_pubkey() + }; + decks_cache.decks(key) +} + +pub fn get_active_columns_mut<'a>( + accounts: &Accounts, + decks_cache: &'a mut DecksCache, +) -> &'a mut Columns { + get_decks_mut(accounts, decks_cache) + .active_mut() + .columns_mut() +} + +pub fn get_decks_mut<'a>(accounts: &Accounts, decks_cache: &'a mut DecksCache) -> &'a mut Decks { + if let Some(acc) = accounts.get_selected_account() { + decks_cache.decks_mut(&acc.pubkey) + } else { + decks_cache.fallback_mut() + } +} + +pub fn set_demo( data_path: &DataPath, ndb: &Ndb, accounts: &mut Accounts, - columns: &mut Columns, + decks_cache: &mut DecksCache, unk_ids: &mut UnknownIds, ) { - let demo_pubkey = - Pubkey::from_hex("aa733081e4f0f79dd43023d8983265593f2b41a988671cfcef3f489b91ad93fe") - .unwrap(); + let demo_pubkey = *decks_cache.get_fallback_pubkey(); + let columns = get_active_columns_mut(accounts, decks_cache); + { let txn = Transaction::new(ndb).expect("txn"); accounts @@ -784,13 +843,13 @@ fn set_demo( accounts.select_account(0); } - columns.add_column(Column::new(vec![ - Route::AddColumn(AddColumnRoute::Base), - Route::Accounts(AccountsRoute::Accounts), + columns.add_column(crate::column::Column::new(vec![ + crate::route::Route::AddColumn(ui::add_column::AddColumnRoute::Base), + crate::route::Route::Accounts(AccountsRoute::Accounts), ])); if let Some(timeline) = - TimelineKind::contact_list(timeline::PubkeySource::Explicit(demo_pubkey)) + timeline::TimelineKind::contact_list(timeline::PubkeySource::Explicit(demo_pubkey)) .into_timeline(ndb, Some(demo_pubkey.bytes())) { columns.add_new_timeline_column(timeline); diff --git a/src/args.rs b/src/args.rs @@ -306,15 +306,15 @@ mod tests { let ctx = egui::Context::default(); let app = Damus::new(&ctx, &tmpdir, args); - assert_eq!(app.columns.columns().len(), 2); + assert_eq!(app.columns().columns().len(), 2); - let tl1 = app.columns.column(0).router().top().timeline_id(); - let tl2 = app.columns.column(1).router().top().timeline_id(); + let tl1 = app.columns().column(0).router().top().timeline_id(); + let tl2 = app.columns().column(1).router().top().timeline_id(); assert_eq!(tl1.is_some(), true); assert_eq!(tl2.is_some(), true); - let timelines = app.columns.timelines(); + let timelines = app.columns().timelines(); assert!(timelines[0].kind.is_notifications()); assert!(timelines[1].kind.is_contacts()); diff --git a/src/nav.rs b/src/nav.rs @@ -1,6 +1,7 @@ use crate::{ accounts::render_accounts_route, actionbar::NoteAction, + app::{get_active_columns, get_active_columns_mut}, notes_holder::NotesHolder, profile::Profile, relay_pool_manager::RelayPoolManager, @@ -87,7 +88,10 @@ impl RenderNavResponse { RenderNavAction::PostAction(post_action) => { let txn = Transaction::new(&app.ndb).expect("txn"); let _ = post_action.execute(&app.ndb, &txn, &mut app.pool, &mut app.drafts); - app.columns_mut().column_mut(col).router_mut().go_back(); + get_active_columns_mut(&app.accounts, &mut app.decks_cache) + .column_mut(col) + .router_mut() + .go_back(); } RenderNavAction::NoteAction(note_action) => { @@ -95,7 +99,7 @@ impl RenderNavResponse { note_action.execute_and_process_result( &app.ndb, - &mut app.columns, + get_active_columns_mut(&app.accounts, &mut app.decks_cache), col, &mut app.threads, &mut app.profiles, @@ -176,7 +180,7 @@ fn render_nav_body( match top { Route::Timeline(tlr) => render_timeline_route( &app.ndb, - &mut app.columns, + get_active_columns_mut(&app.accounts, &mut app.decks_cache), &mut app.drafts, &mut app.img_cache, &mut app.unknown_ids, @@ -194,9 +198,9 @@ fn render_nav_body( ui, &app.ndb, col, - &mut app.columns, &mut app.img_cache, &mut app.accounts, + &mut app.decks_cache, &mut app.view_state.login, *amr, ); @@ -241,7 +245,7 @@ fn render_nav_body( #[must_use = "RenderNavResponse must be handled by calling .process_render_nav_response(..)"] pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) -> RenderNavResponse { - let col_id = app.columns.get_column_id_at_index(col); + let col_id = get_active_columns(&app.accounts, &app.decks_cache).get_column_id_at_index(col); // TODO(jb55): clean up this router_mut mess by using Router<R> in egui-nav directly let nav_response = Nav::new(&app.columns().column(col).router().routes().clone()) @@ -252,7 +256,7 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) -> RenderNavRe NavUiType::Title => NavTitle::new( &app.ndb, &mut app.img_cache, - &app.columns, + get_active_columns_mut(&app.accounts, &mut app.decks_cache), app.accounts.get_selected_account().map(|a| &a.pubkey), nav.routes(), ) diff --git a/src/timeline/mod.rs b/src/timeline/mod.rs @@ -1,5 +1,6 @@ use crate::{ column::Columns, + decks::DecksCache, error::{Error, FilterError}, filter::{self, FilterState, FilterStates}, muted::MuteFun, @@ -601,12 +602,16 @@ pub fn copy_notes_into_timeline( pub fn setup_initial_nostrdb_subs( ndb: &Ndb, note_cache: &mut NoteCache, - columns: &mut Columns, + decks_cache: &mut DecksCache, is_muted: &MuteFun, ) -> Result<()> { - for timeline in columns.timelines_mut() { - if let Err(err) = setup_timeline_nostrdb_sub(ndb, note_cache, timeline, is_muted) { - error!("setup_initial_nostrdb_subs: {err}"); + for decks in decks_cache.get_all_decks_mut() { + for deck in decks.decks_mut() { + for timeline in deck.columns_mut().timelines_mut() { + if let Err(err) = setup_timeline_nostrdb_sub(ndb, note_cache, timeline, is_muted) { + error!("setup_initial_nostrdb_subs: {err}"); + } + } } } diff --git a/src/ui/accounts.rs b/src/ui/accounts.rs @@ -2,7 +2,6 @@ use crate::colors::PINK; use crate::imgcache::ImageCache; use crate::{ accounts::Accounts, - route::{Route, Router}, ui::{Preview, PreviewConfig, View}, Damus, }; @@ -205,15 +204,12 @@ mod preview { pub struct AccountsPreview { app: Damus, - router: Router<Route>, } impl AccountsPreview { fn new() -> Self { let app = test_data::test_app(); - let router = Router::new(vec![Route::accounts()]); - - AccountsPreview { app, router } + AccountsPreview { app } } } @@ -226,7 +222,12 @@ mod preview { .ui(ui) .inner { - process_accounts_view_response(self.app.accounts_mut(), response, &mut self.router); + process_accounts_view_response( + &mut self.app.accounts, + &mut self.app.decks_cache, + 0, + response, + ); } } } diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs @@ -4,9 +4,11 @@ use egui::{ use tracing::info; use crate::{ - accounts::AccountsRoute, + accounts::{Accounts, AccountsRoute}, + app::get_active_columns_mut, app_style, colors, - column::{Column, Columns}, + column::Column, + decks::DecksCache, imgcache::ImageCache, route::Route, support::Support, @@ -27,6 +29,7 @@ pub struct DesktopSidePanel<'a> { ndb: &'a nostrdb::Ndb, img_cache: &'a mut ImageCache, selected_account: Option<&'a UserAccount>, + decks_cache: &'a DecksCache, } impl View for DesktopSidePanel<'_> { @@ -63,11 +66,13 @@ impl<'a> DesktopSidePanel<'a> { ndb: &'a nostrdb::Ndb, img_cache: &'a mut ImageCache, selected_account: Option<&'a UserAccount>, + decks_cache: &'a DecksCache, ) -> Self { Self { ndb, img_cache, selected_account, + decks_cache, } } @@ -200,8 +205,13 @@ impl<'a> DesktopSidePanel<'a> { helper.take_animation_response() } - pub fn perform_action(columns: &mut Columns, support: &mut Support, action: SidePanelAction) { - let router = columns.get_first_router(); + pub fn perform_action( + decks_cache: &mut DecksCache, + accounts: &Accounts, + support: &mut Support, + action: SidePanelAction, + ) { + let router = get_active_columns_mut(accounts, decks_cache).get_first_router(); match action { SidePanelAction::Panel => {} // TODO SidePanelAction::Account => { @@ -232,7 +242,7 @@ impl<'a> DesktopSidePanel<'a> { { router.go_back(); } else { - columns.new_column_picker(); + get_active_columns_mut(accounts, decks_cache).new_column_picker(); } } SidePanelAction::ComposeNote => { @@ -470,6 +480,7 @@ mod preview { use egui_extras::{Size, StripBuilder}; use crate::{ + app::get_active_columns_mut, test_data, ui::{Preview, PreviewConfig}, }; @@ -483,7 +494,8 @@ mod preview { impl DesktopSidePanelPreview { fn new() -> Self { let mut app = test_data::test_app(); - app.columns.add_column(Column::new(vec![Route::accounts()])); + get_active_columns_mut(&app.accounts, &mut app.decks_cache) + .add_column(Column::new(vec![Route::accounts()])); DesktopSidePanelPreview { app } } } @@ -500,11 +512,13 @@ mod preview { &self.app.ndb, &mut self.app.img_cache, self.app.accounts.get_selected_account(), + &self.app.decks_cache, ); let response = panel.show(ui); DesktopSidePanel::perform_action( - &mut self.app.columns, + &mut self.app.decks_cache, + &self.app.accounts, &mut self.app.support, response.action, );