mirror of
https://github.com/aljazceru/notedeck.git
synced 2025-12-19 09:34:19 +01:00
475 lines
16 KiB
Rust
475 lines
16 KiB
Rust
use crate::{
|
|
accounts::render_accounts_route,
|
|
actionbar::NoteAction,
|
|
app::{get_active_columns, get_active_columns_mut, get_decks_mut},
|
|
column::ColumnsAction,
|
|
deck_state::DeckState,
|
|
decks::{Deck, DecksAction, DecksCache},
|
|
notes_holder::NotesHolder,
|
|
profile::{Profile, ProfileAction, SaveProfileChanges},
|
|
profile_state::ProfileState,
|
|
relay_pool_manager::RelayPoolManager,
|
|
route::Route,
|
|
thread::Thread,
|
|
timeline::{
|
|
route::{render_timeline_route, TimelineRoute},
|
|
Timeline,
|
|
},
|
|
ui::{
|
|
self,
|
|
add_column::render_add_column_routes,
|
|
column::NavTitle,
|
|
configure_deck::ConfigureDeckView,
|
|
edit_deck::{EditDeckResponse, EditDeckView},
|
|
note::{PostAction, PostType},
|
|
profile::EditProfileView,
|
|
support::SupportView,
|
|
RelayView, View,
|
|
},
|
|
Damus,
|
|
};
|
|
|
|
use notedeck::{AccountsAction, AppContext};
|
|
|
|
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),
|
|
ProfileAction(ProfileAction),
|
|
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, decks_cache: &mut DecksCache, ctx: &mut AppContext<'_>) -> bool {
|
|
match &self {
|
|
SwitchingAction::Accounts(account_action) => match account_action {
|
|
AccountsAction::Switch(switch_action) => {
|
|
ctx.accounts.select_account(switch_action.switch_to);
|
|
// pop nav after switch
|
|
if let Some(src) = switch_action.source {
|
|
get_active_columns_mut(ctx.accounts, decks_cache)
|
|
.column_mut(src)
|
|
.router_mut()
|
|
.go_back();
|
|
}
|
|
}
|
|
AccountsAction::Remove(index) => ctx.accounts.remove_account(*index),
|
|
},
|
|
SwitchingAction::Columns(columns_action) => match *columns_action {
|
|
ColumnsAction::Remove(index) => {
|
|
get_active_columns_mut(ctx.accounts, decks_cache).delete_column(index)
|
|
}
|
|
ColumnsAction::Switch(from, to) => {
|
|
get_active_columns_mut(ctx.accounts, decks_cache).move_col(from, to);
|
|
}
|
|
},
|
|
SwitchingAction::Decks(decks_action) => match *decks_action {
|
|
DecksAction::Switch(index) => {
|
|
get_decks_mut(ctx.accounts, decks_cache).set_active(index)
|
|
}
|
|
DecksAction::Removing(index) => {
|
|
get_decks_mut(ctx.accounts, decks_cache).remove_deck(index)
|
|
}
|
|
},
|
|
}
|
|
true
|
|
}
|
|
}
|
|
|
|
impl From<PostAction> for RenderNavAction {
|
|
fn from(post_action: PostAction) -> Self {
|
|
Self::PostAction(post_action)
|
|
}
|
|
}
|
|
|
|
impl From<NoteAction> for RenderNavAction {
|
|
fn from(note_action: NoteAction) -> RenderNavAction {
|
|
Self::NoteAction(note_action)
|
|
}
|
|
}
|
|
|
|
pub type NotedeckNavResponse = NavResponse<Option<RenderNavAction>>;
|
|
|
|
pub struct RenderNavResponse {
|
|
column: usize,
|
|
response: NotedeckNavResponse,
|
|
}
|
|
|
|
impl RenderNavResponse {
|
|
#[allow(private_interfaces)]
|
|
pub fn new(column: usize, response: NotedeckNavResponse) -> Self {
|
|
RenderNavResponse { column, response }
|
|
}
|
|
|
|
#[must_use = "Make sure to save columns if result is true"]
|
|
pub fn process_render_nav_response(&self, app: &mut Damus, ctx: &mut AppContext<'_>) -> bool {
|
|
let mut switching_occured: bool = false;
|
|
let col = self.column;
|
|
|
|
if let Some(action) = self
|
|
.response
|
|
.response
|
|
.as_ref()
|
|
.or(self.response.title_response.as_ref())
|
|
{
|
|
// start returning when we're finished posting
|
|
match action {
|
|
RenderNavAction::Back => {
|
|
app.columns_mut(ctx.accounts)
|
|
.column_mut(col)
|
|
.router_mut()
|
|
.go_back();
|
|
}
|
|
|
|
RenderNavAction::RemoveColumn => {
|
|
let tl = app
|
|
.columns(ctx.accounts)
|
|
.find_timeline_for_column_index(col);
|
|
if let Some(timeline) = tl {
|
|
unsubscribe_timeline(ctx.ndb, timeline);
|
|
}
|
|
|
|
app.columns_mut(ctx.accounts).delete_column(col);
|
|
switching_occured = true;
|
|
}
|
|
|
|
RenderNavAction::PostAction(post_action) => {
|
|
let txn = Transaction::new(ctx.ndb).expect("txn");
|
|
let _ = post_action.execute(ctx.ndb, &txn, ctx.pool, &mut app.drafts);
|
|
get_active_columns_mut(ctx.accounts, &mut app.decks_cache)
|
|
.column_mut(col)
|
|
.router_mut()
|
|
.go_back();
|
|
}
|
|
|
|
RenderNavAction::NoteAction(note_action) => {
|
|
let txn = Transaction::new(ctx.ndb).expect("txn");
|
|
|
|
note_action.execute_and_process_result(
|
|
ctx.ndb,
|
|
get_active_columns_mut(ctx.accounts, &mut app.decks_cache),
|
|
col,
|
|
&mut app.threads,
|
|
&mut app.profiles,
|
|
ctx.note_cache,
|
|
ctx.pool,
|
|
&txn,
|
|
);
|
|
}
|
|
|
|
RenderNavAction::SwitchingAction(switching_action) => {
|
|
switching_occured = switching_action.process(&mut app.decks_cache, ctx);
|
|
}
|
|
RenderNavAction::ProfileAction(profile_action) => {
|
|
profile_action.process(
|
|
&mut app.view_state.pubkey_to_profile_state,
|
|
ctx.ndb,
|
|
ctx.pool,
|
|
get_active_columns_mut(ctx.accounts, &mut app.decks_cache)
|
|
.column_mut(col)
|
|
.router_mut(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(action) = self.response.action {
|
|
match action {
|
|
NavAction::Returned => {
|
|
let r = app
|
|
.columns_mut(ctx.accounts)
|
|
.column_mut(col)
|
|
.router_mut()
|
|
.pop();
|
|
let txn = Transaction::new(ctx.ndb).expect("txn");
|
|
if let Some(Route::Timeline(TimelineRoute::Thread(id))) = r {
|
|
let root_id = {
|
|
notedeck::note::root_note_id_from_selected_id(
|
|
ctx.ndb,
|
|
ctx.note_cache,
|
|
&txn,
|
|
id.bytes(),
|
|
)
|
|
};
|
|
Thread::unsubscribe_locally(
|
|
&txn,
|
|
ctx.ndb,
|
|
ctx.note_cache,
|
|
&mut app.threads,
|
|
ctx.pool,
|
|
root_id,
|
|
);
|
|
}
|
|
|
|
if let Some(Route::Timeline(TimelineRoute::Profile(pubkey))) = r {
|
|
Profile::unsubscribe_locally(
|
|
&txn,
|
|
ctx.ndb,
|
|
ctx.note_cache,
|
|
&mut app.profiles,
|
|
ctx.pool,
|
|
pubkey.bytes(),
|
|
);
|
|
}
|
|
|
|
switching_occured = true;
|
|
}
|
|
|
|
NavAction::Navigated => {
|
|
let cur_router = app.columns_mut(ctx.accounts).column_mut(col).router_mut();
|
|
cur_router.navigating = false;
|
|
if cur_router.is_replacing() {
|
|
cur_router.remove_previous_routes();
|
|
}
|
|
switching_occured = true;
|
|
}
|
|
|
|
NavAction::Dragging => {}
|
|
NavAction::Returning => {}
|
|
NavAction::Resetting => {}
|
|
NavAction::Navigating => {}
|
|
}
|
|
}
|
|
|
|
switching_occured
|
|
}
|
|
}
|
|
|
|
fn render_nav_body(
|
|
ui: &mut egui::Ui,
|
|
app: &mut Damus,
|
|
ctx: &mut AppContext<'_>,
|
|
top: &Route,
|
|
col: usize,
|
|
) -> Option<RenderNavAction> {
|
|
match top {
|
|
Route::Timeline(tlr) => render_timeline_route(
|
|
ctx.ndb,
|
|
get_active_columns_mut(ctx.accounts, &mut app.decks_cache),
|
|
&mut app.drafts,
|
|
ctx.img_cache,
|
|
ctx.unknown_ids,
|
|
ctx.note_cache,
|
|
&mut app.threads,
|
|
&mut app.profiles,
|
|
ctx.accounts,
|
|
*tlr,
|
|
col,
|
|
app.textmode,
|
|
ui,
|
|
),
|
|
Route::Accounts(amr) => {
|
|
let mut action = render_accounts_route(
|
|
ui,
|
|
ctx.ndb,
|
|
col,
|
|
ctx.img_cache,
|
|
ctx.accounts,
|
|
&mut app.decks_cache,
|
|
&mut app.view_state.login,
|
|
*amr,
|
|
);
|
|
let txn = Transaction::new(ctx.ndb).expect("txn");
|
|
action.process_action(ctx.unknown_ids, ctx.ndb, &txn);
|
|
action
|
|
.accounts_action
|
|
.map(|f| RenderNavAction::SwitchingAction(SwitchingAction::Accounts(f)))
|
|
}
|
|
Route::Relays => {
|
|
let manager = RelayPoolManager::new(ctx.pool);
|
|
RelayView::new(manager).ui(ui);
|
|
None
|
|
}
|
|
Route::ComposeNote => {
|
|
let kp = ctx.accounts.get_selected_account()?.to_full()?;
|
|
let draft = app.drafts.compose_mut();
|
|
|
|
let txn = Transaction::new(ctx.ndb).expect("txn");
|
|
let post_response = ui::PostView::new(
|
|
ctx.ndb,
|
|
draft,
|
|
PostType::New,
|
|
ctx.img_cache,
|
|
ctx.note_cache,
|
|
kp,
|
|
)
|
|
.ui(&txn, ui);
|
|
|
|
post_response.action.map(Into::into)
|
|
}
|
|
Route::AddColumn(route) => {
|
|
render_add_column_routes(ui, app, ctx, col, route);
|
|
|
|
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) = ctx.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(ctx.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(ctx.accounts, &mut app.decks_cache)
|
|
.get_first_router()
|
|
.go_back();
|
|
}
|
|
resp
|
|
}
|
|
Route::EditDeck(index) => {
|
|
let mut action = None;
|
|
let cur_deck = get_decks_mut(ctx.accounts, &mut app.decks_cache)
|
|
.decks_mut()
|
|
.get_mut(*index)
|
|
.expect("index wasn't valid");
|
|
let id = ui.id().with((
|
|
"edit-deck",
|
|
ctx.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 => {
|
|
action = Some(RenderNavAction::SwitchingAction(SwitchingAction::Decks(
|
|
DecksAction::Removing(*index),
|
|
)));
|
|
}
|
|
}
|
|
get_active_columns_mut(ctx.accounts, &mut app.decks_cache)
|
|
.get_first_router()
|
|
.go_back();
|
|
}
|
|
|
|
action
|
|
}
|
|
Route::EditProfile(pubkey) => {
|
|
let mut action = None;
|
|
if let Some(kp) = ctx.accounts.get_full(pubkey.bytes()) {
|
|
let state = app
|
|
.view_state
|
|
.pubkey_to_profile_state
|
|
.entry(*kp.pubkey)
|
|
.or_insert_with(|| {
|
|
let txn = Transaction::new(ctx.ndb).expect("txn");
|
|
if let Ok(record) = ctx.ndb.get_profile_by_pubkey(&txn, kp.pubkey.bytes()) {
|
|
ProfileState::from_profile(&record)
|
|
} else {
|
|
ProfileState::default()
|
|
}
|
|
});
|
|
if EditProfileView::new(state, ctx.img_cache).ui(ui) {
|
|
if let Some(taken_state) =
|
|
app.view_state.pubkey_to_profile_state.remove(kp.pubkey)
|
|
{
|
|
action = Some(RenderNavAction::ProfileAction(ProfileAction::SaveChanges(
|
|
SaveProfileChanges::new(kp.to_full(), taken_state),
|
|
)))
|
|
}
|
|
}
|
|
} else {
|
|
error!("Pubkey in EditProfile route did not have an nsec attached in Accounts");
|
|
}
|
|
action
|
|
}
|
|
}
|
|
}
|
|
|
|
#[must_use = "RenderNavResponse must be handled by calling .process_render_nav_response(..)"]
|
|
pub fn render_nav(
|
|
col: usize,
|
|
app: &mut Damus,
|
|
ctx: &mut AppContext<'_>,
|
|
ui: &mut egui::Ui,
|
|
) -> RenderNavResponse {
|
|
let col_id = get_active_columns(ctx.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(ctx.accounts)
|
|
.column(col)
|
|
.router()
|
|
.routes()
|
|
.clone(),
|
|
)
|
|
.navigating(
|
|
app.columns_mut(ctx.accounts)
|
|
.column_mut(col)
|
|
.router_mut()
|
|
.navigating,
|
|
)
|
|
.returning(
|
|
app.columns_mut(ctx.accounts)
|
|
.column_mut(col)
|
|
.router_mut()
|
|
.returning,
|
|
)
|
|
.id_source(egui::Id::new(col_id))
|
|
.show_mut(ui, |ui, render_type, nav| match render_type {
|
|
NavUiType::Title => NavTitle::new(
|
|
ctx.ndb,
|
|
ctx.img_cache,
|
|
get_active_columns_mut(ctx.accounts, &mut app.decks_cache),
|
|
ctx.accounts.get_selected_account().map(|a| &a.pubkey),
|
|
nav.routes(),
|
|
col,
|
|
)
|
|
.show(ui),
|
|
NavUiType::Body => render_nav_body(ui, app, ctx, nav.routes().last().expect("top"), col),
|
|
});
|
|
|
|
RenderNavResponse::new(col, nav_response)
|
|
}
|
|
|
|
fn unsubscribe_timeline(ndb: &mut Ndb, timeline: &Timeline) {
|
|
if let Some(sub_id) = timeline.subscription {
|
|
if let Err(e) = ndb.unsubscribe(sub_id) {
|
|
error!("unsubscribe error: {}", e);
|
|
} else {
|
|
info!(
|
|
"successfully unsubscribed from timeline {} with sub id {}",
|
|
timeline.id,
|
|
sub_id.id()
|
|
);
|
|
}
|
|
}
|
|
}
|