notedeck

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

commit 3b9d3338499bace951a2ba4c487ff892992419e2
parent 10d28f72c066a9b4ad46248775ba515f2d2dced2
Author: kernelkind <kernelkind@gmail.com>
Date:   Sat, 22 Nov 2025 22:05:33 -0700

feat(media-loading): wire new media loading cache

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

Diffstat:
Mcrates/notedeck/src/lib.rs | 3++-
Mcrates/notedeck/src/media/action.rs | 52+++++++++++++++++++++++++---------------------------
Mcrates/notedeck/src/note/mod.rs | 4++--
Mcrates/notedeck_chrome/src/chrome.rs | 5++++-
Mcrates/notedeck_clndash/src/ui.rs | 13++++++++-----
Mcrates/notedeck_columns/src/accounts/mod.rs | 7+++----
Mcrates/notedeck_columns/src/actionbar.rs | 9++++++---
Mcrates/notedeck_columns/src/app.rs | 20+++++++++++++-------
Mcrates/notedeck_columns/src/nav.rs | 63++++++++++++++++++++++++++++++---------------------------------
Mcrates/notedeck_columns/src/timeline/route.rs | 44+++++++++-----------------------------------
Mcrates/notedeck_columns/src/ui/accounts.rs | 16++++++++++++++--
Mcrates/notedeck_columns/src/ui/add_column.rs | 10++++++++--
Mcrates/notedeck_columns/src/ui/column/header.rs | 21++++++++++++---------
Mcrates/notedeck_columns/src/ui/mentions_picker.rs | 10+++++++---
Mcrates/notedeck_columns/src/ui/note/custom_zap.rs | 16++++++++++++----
Mcrates/notedeck_columns/src/ui/note/post.rs | 67+++++++++++++++++++++++++++++++------------------------------------
Mcrates/notedeck_columns/src/ui/note/quote_repost.rs | 6+-----
Mcrates/notedeck_columns/src/ui/note/reply.rs | 8++------
Mcrates/notedeck_columns/src/ui/onboarding.rs | 10+++-------
Mcrates/notedeck_columns/src/ui/profile/contacts_list.rs | 7++++++-
Mcrates/notedeck_columns/src/ui/profile/edit.rs | 9+++++++--
Mcrates/notedeck_columns/src/ui/profile/mod.rs | 18+++++++++---------
Mcrates/notedeck_columns/src/ui/search/mod.rs | 12++++++------
Mcrates/notedeck_columns/src/ui/settings.rs | 18+++++-------------
Mcrates/notedeck_columns/src/ui/side_panel.rs | 7+++++--
Mcrates/notedeck_columns/src/ui/thread.rs | 20+++-----------------
Mcrates/notedeck_columns/src/ui/timeline.rs | 47+++++++++++------------------------------------
Mcrates/notedeck_dave/src/lib.rs | 10++--------
Mcrates/notedeck_dave/src/ui/dave.rs | 55+++++++++++++++++++++++++++++++------------------------
Mcrates/notedeck_ui/src/media/viewer.rs | 38++++++++++++++++++++++++++++----------
Mcrates/notedeck_ui/src/mention.rs | 9+++++++--
Mcrates/notedeck_ui/src/nip51_set.rs | 21++++++++-------------
Mcrates/notedeck_ui/src/note/contents.rs | 28++++++++--------------------
Mcrates/notedeck_ui/src/note/media.rs | 140++++++++++++++++++++++++++++---------------------------------------------------
Mcrates/notedeck_ui/src/note/mod.rs | 60+++++++++++++++++++++++++++---------------------------------
Mcrates/notedeck_ui/src/note/reply_description.rs | 15++++-----------
Mcrates/notedeck_ui/src/profile/picture.rs | 48+++++++++++++++++++++++-------------------------
Mcrates/notedeck_ui/src/profile/preview.rs | 21+++++++++++++++++----
38 files changed, 449 insertions(+), 518 deletions(-)

diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs @@ -60,7 +60,8 @@ pub use imgcache::{ }; pub use jobs::{ deliver_completed_media_job, run_media_job_pre_action, BlurhashParams, Job, JobCache, JobError, - JobIdOld, JobParams, JobParamsOwned, JobPool, JobState, JobsCacheOld, MediaJobs, + JobIdOld, JobParams, JobParamsOwned, JobPool, JobState, JobsCacheOld, MediaJobSender, + MediaJobs, }; pub use media::{ compute_blurhash, update_imeta_blurhashes, ImageMetadata, ImageType, MediaAction, diff --git a/crates/notedeck/src/media/action.rs b/crates/notedeck/src/media/action.rs @@ -1,5 +1,4 @@ -use crate::{Images, MediaCacheType, TexturedImage}; -use poll_promise::Promise; +use crate::{jobs::MediaJobSender, ImageType, Images, MediaCacheType}; /// Tracks where media was on the screen so that /// we can do fun animations when opening the @@ -41,8 +40,8 @@ pub enum MediaAction { FetchImage { url: String, cache_type: MediaCacheType, - no_pfp_promise: Promise<Option<Result<TexturedImage, crate::Error>>>, }, + // A media is "done loading" when it has the actual media and it has reached the peak of a shimmer, to transition smoothly DoneLoading { url: String, cache_type: MediaCacheType, @@ -60,15 +59,10 @@ impl std::fmt::Debug for MediaAction { .field("clicked_index", clicked_index) .field("media", medias) .finish(), - Self::FetchImage { - url, - cache_type, - no_pfp_promise, - } => f + Self::FetchImage { url, cache_type } => f .debug_struct("FetchNoPfpImage") .field("url", url) .field("cache_type", cache_type) - .field("no_pfp_promise ready", &no_pfp_promise.ready().is_some()) .finish(), Self::DoneLoading { url, cache_type } => f .debug_struct("DoneLoading") @@ -89,7 +83,12 @@ impl MediaAction { /// Default processing logic for Media Actions. We don't handle ViewMedias here since /// this may be app specific ? - pub fn process_default_media_actions(self, images: &mut Images) { + pub fn process_default_media_actions( + self, + images: &mut Images, + jobs: &MediaJobSender, + ctx: &egui::Context, + ) { match self { MediaAction::ViewMedias(_urls) => { // NOTE(jb55): don't assume we want to show a fullscreen @@ -104,23 +103,22 @@ impl MediaAction { //mview_state.set_urls(urls); } - MediaAction::FetchImage { - url, - cache_type, - no_pfp_promise: promise, - } => { - images - .get_cache_mut(cache_type) - .textures_cache - .insert_pending(&url, promise); - } - MediaAction::DoneLoading { url, cache_type } => { - let cache = match cache_type { - MediaCacheType::Image => &mut images.static_imgs, - MediaCacheType::Gif => &mut images.gifs, - }; - - cache.textures_cache.move_to_loaded(&url); + MediaAction::FetchImage { url, cache_type } => match cache_type { + MediaCacheType::Image => { + images + .textures + .static_image + .request(jobs, ctx, &url, ImageType::Content(None)) + } + MediaCacheType::Gif => { + images + .textures + .animated + .request(jobs, ctx, &url, ImageType::Content(None)) + } + }, + MediaAction::DoneLoading { url, cache_type: _ } => { + images.textures.blurred.finished_transitioning(&url); } } } diff --git a/crates/notedeck/src/note/mod.rs b/crates/notedeck/src/note/mod.rs @@ -4,9 +4,9 @@ mod context; pub use action::{NoteAction, ReactAction, ScrollInfo, ZapAction, ZapTargetAmount}; pub use context::{BroadcastContext, ContextSelection, NoteContextSelection}; +use crate::jobs::MediaJobSender; use crate::Accounts; use crate::GlobalWallet; -use crate::JobPool; use crate::Localization; use crate::UnknownIds; use crate::{notecache::NoteCache, zaps::Zaps, Images}; @@ -27,7 +27,7 @@ pub struct NoteContext<'d> { pub note_cache: &'d mut NoteCache, pub zaps: &'d mut Zaps, pub pool: &'d mut RelayPool, - pub job_pool: &'d mut JobPool, + pub jobs: &'d MediaJobSender, pub unknown_ids: &'d mut UnknownIds, pub clipboard: &'d mut egui_winit::clipboard::Clipboard, } diff --git a/crates/notedeck_chrome/src/chrome.rs b/crates/notedeck_chrome/src/chrome.rs @@ -484,6 +484,7 @@ fn chrome_handle_app_action( ctx.zaps, ctx.img_cache, &mut columns.view_state, + ctx.media_jobs.sender(), ui, ); @@ -540,6 +541,7 @@ fn columns_route_to_profile( ctx.zaps, ctx.img_cache, &mut columns.view_state, + ctx.media_jobs.sender(), ui, ); @@ -589,7 +591,8 @@ fn topdown_sidebar( get_profile_url_owned(None) }; - let pfp_resp = ui.add(&mut ProfilePic::new(ctx.img_cache, profile_url).size(64.0)); + let pfp_resp = ui + .add(&mut ProfilePic::new(ctx.img_cache, ctx.media_jobs.sender(), profile_url).size(64.0)); ui.horizontal_wrapped(|ui| { ui.add(egui::Label::new( diff --git a/crates/notedeck_clndash/src/ui.rs b/crates/notedeck_clndash/src/ui.rs @@ -49,23 +49,26 @@ pub fn note_hover_ui( note_cache: ctx.note_cache, zaps: ctx.zaps, pool: ctx.pool, - job_pool: ctx.job_pool, + jobs: ctx.media_jobs.sender(), unknown_ids: ctx.unknown_ids, clipboard: ctx.clipboard, i18n: ctx.i18n, global_wallet: ctx.global_wallet, }; - let mut jobs = notedeck::JobsCacheOld::default(); let options = notedeck_ui::NoteOptions::default(); - notedeck_ui::ProfilePic::from_profile_or_default(note_context.img_cache, author.as_ref()) - .ui(ui); + notedeck_ui::ProfilePic::from_profile_or_default( + note_context.img_cache, + note_context.jobs, + author.as_ref(), + ) + .ui(ui); let nostr_name = notedeck::name::get_display_name(author.as_ref()); ui.label(format!("{} zapped you", nostr_name.name())); - return notedeck_ui::NoteView::new(&mut note_context, &note, options, &mut jobs) + return notedeck_ui::NoteView::new(&mut note_context, &note, options) .preview_style() .hide_media(true) .show(ui) diff --git a/crates/notedeck_columns/src/accounts/mod.rs b/crates/notedeck_columns/src/accounts/mod.rs @@ -1,7 +1,7 @@ use enostr::{FullKeypair, Pubkey}; use nostrdb::{Ndb, Transaction}; -use notedeck::{Accounts, AppContext, JobsCacheOld, Localization, SingleUnkIdAction, UnknownIds}; +use notedeck::{Accounts, AppContext, Localization, SingleUnkIdAction, UnknownIds}; use notedeck_ui::nip51_set::Nip51SetUiCache; pub use crate::accounts::route::AccountsResponse; @@ -77,7 +77,6 @@ pub struct AddAccountAction { pub fn render_accounts_route( ui: &mut egui::Ui, app_ctx: &mut AppContext, - jobs: &mut JobsCacheOld, login_state: &mut AcquireKeyState, onboarding: &mut Onboarding, follow_packs_ui: &mut Nip51SetUiCache, @@ -87,6 +86,7 @@ pub fn render_accounts_route( AccountsRoute::Accounts => AccountsView::new( app_ctx.ndb, app_ctx.accounts, + app_ctx.media_jobs.sender(), app_ctx.img_cache, app_ctx.i18n, ) @@ -107,8 +107,7 @@ pub fn render_accounts_route( app_ctx.ndb, app_ctx.img_cache, app_ctx.i18n, - app_ctx.job_pool, - jobs, + app_ctx.media_jobs.sender(), ) .ui(ui) .map_output(|r| match r { diff --git a/crates/notedeck_columns/src/actionbar.rs b/crates/notedeck_columns/src/actionbar.rs @@ -17,8 +17,8 @@ use nostrdb::{IngestMetadata, Ndb, NoteBuilder, NoteKey, Transaction}; use notedeck::{ get_wallet_for, note::{reaction_sent_id, ReactAction, ZapTargetAmount}, - Accounts, GlobalWallet, Images, NoteAction, NoteCache, NoteZapTargetOwned, UnknownIds, - ZapAction, ZapTarget, ZappingError, Zaps, + Accounts, GlobalWallet, Images, MediaJobSender, NoteAction, NoteCache, NoteZapTargetOwned, + UnknownIds, ZapAction, ZapTarget, ZappingError, Zaps, }; use notedeck_ui::media::MediaViewerFlags; use tracing::error; @@ -59,6 +59,7 @@ fn execute_note_action( images: &mut Images, view_state: &mut ViewState, router_type: RouterType, + jobs: &MediaJobSender, ui: &mut egui::Ui, col: usize, ) -> NoteActionResponse { @@ -207,7 +208,7 @@ fn execute_note_action( .set(MediaViewerFlags::Open, true); }); - media_action.process_default_media_actions(images) + media_action.process_default_media_actions(images, jobs, ui.ctx()) } } @@ -235,6 +236,7 @@ pub fn execute_and_process_note_action( zaps: &mut Zaps, images: &mut Images, view_state: &mut ViewState, + jobs: &MediaJobSender, ui: &mut egui::Ui, ) -> Option<RouterAction> { let router_type = { @@ -261,6 +263,7 @@ pub fn execute_and_process_note_action( images, view_state, router_type, + jobs, ui, col, ); diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs @@ -21,7 +21,8 @@ use enostr::{ClientMessage, PoolRelay, Pubkey, RelayEvent, RelayMessage, RelayPo use nostrdb::Transaction; use notedeck::{ tr, ui::is_narrow, Accounts, AppAction, AppContext, AppResponse, DataPath, DataPathType, - FilterState, Images, JobsCacheOld, Localization, NotedeckOptions, SettingsHandler, UnknownIds, + FilterState, Images, Localization, MediaJobSender, NotedeckOptions, SettingsHandler, + UnknownIds, }; use notedeck_ui::{ media::{MediaViewer, MediaViewerFlags, MediaViewerState}, @@ -48,7 +49,6 @@ pub struct Damus { pub timeline_cache: TimelineCache, pub subscriptions: Subscriptions, pub support: Support, - pub jobs: JobsCacheOld, pub threads: Threads, //frame_history: crate::frame_history::FrameHistory, @@ -442,7 +442,12 @@ fn render_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>, ui: &mut egui:: render_damus_desktop(damus, app_ctx, ui) }; - fullscreen_media_viewer_ui(ui, &mut damus.view_state.media_viewer, app_ctx.img_cache); + fullscreen_media_viewer_ui( + ui, + &mut damus.view_state.media_viewer, + app_ctx.img_cache, + app_ctx.media_jobs.sender(), + ); // We use this for keeping timestamps and things up to date //ui.ctx().request_repaint_after(Duration::from_secs(5)); @@ -457,6 +462,7 @@ fn fullscreen_media_viewer_ui( ui: &mut egui::Ui, state: &mut MediaViewerState, img_cache: &mut Images, + jobs: &MediaJobSender, ) { if !state.should_show(ui) { if state.scene_rect.is_some() { @@ -468,7 +474,9 @@ fn fullscreen_media_viewer_ui( return; } - let resp = MediaViewer::new(state).fullscreen(true).ui(img_cache, ui); + let resp = MediaViewer::new(state) + .fullscreen(true) + .ui(img_cache, jobs, ui); if resp.clicked() || ui.input(|i| i.key_pressed(egui::Key::Escape)) { fullscreen_media_close(state); @@ -568,7 +576,6 @@ impl Damus { let support = Support::new(app_context.path); let note_options = get_note_options(parsed_args, app_context.settings); - let jobs = JobsCacheOld::default(); let threads = Threads::default(); Self { @@ -583,7 +590,6 @@ impl Damus { support, decks_cache, unrecognized_args, - jobs, threads, onboarding: Onboarding::default(), hovered_column: None, @@ -635,7 +641,6 @@ impl Damus { options, decks_cache, unrecognized_args: BTreeSet::default(), - jobs: JobsCacheOld::default(), threads: Threads::default(), onboarding: Onboarding::default(), hovered_column: None, @@ -915,6 +920,7 @@ fn timelines_view( ctx.i18n, ctx.ndb, ctx.img_cache, + ctx.media_jobs.sender(), ) .show(ui); diff --git a/crates/notedeck_columns/src/nav.rs b/crates/notedeck_columns/src/nav.rs @@ -551,6 +551,7 @@ fn process_render_nav_action( ctx.zaps, ctx.img_cache, &mut app.view_state, + ctx.media_jobs.sender(), ui, ) } @@ -627,7 +628,7 @@ fn render_nav_body( note_cache: ctx.note_cache, zaps: ctx.zaps, pool: ctx.pool, - job_pool: ctx.job_pool, + jobs: ctx.media_jobs.sender(), unknown_ids: ctx.unknown_ids, clipboard: ctx.clipboard, i18n: ctx.i18n, @@ -650,7 +651,6 @@ fn render_nav_body( depth, ui, &mut note_context, - &mut app.jobs, scroll_to_top, ); @@ -670,13 +670,11 @@ fn render_nav_body( app.note_options, ui, &mut note_context, - &mut app.jobs, ), Route::Accounts(amr) => { let resp = render_accounts_route( ui, ctx, - &mut app.jobs, &mut app.view_state.login, &mut app.onboarding, &mut app.view_state.follow_packs, @@ -706,7 +704,6 @@ fn render_nav_body( ctx.settings.get_settings_mut(), &mut note_context, &mut app.note_options, - &mut app.jobs, ) .ui(ui) .map_output(RenderNavAction::SettingsAction), @@ -751,7 +748,6 @@ fn render_nav_body( &note, inner_rect, options, - &mut app.jobs, col, ) .show(ui) @@ -786,7 +782,6 @@ fn render_nav_body( &note, inner_rect, app.note_options, - &mut app.jobs, col, ) .show(ui); @@ -818,7 +813,6 @@ fn render_nav_body( kp, inner_rect, app.note_options, - &mut app.jobs, ) .ui(&txn, ui); @@ -852,15 +846,9 @@ fn render_nav_body( search_buffer.focus_state = FocusState::ShouldRequestFocus; } - SearchView::new( - &txn, - app.note_options, - search_buffer, - &mut note_context, - &mut app.jobs, - ) - .show(ui) - .map_output(RenderNavAction::NoteAction) + SearchView::new(&txn, app.note_options, search_buffer, &mut note_context) + .show(ui) + .map_output(RenderNavAction::NoteAction) } Route::NewDeck => { let id = ui.id().with("new-deck"); @@ -933,22 +921,28 @@ fn render_nav_body( return BodyResponse::none(); }; - EditProfileView::new(ctx.i18n, state, ctx.img_cache, ctx.clipboard) - .ui(ui) - .map_output_maybe(|save| { - if save { - app.view_state - .pubkey_to_profile_state - .get(kp.pubkey) - .map(|state| { - RenderNavAction::ProfileAction(ProfileAction::SaveChanges( - SaveProfileChanges::new(kp.to_full(), state.clone()), - )) - }) - } else { - None - } - }) + EditProfileView::new( + ctx.i18n, + state, + ctx.img_cache, + ctx.clipboard, + ctx.media_jobs.sender(), + ) + .ui(ui) + .map_output_maybe(|save| { + if save { + app.view_state + .pubkey_to_profile_state + .get(kp.pubkey) + .map(|state| { + RenderNavAction::ProfileAction(ProfileAction::SaveChanges( + SaveProfileChanges::new(kp.to_full(), state.clone()), + )) + }) + } else { + None + } + }) } Route::Following(pubkey) => { let cache_id = egui::Id::new(("following_contacts_cache", pubkey)); @@ -1068,6 +1062,7 @@ fn render_nav_body( &txn, &target.zap_recipient, default_msats, + ctx.media_jobs.sender(), ) .ui(ui), ) @@ -1218,6 +1213,7 @@ pub fn render_nav( std::slice::from_ref(route), col, ctx.i18n, + ctx.media_jobs.sender(), ) .show_move_button(!narrow) .show_delete_button(!narrow) @@ -1262,6 +1258,7 @@ pub fn render_nav( nav.routes(), col, ctx.i18n, + ctx.media_jobs.sender(), ) .show_move_button(!narrow) .show_delete_button(!narrow) diff --git a/crates/notedeck_columns/src/timeline/route.rs b/crates/notedeck_columns/src/timeline/route.rs @@ -6,7 +6,7 @@ use crate::{ }; use enostr::Pubkey; -use notedeck::{JobsCacheOld, NoteContext}; +use notedeck::NoteContext; use notedeck_ui::NoteOptions; #[allow(clippy::too_many_arguments)] @@ -18,7 +18,6 @@ pub fn render_timeline_route( depth: usize, ui: &mut egui::Ui, note_context: &mut NoteContext, - jobs: &mut JobsCacheOld, scroll_to_top: bool, ) -> BodyResponse<RenderNavAction> { match kind { @@ -30,35 +29,20 @@ pub fn render_timeline_route( | TimelineKind::Hashtag(_) | TimelineKind::Generic(_) => { let resp = - ui::TimelineView::new(kind, timeline_cache, note_context, note_options, jobs, col) - .ui(ui); + ui::TimelineView::new(kind, timeline_cache, note_context, note_options, col).ui(ui); resp.map_output(RenderNavAction::NoteAction) } TimelineKind::Profile(pubkey) => { if depth > 1 { - render_profile_route( - pubkey, - timeline_cache, - col, - ui, - note_options, - note_context, - jobs, - ) + render_profile_route(pubkey, timeline_cache, col, ui, note_options, note_context) } else { // we render profiles like timelines if they are at the root - let resp = ui::TimelineView::new( - kind, - timeline_cache, - note_context, - note_options, - jobs, - col, - ) - .scroll_to_top(scroll_to_top) - .ui(ui); + let resp = + ui::TimelineView::new(kind, timeline_cache, note_context, note_options, col) + .scroll_to_top(scroll_to_top) + .ui(ui); resp.map_output(RenderNavAction::NoteAction) } @@ -74,7 +58,6 @@ pub fn render_thread_route( mut note_options: NoteOptions, ui: &mut egui::Ui, note_context: &mut NoteContext, - jobs: &mut JobsCacheOld, ) -> BodyResponse<RenderNavAction> { // don't truncate thread notes for now, since they are // default truncated everywher eelse @@ -88,7 +71,6 @@ pub fn render_thread_route( selection.selected_or_root(), note_options, note_context, - jobs, col, ) .ui(ui) @@ -103,17 +85,9 @@ pub fn render_profile_route( ui: &mut egui::Ui, note_options: NoteOptions, note_context: &mut NoteContext, - jobs: &mut JobsCacheOld, ) -> BodyResponse<RenderNavAction> { - let profile_view = ProfileView::new( - pubkey, - col, - timeline_cache, - note_options, - note_context, - jobs, - ) - .ui(ui); + let profile_view = + ProfileView::new(pubkey, col, timeline_cache, note_options, note_context).ui(ui); profile_view.map_output_maybe(|action| match action { ui::profile::ProfileViewAction::EditProfile => note_context diff --git a/crates/notedeck_columns/src/ui/accounts.rs b/crates/notedeck_columns/src/ui/accounts.rs @@ -3,7 +3,7 @@ use egui::{ }; use enostr::Pubkey; use nostrdb::{Ndb, Transaction}; -use notedeck::{tr, Accounts, Images, Localization}; +use notedeck::{tr, Accounts, Images, Localization, MediaJobSender}; use notedeck_ui::colors::PINK; use notedeck_ui::profile::preview::SimpleProfilePreview; @@ -15,6 +15,7 @@ pub struct AccountsView<'a> { ndb: &'a Ndb, accounts: &'a Accounts, img_cache: &'a mut Images, + jobs: &'a MediaJobSender, i18n: &'a mut Localization, } @@ -35,6 +36,7 @@ impl<'a> AccountsView<'a> { pub fn new( ndb: &'a Ndb, accounts: &'a Accounts, + jobs: &'a MediaJobSender, img_cache: &'a mut Images, i18n: &'a mut Localization, ) -> Self { @@ -43,6 +45,7 @@ impl<'a> AccountsView<'a> { accounts, img_cache, i18n, + jobs, } } @@ -57,7 +60,14 @@ impl<'a> AccountsView<'a> { let scroll_out = scroll_area() .id_salt(AccountsView::scroll_id()) .show(ui, |ui| { - Self::show_accounts(ui, self.accounts, self.ndb, self.img_cache, self.i18n) + Self::show_accounts( + ui, + self.accounts, + self.ndb, + self.img_cache, + self.jobs, + self.i18n, + ) }); out.set_scroll_id(&scroll_out); @@ -77,6 +87,7 @@ impl<'a> AccountsView<'a> { accounts: &Accounts, ndb: &Ndb, img_cache: &mut Images, + jobs: &MediaJobSender, i18n: &mut Localization, ) -> Option<AccountsViewResponse> { let mut return_op: Option<AccountsViewResponse> = None; @@ -103,6 +114,7 @@ impl<'a> AccountsView<'a> { let preview = SimpleProfilePreview::new( profile.as_ref(), img_cache, + jobs, i18n, has_nsec, ); diff --git a/crates/notedeck_columns/src/ui/add_column.rs b/crates/notedeck_columns/src/ui/add_column.rs @@ -17,7 +17,9 @@ use crate::{ Damus, }; -use notedeck::{tr, AppContext, Images, Localization, NotedeckTextStyle, UserAccount}; +use notedeck::{ + tr, AppContext, Images, Localization, MediaJobSender, NotedeckTextStyle, UserAccount, +}; use notedeck_ui::{anim::ICON_EXPANSION_MULTIPLE, app_images}; use tokenator::{ParseError, TokenParser, TokenSerializable, TokenWriter}; @@ -168,6 +170,7 @@ pub struct AddColumnView<'a> { img_cache: &'a mut Images, cur_account: &'a UserAccount, i18n: &'a mut Localization, + jobs: &'a MediaJobSender, } impl<'a> AddColumnView<'a> { @@ -177,6 +180,7 @@ impl<'a> AddColumnView<'a> { img_cache: &'a mut Images, cur_account: &'a UserAccount, i18n: &'a mut Localization, + jobs: &'a MediaJobSender, ) -> Self { Self { key_state_map, @@ -184,6 +188,7 @@ impl<'a> AddColumnView<'a> { img_cache, cur_account, i18n, + jobs, } } @@ -349,7 +354,7 @@ impl<'a> AddColumnView<'a> { bottom: 32, }) .show(ui, |ui| { - ProfilePreview::new(&profile, self.img_cache).ui(ui); + ProfilePreview::new(&profile, self.img_cache, self.jobs).ui(ui); }); } } @@ -673,6 +678,7 @@ pub fn render_add_column_routes( ctx.img_cache, ctx.accounts.get_selected_account(), ctx.i18n, + ctx.media_jobs.sender(), ); let resp = match route { AddColumnRoute::Base => add_column_view.ui(ui), diff --git a/crates/notedeck_columns/src/ui/column/header.rs b/crates/notedeck_columns/src/ui/column/header.rs @@ -13,7 +13,7 @@ use egui::{Margin, Response, RichText, Sense, Stroke, UiBuilder}; use enostr::Pubkey; use nostrdb::{Ndb, Transaction}; use notedeck::tr; -use notedeck::{Images, Localization, NotedeckTextStyle}; +use notedeck::{Images, Localization, MediaJobSender, NotedeckTextStyle}; use notedeck_ui::app_images; use notedeck_ui::{ anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE}, @@ -28,6 +28,7 @@ pub struct NavTitle<'a> { col_id: usize, options: u32, i18n: &'a mut Localization, + jobs: &'a MediaJobSender, } impl<'a> NavTitle<'a> { @@ -42,6 +43,7 @@ impl<'a> NavTitle<'a> { routes: &'a [Route], col_id: usize, i18n: &'a mut Localization, + jobs: &'a MediaJobSender, ) -> Self { let options = Self::SHOW_MOVE | Self::SHOW_DELETE; NavTitle { @@ -52,6 +54,7 @@ impl<'a> NavTitle<'a> { col_id, options, i18n, + jobs, } } @@ -435,11 +438,8 @@ impl<'a> NavTitle<'a> { .as_ref() .ok() .and_then(move |p| { - Some( - ProfilePic::from_profile(self.img_cache, p)? - .size(pfp_size) - .sense(Sense::click()), - ) + ProfilePic::from_profile(self.img_cache, self.jobs, p) + .map(|pfp| pfp.size(pfp_size).sense(Sense::click())) }) } @@ -453,7 +453,7 @@ impl<'a> NavTitle<'a> { ui.add(&mut pfp) } else { ui.add( - &mut ProfilePic::new(self.img_cache, notedeck::profile::no_pfp_url()) + &mut ProfilePic::new(self.img_cache, self.jobs, notedeck::profile::no_pfp_url()) .size(pfp_size) .sense(Sense::click()), ) @@ -515,7 +515,7 @@ impl<'a> NavTitle<'a> { ui.add(&mut pfp) } else { ui.add( - &mut ProfilePic::new(self.img_cache, notedeck::profile::no_pfp_url()) + &mut ProfilePic::new(self.img_cache, self.jobs, notedeck::profile::no_pfp_url()) .size(pfp_size) .sense(Sense::click()), ) @@ -536,7 +536,10 @@ impl<'a> NavTitle<'a> { } } - ui.add(&mut ProfilePic::new(self.img_cache, notedeck::profile::no_pfp_url()).size(pfp_size)) + ui.add( + &mut ProfilePic::new(self.img_cache, self.jobs, notedeck::profile::no_pfp_url()) + .size(pfp_size), + ) } fn title_label_value(title: &str) -> egui::Label { diff --git a/crates/notedeck_columns/src/ui/mentions_picker.rs b/crates/notedeck_columns/src/ui/mentions_picker.rs @@ -1,7 +1,7 @@ use egui::{vec2, FontId, Layout, Pos2, Rect, ScrollArea, UiBuilder, Vec2b}; use nostrdb::{Ndb, ProfileRecord, Transaction}; use notedeck::{ - fonts::get_font_size, name::get_display_name, profile::get_profile_url, Images, + fonts::get_font_size, name::get_display_name, profile::get_profile_url, Images, MediaJobSender, NotedeckTextStyle, }; use notedeck_ui::{ @@ -20,6 +20,7 @@ pub struct MentionPickerView<'a> { txn: &'a Transaction, img_cache: &'a mut Images, results: &'a Vec<&'a [u8; 32]>, + jobs: &'a MediaJobSender, } pub enum MentionPickerResponse { @@ -33,12 +34,14 @@ impl<'a> MentionPickerView<'a> { ndb: &'a Ndb, txn: &'a Transaction, results: &'a Vec<&'a [u8; 32]>, + jobs: &'a MediaJobSender, ) -> Self { Self { ndb, txn, img_cache, results, + jobs, } } @@ -55,7 +58,7 @@ impl<'a> MentionPickerView<'a> { }; if ui - .add(user_result(&profile, self.img_cache, i, width)) + .add(user_result(&profile, self.img_cache, self.jobs, i, width)) .clicked() { selection = Some(i) @@ -129,6 +132,7 @@ impl<'a> MentionPickerView<'a> { fn user_result<'a>( profile: &'a ProfileRecord<'_>, cache: &'a mut Images, + jobs: &'a MediaJobSender, index: usize, width: f32, ) -> impl egui::Widget + 'a { @@ -161,7 +165,7 @@ fn user_result<'a>( let pfp_resp = ui.put( icon_rect, - &mut ProfilePic::new(cache, get_profile_url(Some(profile))) + &mut ProfilePic::new(cache, jobs, get_profile_url(Some(profile))) .size(helper.scale_1d_pos(min_img_size)), ); diff --git a/crates/notedeck_columns/src/ui/note/custom_zap.rs b/crates/notedeck_columns/src/ui/note/custom_zap.rs @@ -6,7 +6,7 @@ use enostr::Pubkey; use nostrdb::{Ndb, ProfileRecord, Transaction}; use notedeck::{ fonts::get_font_size, get_profile_url, name::get_display_name, tr, Images, Localization, - NotedeckTextStyle, + MediaJobSender, NotedeckTextStyle, }; use notedeck_ui::{ app_images, colors, profile::display_name_widget, widgets::styled_button_toggleable, @@ -20,6 +20,7 @@ pub struct CustomZapView<'a> { target_pubkey: &'a Pubkey, default_msats: u64, i18n: &'a mut Localization, + jobs: &'a MediaJobSender, } #[allow(clippy::new_without_default)] @@ -31,6 +32,7 @@ impl<'a> CustomZapView<'a> { txn: &'a Transaction, target_pubkey: &'a Pubkey, default_msats: u64, + jobs: &'a MediaJobSender, ) -> Self { Self { target_pubkey, @@ -39,6 +41,7 @@ impl<'a> CustomZapView<'a> { txn, default_msats, i18n, + jobs, } } @@ -59,7 +62,7 @@ impl<'a> CustomZapView<'a> { .get_profile_by_pubkey(self.txn, self.target_pubkey.bytes()) .ok(); let profile = profile.as_ref(); - show_profile(ui, self.images, profile); + show_profile(ui, self.images, self.jobs, profile); ui.add_space(8.0); @@ -167,13 +170,18 @@ fn show_title(ui: &mut egui::Ui, i18n: &mut Localization) { ); } -fn show_profile(ui: &mut egui::Ui, images: &mut Images, profile: Option<&ProfileRecord>) { +fn show_profile( + ui: &mut egui::Ui, + images: &mut Images, + jobs: &MediaJobSender, + profile: Option<&ProfileRecord>, +) { let max_size = 24.0; ui.allocate_ui_with_layout( vec2(ui.available_width(), max_size), Layout::left_to_right(egui::Align::Center).with_main_wrap(true), |ui| { - ui.add(&mut ProfilePic::new(images, get_profile_url(profile)).size(max_size)); + ui.add(&mut ProfilePic::new(images, jobs, get_profile_url(profile)).size(max_size)); ui.vertical(|ui| { ui.add(display_name_widget(&get_display_name(profile), false)); }); diff --git a/crates/notedeck_columns/src/ui/note/post.rs b/crates/notedeck_columns/src/ui/note/post.rs @@ -13,12 +13,12 @@ use egui::{ }; use enostr::{FilledKeypair, FullKeypair, NoteId, Pubkey, RelayPool}; use nostrdb::{Ndb, Transaction}; -use notedeck::media::gif::ensure_latest_texture; +use notedeck::media::latest::LatestImageTex; use notedeck::media::AnimationMode; #[cfg(target_os = "android")] use notedeck::platform::android::try_open_file_picker; use notedeck::platform::get_next_selected_file; -use notedeck::{get_render_state, JobsCacheOld, PixelDimensions, RenderState}; +use notedeck::PixelDimensions; use notedeck::{ name::get_display_name, supported_mime_hosted_at_url, tr, Localization, NoteAction, NoteContext, }; @@ -39,7 +39,6 @@ pub struct PostView<'a, 'd> { poster: FilledKeypair<'a>, inner_rect: egui::Rect, note_options: NoteOptions, - jobs: &'a mut JobsCacheOld, animation_mode: AnimationMode, } @@ -112,7 +111,6 @@ impl<'a, 'd> PostView<'a, 'd> { poster: FilledKeypair<'a>, inner_rect: egui::Rect, note_options: NoteOptions, - jobs: &'a mut JobsCacheOld, ) -> Self { let animation_mode = if note_options.contains(NoteOptions::NoAnimations) { AnimationMode::NoAnimation @@ -127,7 +125,6 @@ impl<'a, 'd> PostView<'a, 'd> { inner_rect, note_options, animation_mode, - jobs, } } @@ -157,15 +154,20 @@ impl<'a, 'd> PostView<'a, 'd> { .as_ref() .ok() .and_then(|p| { - Some(ProfilePic::from_profile(self.note_context.img_cache, p)?.size(pfp_size)) + ProfilePic::from_profile(self.note_context.img_cache, self.note_context.jobs, p) + .map(|pfp| pfp.size(pfp_size)) }); if let Some(mut pfp) = poster_pfp { ui.add(&mut pfp); } else { ui.add( - &mut ProfilePic::new(self.note_context.img_cache, notedeck::profile::no_pfp_url()) - .size(pfp_size), + &mut ProfilePic::new( + self.note_context.img_cache, + self.note_context.jobs, + notedeck::profile::no_pfp_url(), + ) + .size(pfp_size), ); } @@ -307,6 +309,7 @@ impl<'a, 'd> PostView<'a, 'd> { self.note_context.ndb, txn, &res, + self.note_context.jobs, ) .show_in_rect(hint_rect, ui); @@ -443,7 +446,6 @@ impl<'a, 'd> PostView<'a, 'd> { id.bytes(), nostrdb::NoteKey::new(0), self.note_options, - self.jobs, ) }) .inner @@ -541,13 +543,19 @@ impl<'a, 'd> PostView<'a, 'd> { }; let url = &media.url; - let cur_state = get_render_state( - ui.ctx(), - self.note_context.img_cache, - cache_type, - url, - notedeck::ImageType::Content(Some((width, height))), - ); + + let cur_state = self + .note_context + .img_cache + .no_img_loading_tex_loader() + .latest_state( + self.note_context.jobs, + ui.ctx(), + url, + cache_type, + notedeck::ImageType::Content(Some((width, height))), + self.animation_mode, + ); render_post_view_media( ui, @@ -557,8 +565,6 @@ impl<'a, 'd> PostView<'a, 'd> { width, height, cur_state, - url, - self.animation_mode, ) } to_remove.reverse(); @@ -644,19 +650,17 @@ fn render_post_view_media( cur_index: usize, width: u32, height: u32, - render_state: RenderState, - url: &str, - animation_mode: AnimationMode, + render_state: LatestImageTex, ) { - match render_state.texture_state { - notedeck::TextureStateOld::Pending => { + match render_state { + LatestImageTex::Pending => { ui.spinner(); } - notedeck::TextureStateOld::Error(e) => { + LatestImageTex::Error(e) => { upload_errors.push(e.to_string()); error!("{e}"); } - notedeck::TextureStateOld::Loaded(renderable_media) => { + LatestImageTex::Loaded(tex) => { let max_size = 300; let size = if width > max_size || height > max_size { PixelDimensions { x: 300, y: 300 } @@ -669,13 +673,7 @@ fn render_post_view_media( .to_points(ui.pixels_per_point()) .to_vec(); - let texture_handle = - ensure_latest_texture(ui, url, render_state.gifs, renderable_media, animation_mode); - let img_resp = ui.add( - egui::Image::new(&texture_handle) - .max_size(size) - .corner_radius(12.0), - ); + let img_resp = ui.add(egui::Image::new(tex).max_size(size).corner_radius(12.0)); let remove_button_rect = { let top_left = img_resp.rect.left_top(); @@ -837,7 +835,6 @@ mod preview { pub struct PostPreview { draft: Draft, poster: FullKeypair, - jobs: JobsCacheOld, } impl PostPreview { @@ -867,7 +864,6 @@ mod preview { PostPreview { draft, poster: FullKeypair::generate(), - jobs: Default::default(), } } } @@ -883,7 +879,7 @@ mod preview { note_cache: app.note_cache, zaps: app.zaps, pool: app.pool, - job_pool: app.job_pool, + jobs: app.media_jobs.sender(), unknown_ids: app.unknown_ids, clipboard: app.clipboard, i18n: app.i18n, @@ -896,7 +892,6 @@ mod preview { self.poster.to_filled(), ui.available_rect_before_wrap(), NoteOptions::default(), - &mut self.jobs, ) .ui(&txn, ui); diff --git a/crates/notedeck_columns/src/ui/note/quote_repost.rs b/crates/notedeck_columns/src/ui/note/quote_repost.rs @@ -7,7 +7,7 @@ use crate::{ use egui::ScrollArea; use enostr::{FilledKeypair, NoteId}; -use notedeck::{JobsCacheOld, NoteContext}; +use notedeck::NoteContext; use notedeck_ui::NoteOptions; pub struct QuoteRepostView<'a, 'd> { @@ -18,7 +18,6 @@ pub struct QuoteRepostView<'a, 'd> { scroll_id: egui::Id, inner_rect: egui::Rect, note_options: NoteOptions, - jobs: &'a mut JobsCacheOld, } impl<'a, 'd> QuoteRepostView<'a, 'd> { @@ -30,7 +29,6 @@ impl<'a, 'd> QuoteRepostView<'a, 'd> { quoting_note: &'a nostrdb::Note<'a>, inner_rect: egui::Rect, note_options: NoteOptions, - jobs: &'a mut JobsCacheOld, col: usize, ) -> Self { QuoteRepostView { @@ -41,7 +39,6 @@ impl<'a, 'd> QuoteRepostView<'a, 'd> { scroll_id: QuoteRepostView::scroll_id(col, quoting_note.id()), inner_rect, note_options, - jobs, } } @@ -78,7 +75,6 @@ impl<'a, 'd> QuoteRepostView<'a, 'd> { self.poster, self.inner_rect, self.note_options, - self.jobs, ) .ui_no_scroll(self.quoting_note.txn().unwrap(), ui); post_resp diff --git a/crates/notedeck_columns/src/ui/note/reply.rs b/crates/notedeck_columns/src/ui/note/reply.rs @@ -7,7 +7,7 @@ use crate::ui::{ use egui::{Rect, Response, ScrollArea, Ui}; use enostr::{FilledKeypair, NoteId}; -use notedeck::{JobsCacheOld, NoteContext}; +use notedeck::NoteContext; use notedeck_ui::{NoteOptions, NoteView, ProfilePic}; pub struct PostReplyView<'a, 'd> { @@ -18,7 +18,6 @@ pub struct PostReplyView<'a, 'd> { scroll_id: egui::Id, inner_rect: egui::Rect, note_options: NoteOptions, - jobs: &'a mut JobsCacheOld, } impl<'a, 'd> PostReplyView<'a, 'd> { @@ -30,7 +29,6 @@ impl<'a, 'd> PostReplyView<'a, 'd> { note: &'a nostrdb::Note<'a>, inner_rect: egui::Rect, note_options: NoteOptions, - jobs: &'a mut JobsCacheOld, col: usize, ) -> Self { PostReplyView { @@ -41,7 +39,6 @@ impl<'a, 'd> PostReplyView<'a, 'd> { scroll_id: PostReplyView::scroll_id(col, note.id()), inner_rect, note_options, - jobs, } } @@ -85,7 +82,7 @@ impl<'a, 'd> PostReplyView<'a, 'd> { let quoted_note = egui::Frame::NONE .outer_margin(egui::Margin::same(note_offset)) .show(ui, |ui| { - NoteView::new(self.note_context, self.note, self.note_options, self.jobs) + NoteView::new(self.note_context, self.note, self.note_options) .truncate(false) .selectable_text(true) .actionbar(false) @@ -106,7 +103,6 @@ impl<'a, 'd> PostReplyView<'a, 'd> { self.poster, self.inner_rect, self.note_options, - self.jobs, ) .ui_no_scroll(self.note.txn().unwrap(), ui) }; diff --git a/crates/notedeck_columns/src/ui/onboarding.rs b/crates/notedeck_columns/src/ui/onboarding.rs @@ -2,7 +2,7 @@ use std::mem; use egui::{Layout, ScrollArea}; use nostrdb::Ndb; -use notedeck::{tr, Images, JobPool, JobsCacheOld, Localization}; +use notedeck::{tr, Images, Localization, MediaJobSender}; use notedeck_ui::{ colors, nip51_set::{Nip51SetUiCache, Nip51SetWidget, Nip51SetWidgetAction, Nip51SetWidgetFlags}, @@ -17,8 +17,7 @@ pub struct FollowPackOnboardingView<'a> { ndb: &'a Ndb, images: &'a mut Images, loc: &'a mut Localization, - job_pool: &'a mut JobPool, - jobs: &'a mut JobsCacheOld, + jobs: &'a MediaJobSender, } pub enum OnboardingResponse { @@ -38,8 +37,7 @@ impl<'a> FollowPackOnboardingView<'a> { ndb: &'a Ndb, images: &'a mut Images, loc: &'a mut Localization, - job_pool: &'a mut JobPool, - jobs: &'a mut JobsCacheOld, + jobs: &'a MediaJobSender, ) -> Self { Self { onboarding, @@ -47,7 +45,6 @@ impl<'a> FollowPackOnboardingView<'a> { ndb, images, loc, - job_pool, jobs, } } @@ -81,7 +78,6 @@ impl<'a> FollowPackOnboardingView<'a> { self.ndb, self.loc, self.images, - self.job_pool, self.jobs, ) .with_flags(Nip51SetWidgetFlags::TRUST_IMAGES) diff --git a/crates/notedeck_columns/src/ui/profile/contacts_list.rs b/crates/notedeck_columns/src/ui/profile/contacts_list.rs @@ -67,7 +67,12 @@ impl<'a, 'd, 'txn> ContactsListView<'a, 'd, 'txn> { ui.add_space(16.0); ui.add( - &mut ProfilePic::new(self.note_context.img_cache, profile_url).size(48.0), + &mut ProfilePic::new( + self.note_context.img_cache, + self.note_context.jobs, + profile_url, + ) + .size(48.0), ); ui.add_space(12.0); diff --git a/crates/notedeck_columns/src/ui/profile/edit.rs b/crates/notedeck_columns/src/ui/profile/edit.rs @@ -3,7 +3,9 @@ use core::f32; use egui::{vec2, Button, CornerRadius, Layout, Margin, RichText, ScrollArea, TextEdit}; use egui_winit::clipboard::Clipboard; use enostr::ProfileState; -use notedeck::{profile::unwrap_profile_url, tr, Images, Localization, NotedeckTextStyle}; +use notedeck::{ + profile::unwrap_profile_url, tr, Images, Localization, MediaJobSender, NotedeckTextStyle, +}; use notedeck_ui::context_menu::{input_context, PasteBehavior}; use notedeck_ui::{profile::banner, ProfilePic}; @@ -14,6 +16,7 @@ pub struct EditProfileView<'a> { clipboard: &'a mut Clipboard, img_cache: &'a mut Images, i18n: &'a mut Localization, + jobs: &'a MediaJobSender, } impl<'a> EditProfileView<'a> { @@ -22,12 +25,14 @@ impl<'a> EditProfileView<'a> { state: &'a mut ProfileState, img_cache: &'a mut Images, clipboard: &'a mut Clipboard, + jobs: &'a MediaJobSender, ) -> Self { Self { i18n, state, img_cache, clipboard, + jobs, } } @@ -89,7 +94,7 @@ impl<'a> EditProfileView<'a> { let pfp_url = unwrap_profile_url(self.state.picture()); ui.put( pfp_rect, - &mut ProfilePic::new(self.img_cache, pfp_url) + &mut ProfilePic::new(self.img_cache, self.jobs, pfp_url) .size(size) .border(ProfilePic::border_stroke(ui)), ); diff --git a/crates/notedeck_columns/src/ui/profile/mod.rs b/crates/notedeck_columns/src/ui/profile/mod.rs @@ -17,8 +17,8 @@ use crate::{ ui::timeline::{tabs_ui, TimelineTabView}, }; use notedeck::{ - name::get_display_name, profile::get_profile_url, IsFollowing, JobsCacheOld, NoteAction, - NoteContext, NotedeckTextStyle, + name::get_display_name, profile::get_profile_url, IsFollowing, NoteAction, NoteContext, + NotedeckTextStyle, }; use notedeck_ui::{ app_images, @@ -32,7 +32,6 @@ pub struct ProfileView<'a, 'd> { timeline_cache: &'a mut TimelineCache, note_options: NoteOptions, note_context: &'a mut NoteContext<'d>, - jobs: &'a mut JobsCacheOld, } pub enum ProfileViewAction { @@ -58,7 +57,6 @@ impl<'a, 'd> ProfileView<'a, 'd> { timeline_cache: &'a mut TimelineCache, note_options: NoteOptions, note_context: &'a mut NoteContext<'d>, - jobs: &'a mut JobsCacheOld, ) -> Self { ProfileView { pubkey, @@ -66,7 +64,6 @@ impl<'a, 'd> ProfileView<'a, 'd> { timeline_cache, note_options, note_context, - jobs, } } @@ -125,7 +122,6 @@ impl<'a, 'd> ProfileView<'a, 'd> { self.note_options, &txn, self.note_context, - self.jobs, ) .show(ui) { @@ -191,9 +187,13 @@ fn profile_body( ui.horizontal(|ui| { ui.put( pfp_rect, - &mut ProfilePic::new(note_context.img_cache, get_profile_url(profile)) - .size(size) - .border(ProfilePic::border_stroke(ui)), + &mut ProfilePic::new( + note_context.img_cache, + note_context.jobs, + get_profile_url(profile), + ) + .size(size) + .border(ProfilePic::border_stroke(ui)), ); if ui diff --git a/crates/notedeck_columns/src/ui/search/mod.rs b/crates/notedeck_columns/src/ui/search/mod.rs @@ -11,7 +11,7 @@ use egui_winit::clipboard::Clipboard; use nostrdb::{Filter, Ndb, ProfileRecord, Transaction}; use notedeck::{ fonts::get_font_size, name::get_display_name, profile::get_profile_url, tr, tr_plural, Images, - JobsCacheOld, Localization, NoteAction, NoteContext, NoteRef, NotedeckTextStyle, + Localization, MediaJobSender, NoteAction, NoteContext, NoteRef, NotedeckTextStyle, }; use notedeck_ui::{ @@ -33,7 +33,6 @@ pub struct SearchView<'a, 'd> { note_options: NoteOptions, txn: &'a Transaction, note_context: &'a mut NoteContext<'d>, - jobs: &'a mut JobsCacheOld, } impl<'a, 'd> SearchView<'a, 'd> { @@ -42,14 +41,12 @@ impl<'a, 'd> SearchView<'a, 'd> { note_options: NoteOptions, query: &'a mut SearchQueryState, note_context: &'a mut NoteContext<'d>, - jobs: &'a mut JobsCacheOld, ) -> Self { Self { txn, query, note_options, note_context, - jobs, } } @@ -174,6 +171,7 @@ impl<'a, 'd> SearchView<'a, 'd> { self.note_context.ndb, self.txn, &results, + self.note_context.jobs, ) .show_in_rect(ui.available_rect_before_wrap(), ui); @@ -259,6 +257,7 @@ impl<'a, 'd> SearchView<'a, 'd> { is_selected, ui.available_width(), self.note_context.img_cache, + self.note_context.jobs, )); if resp.clicked() { @@ -327,6 +326,7 @@ impl<'a, 'd> SearchView<'a, 'd> { is_selected, ui.available_width(), self.note_context.img_cache, + self.note_context.jobs, )); if resp.clicked() || (is_selected && keyboard_resp.enter_pressed) { @@ -354,7 +354,6 @@ impl<'a, 'd> SearchView<'a, 'd> { self.note_options, self.txn, self.note_context, - self.jobs, ) .show(ui) }); @@ -706,6 +705,7 @@ fn recent_profile_item<'a>( is_selected: bool, width: f32, cache: &'a mut Images, + jobs: &'a MediaJobSender, ) -> impl egui::Widget + 'a { move |ui: &mut egui::Ui| -> egui::Response { let min_img_size = 48.0; @@ -733,7 +733,7 @@ fn recent_profile_item<'a>( ui.put( pfp_rect, - &mut ProfilePic::new(cache, get_profile_url(profile)).size(min_img_size), + &mut ProfilePic::new(cache, jobs, get_profile_url(profile)).size(min_img_size), ); let name = get_display_name(profile).name(); diff --git a/crates/notedeck_columns/src/ui/settings.rs b/crates/notedeck_columns/src/ui/settings.rs @@ -6,7 +6,7 @@ use egui_extras::{Size, StripBuilder}; use enostr::NoteId; use nostrdb::Transaction; use notedeck::{ - tr, ui::richtext_small, Images, JobsCacheOld, LanguageIdentifier, Localization, NoteContext, + tr, ui::richtext_small, Images, LanguageIdentifier, Localization, NoteContext, NotedeckTextStyle, Settings, SettingsHandler, DEFAULT_MAX_HASHTAGS_PER_NOTE, DEFAULT_NOTE_BODY_FONT_SIZE, }; @@ -109,7 +109,6 @@ pub struct SettingsView<'a> { settings: &'a mut Settings, note_context: &'a mut NoteContext<'a>, note_options: &'a mut NoteOptions, - jobs: &'a mut JobsCacheOld, } fn settings_group<S>(ui: &mut egui::Ui, title: S, contents: impl FnOnce(&mut egui::Ui)) @@ -136,13 +135,11 @@ impl<'a> SettingsView<'a> { settings: &'a mut Settings, note_context: &'a mut NoteContext<'a>, note_options: &'a mut NoteOptions, - jobs: &'a mut JobsCacheOld, ) -> Self { Self { settings, note_context, note_options, - jobs, } } @@ -210,15 +207,10 @@ impl<'a> SettingsView<'a> { if notedeck::ui::is_narrow(ui.ctx()) { ui.set_max_width(ui.available_width()); - NoteView::new( - self.note_context, - &preview_note, - *self.note_options, - self.jobs, - ) - .actionbar(false) - .options_button(false) - .show(ui); + NoteView::new(self.note_context, &preview_note, *self.note_options) + .actionbar(false) + .options_button(false) + .show(ui); } }); ui.separator(); diff --git a/crates/notedeck_columns/src/ui/side_panel.rs b/crates/notedeck_columns/src/ui/side_panel.rs @@ -12,7 +12,7 @@ use crate::{ route::Route, }; -use notedeck::{tr, Accounts, Localization, UserAccount}; +use notedeck::{tr, Accounts, Localization, MediaJobSender, UserAccount}; use notedeck_ui::{ anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE}, app_images, colors, ProfilePic, View, @@ -29,6 +29,7 @@ pub struct DesktopSidePanel<'a> { i18n: &'a mut Localization, ndb: &'a nostrdb::Ndb, img_cache: &'a mut notedeck::Images, + jobs: &'a MediaJobSender, } impl View for DesktopSidePanel<'_> { @@ -68,6 +69,7 @@ impl<'a> DesktopSidePanel<'a> { i18n: &'a mut Localization, ndb: &'a nostrdb::Ndb, img_cache: &'a mut notedeck::Images, + jobs: &'a MediaJobSender, ) -> Self { Self { selected_account, @@ -75,6 +77,7 @@ impl<'a> DesktopSidePanel<'a> { i18n, ndb, img_cache, + jobs, } } @@ -163,7 +166,7 @@ impl<'a> DesktopSidePanel<'a> { let pfp_resp = ui .add( - &mut ProfilePic::new(self.img_cache, profile_url) + &mut ProfilePic::new(self.img_cache, self.jobs, profile_url) .size(avatar_size) .sense(egui::Sense::click()), ) diff --git a/crates/notedeck_columns/src/ui/thread.rs b/crates/notedeck_columns/src/ui/thread.rs @@ -2,7 +2,6 @@ use egui::InnerResponse; use egui_virtual_list::VirtualList; use nostrdb::{Note, Transaction}; use notedeck::note::root_note_id_from_selected_id; -use notedeck::JobsCacheOld; use notedeck::{NoteAction, NoteContext}; use notedeck_ui::note::NoteResponse; use notedeck_ui::{NoteOptions, NoteView}; @@ -16,7 +15,6 @@ pub struct ThreadView<'a, 'd> { note_options: NoteOptions, col: usize, note_context: &'a mut NoteContext<'d>, - jobs: &'a mut JobsCacheOld, } impl<'a, 'd> ThreadView<'a, 'd> { @@ -26,7 +24,6 @@ impl<'a, 'd> ThreadView<'a, 'd> { selected_note_id: &'a [u8; 32], note_options: NoteOptions, note_context: &'a mut NoteContext<'d>, - jobs: &'a mut JobsCacheOld, col: usize, ) -> Self { ThreadView { @@ -34,7 +31,6 @@ impl<'a, 'd> ThreadView<'a, 'd> { selected_note_id, note_options, note_context, - jobs, col, } } @@ -136,15 +132,7 @@ impl<'a, 'd> ThreadView<'a, 'd> { ui.colored_label(ui.visuals().error_fg_color, "LOADING NOTES"); } - show_notes( - ui, - list, - &notes, - self.note_context, - self.note_options, - self.jobs, - txn, - ) + show_notes(ui, list, &notes, self.note_context, self.note_options, txn) } } @@ -155,7 +143,6 @@ fn show_notes( thread_notes: &ThreadNotes, note_context: &mut NoteContext<'_>, flags: NoteOptions, - jobs: &mut JobsCacheOld, txn: &Transaction, ) -> Option<NoteAction> { let mut action = None; @@ -185,7 +172,7 @@ fn show_notes( return 1; } - let resp = note.show(note_context, flags, jobs, ui); + let resp = note.show(note_context, flags, ui); action = if cur_index == selected_note_index { resp.action.and_then(strip_note_action) @@ -326,11 +313,10 @@ impl<'a> ThreadNote<'a> { &self, note_context: &'a mut NoteContext<'_>, flags: NoteOptions, - jobs: &'a mut JobsCacheOld, ui: &mut egui::Ui, ) -> NoteResponse { let inner = notedeck_ui::padding(8.0, ui, |ui| { - NoteView::new(note_context, &self.note, self.options(flags), jobs) + NoteView::new(note_context, &self.note, self.options(flags)) .selected_style(self.note_type.is_selected()) .unread_indicator(self.unread_and_have_replies) .show(ui) diff --git a/crates/notedeck_columns/src/ui/timeline.rs b/crates/notedeck_columns/src/ui/timeline.rs @@ -6,7 +6,7 @@ use nostrdb::{Note, ProfileRecord, Transaction}; use notedeck::fonts::get_font_size; use notedeck::name::get_display_name; use notedeck::ui::is_narrow; -use notedeck::{tr_plural, JobsCacheOld, Muted, NotedeckTextStyle}; +use notedeck::{tr_plural, Muted, NotedeckTextStyle}; use notedeck_ui::app_images::{like_image_filled, repost_image}; use notedeck_ui::{ProfilePic, ProfilePreview}; use std::f32::consts::PI; @@ -30,7 +30,6 @@ pub struct TimelineView<'a, 'd> { timeline_cache: &'a mut TimelineCache, note_options: NoteOptions, note_context: &'a mut NoteContext<'d>, - jobs: &'a mut JobsCacheOld, col: usize, scroll_to_top: bool, } @@ -42,7 +41,6 @@ impl<'a, 'd> TimelineView<'a, 'd> { timeline_cache: &'a mut TimelineCache, note_context: &'a mut NoteContext<'d>, note_options: NoteOptions, - jobs: &'a mut JobsCacheOld, col: usize, ) -> Self { let scroll_to_top = false; @@ -51,7 +49,6 @@ impl<'a, 'd> TimelineView<'a, 'd> { timeline_cache, note_options, note_context, - jobs, col, scroll_to_top, } @@ -64,7 +61,6 @@ impl<'a, 'd> TimelineView<'a, 'd> { self.timeline_cache, self.note_options, self.note_context, - self.jobs, self.col, self.scroll_to_top, ) @@ -93,7 +89,6 @@ fn timeline_ui( timeline_cache: &mut TimelineCache, mut note_options: NoteOptions, note_context: &mut NoteContext, - jobs: &mut JobsCacheOld, col: usize, scroll_to_top: bool, ) -> BodyResponse<NoteAction> { @@ -185,14 +180,7 @@ fn timeline_ui( note_options.set(NoteOptions::Notification, true) } - TimelineTabView::new( - timeline.current_view(), - note_options, - &txn, - note_context, - jobs, - ) - .show(ui) + TimelineTabView::new(timeline.current_view(), note_options, &txn, note_context).show(ui) }); let at_top_after_scroll = scroll_output.state.offset.y == 0.0; @@ -378,7 +366,6 @@ pub struct TimelineTabView<'a, 'd> { note_options: NoteOptions, txn: &'a Transaction, note_context: &'a mut NoteContext<'d>, - jobs: &'a mut JobsCacheOld, } impl<'a, 'd> TimelineTabView<'a, 'd> { @@ -388,14 +375,12 @@ impl<'a, 'd> TimelineTabView<'a, 'd> { note_options: NoteOptions, txn: &'a Transaction, note_context: &'a mut NoteContext<'d>, - jobs: &'a mut JobsCacheOld, ) -> Self { Self { tab, note_options, txn, note_context, - jobs, } } @@ -473,19 +458,14 @@ impl<'a, 'd> TimelineTabView<'a, 'd> { } match entry { - NoteUnit::Single(_) => render_note( - ui, - self.note_context, - self.note_options, - self.jobs, - &underlying_note, - ), + NoteUnit::Single(_) => { + render_note(ui, self.note_context, self.note_options, &underlying_note) + } NoteUnit::Composite(composite) => match composite { CompositeUnit::Reaction(reaction_unit) => render_reaction_cluster( ui, self.note_context, self.note_options, - self.jobs, mute, self.txn, &underlying_note, @@ -495,7 +475,6 @@ impl<'a, 'd> TimelineTabView<'a, 'd> { ui, self.note_context, self.note_options, - self.jobs, mute, self.txn, &underlying_note, @@ -676,12 +655,11 @@ fn render_note( ui: &mut egui::Ui, note_context: &mut NoteContext, note_options: NoteOptions, - jobs: &mut JobsCacheOld, note: &Note, ) -> RenderEntryResponse { let mut action = None; notedeck_ui::padding(8.0, ui, |ui| { - let resp = NoteView::new(note_context, note, note_options, jobs).show(ui); + let resp = NoteView::new(note_context, note, note_options).show(ui); if let Some(note_action) = resp.action { action = Some(note_action); @@ -699,7 +677,6 @@ fn render_reaction_cluster( ui: &mut egui::Ui, note_context: &mut NoteContext, note_options: NoteOptions, - jobs: &mut JobsCacheOld, mute: &std::sync::Arc<Muted>, txn: &Transaction, underlying_note: &Note, @@ -729,7 +706,6 @@ fn render_reaction_cluster( ui, note_context, note_options | NoteOptions::Notification, - jobs, underlying_note, profiles_to_show, CompositeType::Reaction, @@ -742,7 +718,6 @@ fn render_composite_entry( ui: &mut egui::Ui, note_context: &mut NoteContext, mut note_options: NoteOptions, - jobs: &mut JobsCacheOld, underlying_note: &nostrdb::Note<'_>, profiles_to_show: Vec<ProfileEntry>, composite_type: CompositeType, @@ -793,6 +768,7 @@ fn render_composite_entry( profiles_to_show, &composite_type, note_context.img_cache, + note_context.jobs, note_options.contains(NoteOptions::Notification), ) }, @@ -867,7 +843,7 @@ fn render_composite_entry( ui.add_space(48.0); }; - NoteView::new(note_context, underlying_note, note_options, jobs).show(ui) + NoteView::new(note_context, underlying_note, note_options).show(ui) }) .inner; @@ -886,6 +862,7 @@ fn render_profiles( profiles_to_show: Vec<ProfileEntry>, composite_type: &CompositeType, img_cache: &mut notedeck::Images, + jobs: &notedeck::MediaJobSender, notification: bool, ) -> PfpsResponse { let mut action = None; @@ -932,7 +909,7 @@ fn render_profiles( profiling::scope!("actual rendering individual pfp"); let mut widget = - ProfilePic::from_profile_or_default(img_cache, entry.record.as_ref()) + ProfilePic::from_profile_or_default(img_cache, jobs, entry.record.as_ref()) .size(24.0) .sense(Sense::click()); let mut resp = ui.put(rect, &mut widget); @@ -941,7 +918,7 @@ fn render_profiles( if let Some(record) = entry.record.as_ref() { resp = resp.on_hover_ui_at_pointer(|ui| { ui.set_max_width(300.0); - ui.add(ProfilePreview::new(record, img_cache)); + ui.add(ProfilePreview::new(record, img_cache, jobs)); }); } @@ -977,7 +954,6 @@ fn render_repost_cluster( ui: &mut egui::Ui, note_context: &mut NoteContext, note_options: NoteOptions, - jobs: &mut JobsCacheOld, mute: &std::sync::Arc<Muted>, txn: &Transaction, underlying_note: &Note, @@ -997,7 +973,6 @@ fn render_repost_cluster( ui, note_context, note_options, - jobs, underlying_note, profiles_to_show, CompositeType::Repost, diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs @@ -8,7 +8,7 @@ use egui_wgpu::RenderState; use enostr::KeypairUnowned; use futures::StreamExt; use nostrdb::Transaction; -use notedeck::{AppAction, AppContext, AppResponse, JobsCacheOld}; +use notedeck::{AppAction, AppContext, AppResponse}; use std::collections::HashMap; use std::string::ToString; use std::sync::mpsc::{self, Receiver}; @@ -43,7 +43,6 @@ pub struct Dave { client: async_openai::Client<OpenAIConfig>, incoming_tokens: Option<Receiver<DaveApiResponse>>, model_config: ModelConfig, - jobs: JobsCacheOld, } /// Calculate an anonymous user_id from a keypair @@ -108,7 +107,6 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr input, model_config, chat: vec![], - jobs: JobsCacheOld::default(), } } @@ -189,11 +187,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr DaveResponse::default() */ - DaveUi::new(self.model_config.trial, &self.chat, &mut self.input).ui( - app_ctx, - &mut self.jobs, - ui, - ) + DaveUi::new(self.model_config.trial, &self.chat, &mut self.input).ui(app_ctx, ui) } fn handle_new_chat(&mut self) { diff --git a/crates/notedeck_dave/src/ui/dave.rs b/crates/notedeck_dave/src/ui/dave.rs @@ -5,7 +5,7 @@ use crate::{ use egui::{Align, Key, KeyboardShortcut, Layout, Modifiers}; use nostrdb::{Ndb, Transaction}; use notedeck::{ - tr, Accounts, AppContext, Images, JobsCacheOld, Localization, NoteAction, NoteContext, + tr, Accounts, AppContext, Images, Localization, MediaJobSender, NoteAction, NoteContext, }; use notedeck_ui::{app_images, icons::search_icon, NoteOptions, ProfilePic}; @@ -86,12 +86,7 @@ impl<'a> DaveUi<'a> { } /// The main render function. Call this to render Dave - pub fn ui( - &mut self, - app_ctx: &mut AppContext, - jobs: &mut JobsCacheOld, - ui: &mut egui::Ui, - ) -> DaveResponse { + pub fn ui(&mut self, app_ctx: &mut AppContext, ui: &mut egui::Ui) -> DaveResponse { let action = top_buttons_ui(app_ctx, ui); egui::Frame::NONE @@ -118,7 +113,7 @@ impl<'a> DaveUi<'a> { .show(ui, |ui| { Self::chat_frame(ui.ctx()) .show(ui, |ui| { - ui.vertical(|ui| self.render_chat(app_ctx, jobs, ui)).inner + ui.vertical(|ui| self.render_chat(app_ctx, ui)).inner }) .inner }) @@ -152,12 +147,7 @@ impl<'a> DaveUi<'a> { } /// Render a chat message (user, assistant, tool call/response, etc) - fn render_chat( - &self, - ctx: &mut AppContext, - jobs: &mut JobsCacheOld, - ui: &mut egui::Ui, - ) -> Option<NoteAction> { + fn render_chat(&self, ctx: &mut AppContext, ui: &mut egui::Ui) -> Option<NoteAction> { let mut action: Option<NoteAction> = None; for message in self.chat { let r = match message { @@ -182,7 +172,7 @@ impl<'a> DaveUi<'a> { // have a debug option to show this None } - Message::ToolCalls(toolcalls) => Self::tool_calls_ui(ctx, jobs, toolcalls, ui), + Message::ToolCalls(toolcalls) => Self::tool_calls_ui(ctx, toolcalls, ui), }; if r.is_some() { @@ -201,13 +191,18 @@ impl<'a> DaveUi<'a> { ui.add(search_icon(16.0, 16.0)); ui.add_space(8.0); - query_call_ui(ctx.img_cache, ctx.ndb, query_call, ui); + query_call_ui( + ctx.img_cache, + ctx.ndb, + query_call, + ctx.media_jobs.sender(), + ui, + ); } /// The ai has asked us to render some notes, so we do that here fn present_notes_ui( ctx: &mut AppContext, - jobs: &mut JobsCacheOld, call: &PresentNotesCall, ui: &mut egui::Ui, ) -> Option<NoteAction> { @@ -218,7 +213,7 @@ impl<'a> DaveUi<'a> { note_cache: ctx.note_cache, zaps: ctx.zaps, pool: ctx.pool, - job_pool: ctx.job_pool, + jobs: ctx.media_jobs.sender(), unknown_ids: ctx.unknown_ids, clipboard: ctx.clipboard, i18n: ctx.i18n, @@ -249,7 +244,6 @@ impl<'a> DaveUi<'a> { &mut note_context, &note, NoteOptions::default(), - jobs, ) .preview_style() .hide_media(true) @@ -272,7 +266,6 @@ impl<'a> DaveUi<'a> { fn tool_calls_ui( ctx: &mut AppContext, - jobs: &mut JobsCacheOld, toolcalls: &[ToolCall], ui: &mut egui::Ui, ) -> Option<NoteAction> { @@ -282,7 +275,7 @@ impl<'a> DaveUi<'a> { for call in toolcalls { match call.calls() { ToolCalls::PresentNotes(call) => { - let r = Self::present_notes_ui(ctx, jobs, call, ui); + let r = Self::present_notes_ui(ctx, call, ui); if r.is_some() { note_action = r; } @@ -399,7 +392,13 @@ fn new_chat_button() -> impl egui::Widget { } } -fn query_call_ui(cache: &mut notedeck::Images, ndb: &Ndb, query: &QueryCall, ui: &mut egui::Ui) { +fn query_call_ui( + cache: &mut notedeck::Images, + ndb: &Ndb, + query: &QueryCall, + jobs: &MediaJobSender, + ui: &mut egui::Ui, +) { ui.spacing_mut().item_spacing.x = 8.0; if let Some(pubkey) = query.author() { let txn = Transaction::new(ndb).unwrap(); @@ -409,6 +408,7 @@ fn query_call_ui(cache: &mut notedeck::Images, ndb: &Ndb, query: &QueryCall, ui: ui.add( &mut ProfilePic::from_profile_or_default( cache, + jobs, ndb.get_profile_by_pubkey(&txn, pubkey.bytes()) .ok() .as_ref(), @@ -489,7 +489,13 @@ fn top_buttons_ui(app_ctx: &mut AppContext, ui: &mut egui::Ui) -> Option<DaveAct let r = ui .put( rect, - &mut pfp_button(&txn, app_ctx.accounts, app_ctx.img_cache, app_ctx.ndb), + &mut pfp_button( + &txn, + app_ctx.accounts, + app_ctx.img_cache, + app_ctx.ndb, + app_ctx.media_jobs.sender(), + ), ) .on_hover_cursor(egui::CursorIcon::PointingHand); @@ -512,13 +518,14 @@ fn pfp_button<'me, 'a>( accounts: &Accounts, img_cache: &'me mut Images, ndb: &Ndb, + jobs: &'me MediaJobSender, ) -> ProfilePic<'me, 'a> { let account = accounts.get_selected_account(); let profile = ndb .get_profile_by_pubkey(txn, account.key.pubkey.bytes()) .ok(); - ProfilePic::from_profile_or_default(img_cache, profile.as_ref()) + ProfilePic::from_profile_or_default(img_cache, jobs, profile.as_ref()) .size(24.0) .sense(egui::Sense::click()) } diff --git a/crates/notedeck_ui/src/media/viewer.rs b/crates/notedeck_ui/src/media/viewer.rs @@ -1,7 +1,7 @@ use bitflags::bitflags; use egui::{emath::TSTransform, pos2, Color32, Rangef, Rect}; use notedeck::media::{AnimationMode, MediaInfo, ViewMediaInfo}; -use notedeck::{ImageType, Images}; +use notedeck::{ImageType, Images, MediaJobSender}; bitflags! { #[repr(transparent)] @@ -90,23 +90,33 @@ impl<'a> MediaViewer<'a> { self } - pub fn ui(&mut self, images: &mut Images, ui: &mut egui::Ui) -> egui::Response { + pub fn ui( + &mut self, + images: &mut Images, + jobs: &MediaJobSender, + ui: &mut egui::Ui, + ) -> egui::Response { if self.state.flags.contains(MediaViewerFlags::Fullscreen) { egui::Window::new("Media Viewer") .title_bar(false) .fixed_size(ui.ctx().screen_rect().size()) .fixed_pos(ui.ctx().screen_rect().min) .frame(egui::Frame::NONE) - .show(ui.ctx(), |ui| self.ui_content(images, ui)) + .show(ui.ctx(), |ui| self.ui_content(images, jobs, ui)) .unwrap() // SAFETY: we are always open .inner .unwrap() } else { - self.ui_content(images, ui) + self.ui_content(images, jobs, ui) } } - fn ui_content(&mut self, images: &mut Images, ui: &mut egui::Ui) -> egui::Response { + fn ui_content( + &mut self, + images: &mut Images, + jobs: &MediaJobSender, + ui: &mut egui::Ui, + ) -> egui::Response { let avail_rect = ui.available_rect_before_wrap(); let scene_rect = if let Some(scene_rect) = self.state.scene_rect { @@ -132,7 +142,7 @@ impl<'a> MediaViewer<'a> { let mut trans_rect = if transitioning { let clicked_img = &self.state.media_info.clicked_media(); let src_pos = &clicked_img.original_position; - let in_scene_pos = Self::first_image_rect(ui, clicked_img, images); + let in_scene_pos = Self::first_image_rect(ui, clicked_img, images, jobs); transition_scene_rect( &avail_rect, &zoom_range, @@ -161,7 +171,7 @@ impl<'a> MediaViewer<'a> { */ let resp = scene.show(ui, &mut trans_rect, |ui| { - Self::render_image_tiles(&self.state.media_info.medias, images, ui, open_amount); + Self::render_image_tiles(&self.state.media_info.medias, images, jobs, ui, open_amount); }); self.state.scene_rect = Some(trans_rect); @@ -174,9 +184,15 @@ impl<'a> MediaViewer<'a> { /// /// TODO(jb55): replace this with a "placed" variant once /// we have image layouts - fn first_image_rect(ui: &mut egui::Ui, media: &MediaInfo, images: &mut Images) -> Rect { + fn first_image_rect( + ui: &mut egui::Ui, + media: &MediaInfo, + images: &mut Images, + jobs: &MediaJobSender, + ) -> Rect { // fetch image texture - let Some(texture) = images.latest_texture_old( + let Some(texture) = images.latest_texture( + jobs, ui, &media.url, ImageType::Content(None), @@ -204,6 +220,7 @@ impl<'a> MediaViewer<'a> { fn render_image_tiles( infos: &[MediaInfo], images: &mut Images, + jobs: &MediaJobSender, ui: &mut egui::Ui, open_amount: f32, ) { @@ -213,7 +230,8 @@ impl<'a> MediaViewer<'a> { // fetch image texture // we want to continually redraw things in the gallery - let Some(texture) = images.latest_texture_old( + let Some(texture) = images.latest_texture( + jobs, ui, url, ImageType::Content(None), diff --git a/crates/notedeck_ui/src/mention.rs b/crates/notedeck_ui/src/mention.rs @@ -2,11 +2,12 @@ use crate::ProfilePreview; use egui::Sense; use enostr::Pubkey; use nostrdb::{Ndb, Transaction}; -use notedeck::{name::get_display_name, Images, NoteAction, NotedeckTextStyle}; +use notedeck::{name::get_display_name, Images, MediaJobSender, NoteAction, NotedeckTextStyle}; pub struct Mention<'a> { ndb: &'a Ndb, img_cache: &'a mut Images, + jobs: &'a MediaJobSender, txn: &'a Transaction, pk: &'a [u8; 32], selectable: bool, @@ -17,6 +18,7 @@ impl<'a> Mention<'a> { pub fn new( ndb: &'a Ndb, img_cache: &'a mut Images, + jobs: &'a MediaJobSender, txn: &'a Transaction, pk: &'a [u8; 32], ) -> Self { @@ -29,6 +31,7 @@ impl<'a> Mention<'a> { pk, selectable, size, + jobs, } } @@ -46,6 +49,7 @@ impl<'a> Mention<'a> { mention_ui( self.ndb, self.img_cache, + self.jobs, self.txn, self.pk, ui, @@ -60,6 +64,7 @@ impl<'a> Mention<'a> { fn mention_ui( ndb: &Ndb, img_cache: &mut Images, + jobs: &MediaJobSender, txn: &Transaction, pk: &[u8; 32], ui: &mut egui::Ui, @@ -99,7 +104,7 @@ fn mention_ui( if let Some(rec) = profile.as_ref() { resp.on_hover_ui_at_pointer(|ui| { ui.set_max_width(300.0); - ui.add(ProfilePreview::new(rec, img_cache)); + ui.add(ProfilePreview::new(rec, img_cache, jobs)); }); } diff --git a/crates/notedeck_ui/src/nip51_set.rs b/crates/notedeck_ui/src/nip51_set.rs @@ -4,8 +4,8 @@ use enostr::Pubkey; use hashbrown::{hash_map::RawEntryMut, HashMap}; use nostrdb::{Ndb, ProfileRecord, Transaction}; use notedeck::{ - fonts::get_font_size, get_profile_url, name::get_display_name, tr, Images, JobPool, - JobsCacheOld, Localization, Nip51Set, Nip51SetCache, NotedeckTextStyle, + fonts::get_font_size, get_profile_url, name::get_display_name, tr, Images, Localization, + MediaJobSender, Nip51Set, Nip51SetCache, NotedeckTextStyle, }; use crate::{ @@ -19,8 +19,7 @@ pub struct Nip51SetWidget<'a> { ndb: &'a Ndb, images: &'a mut Images, loc: &'a mut Localization, - job_pool: &'a mut JobPool, - jobs: &'a mut JobsCacheOld, + jobs: &'a MediaJobSender, flags: Nip51SetWidgetFlags, } @@ -53,8 +52,7 @@ impl<'a> Nip51SetWidget<'a> { ndb: &'a Ndb, loc: &'a mut Localization, images: &'a mut Images, - job_pool: &'a mut JobPool, - jobs: &'a mut JobsCacheOld, + jobs: &'a MediaJobSender, ) -> Self { Self { state, @@ -62,7 +60,6 @@ impl<'a> Nip51SetWidget<'a> { ndb, loc, images, - job_pool, jobs, flags: Nip51SetWidgetFlags::default(), } @@ -92,7 +89,6 @@ impl<'a> Nip51SetWidget<'a> { self.ui_state, self.ndb, self.images, - self.job_pool, self.jobs, self.loc, self.flags.contains(Nip51SetWidgetFlags::TRUST_IMAGES), @@ -157,8 +153,7 @@ fn render_pack( ui_state: &mut Nip51SetUiCache, ndb: &Ndb, images: &mut Images, - job_pool: &mut JobPool, - jobs: &mut JobsCacheOld, + jobs: &MediaJobSender, loc: &mut Localization, image_trusted: bool, ) -> Option<Nip51SetWidgetAction> { @@ -175,7 +170,6 @@ fn render_pack( let media_rect = render_media( ui, images, - job_pool, jobs, &media, image_trusted, @@ -250,7 +244,7 @@ fn render_pack( }; ui.separator(); - if render_profile_item(ui, images, m_profile.as_ref(), cur_state) { + if render_profile_item(ui, images, jobs, m_profile.as_ref(), cur_state) { resp = Some(Nip51SetWidgetAction::ViewProfile(*pk)); } } @@ -263,6 +257,7 @@ const PFP_SIZE: f32 = 32.0; fn render_profile_item( ui: &mut egui::Ui, images: &mut Images, + jobs: &MediaJobSender, profile: Option<&ProfileRecord>, checked: &mut bool, ) -> bool { @@ -294,7 +289,7 @@ fn render_profile_item( let _ = ui.allocate_new_ui(UiBuilder::new().max_rect(pfp_rect), |ui| { let pfp_resp = ui.add( - &mut ProfilePic::new(images, get_profile_url(profile)) + &mut ProfilePic::new(images, jobs, get_profile_url(profile)) .sense(Sense::click()) .size(PFP_SIZE), ); diff --git a/crates/notedeck_ui/src/note/contents.rs b/crates/notedeck_ui/src/note/contents.rs @@ -6,8 +6,8 @@ use crate::{ use egui::{Color32, Hyperlink, Label, RichText}; use nostrdb::{BlockType, Mention, Note, NoteKey, Transaction}; use notedeck::Localization; +use notedeck::RenderableMedia; use notedeck::{time_format, update_imeta_blurhashes, NoteCache, NoteContext, NotedeckTextStyle}; -use notedeck::{JobsCacheOld, RenderableMedia}; use tracing::warn; pub struct NoteContents<'a, 'd> { @@ -16,7 +16,6 @@ pub struct NoteContents<'a, 'd> { note: &'a Note<'a>, options: NoteOptions, pub action: Option<NoteAction>, - jobs: &'a mut JobsCacheOld, } impl<'a, 'd> NoteContents<'a, 'd> { @@ -26,7 +25,6 @@ impl<'a, 'd> NoteContents<'a, 'd> { txn: &'a Transaction, note: &'a Note, options: NoteOptions, - jobs: &'a mut JobsCacheOld, ) -> Self { NoteContents { note_context, @@ -34,21 +32,13 @@ impl<'a, 'd> NoteContents<'a, 'd> { note, options, action: None, - jobs, } } } impl egui::Widget for &mut NoteContents<'_, '_> { fn ui(self, ui: &mut egui::Ui) -> egui::Response { - let result = render_note_contents( - ui, - self.note_context, - self.txn, - self.note, - self.options, - self.jobs, - ); + let result = render_note_contents(ui, self.note_context, self.txn, self.note, self.options); self.action = result.action; result.response } @@ -83,7 +73,6 @@ pub fn render_note_preview( id: &[u8; 32], parent: NoteKey, note_options: NoteOptions, - jobs: &mut JobsCacheOld, ) -> NoteResponse { let note = if let Ok(note) = note_context.ndb.get_note_by_id(txn, id) { // TODO: support other preview kinds @@ -112,7 +101,7 @@ pub fn render_note_preview( */ }; - NoteView::new(note_context, &note, note_options, jobs) + NoteView::new(note_context, &note, note_options) .preview_style() .parent(parent) .show(ui) @@ -125,9 +114,8 @@ fn render_note_contents( txn: &Transaction, note: &Note, options: NoteOptions, - jobs: &mut JobsCacheOld, ) -> NoteResponse { - let response = render_undecorated_note_contents(ui, note_context, txn, note, options, jobs); + let response = render_undecorated_note_contents(ui, note_context, txn, note, options); ui.horizontal_wrapped(|ui| { note_bottom_metadata_ui( @@ -169,7 +157,6 @@ fn render_undecorated_note_contents<'a>( txn: &Transaction, note: &'a Note, options: NoteOptions, - jobs: &mut JobsCacheOld, ) -> NoteResponse { let note_key = note.key().expect("todo: implement non-db notes"); let selectable = options.contains(NoteOptions::SelectableText); @@ -208,6 +195,7 @@ fn render_undecorated_note_contents<'a>( let act = crate::Mention::new( note_context.ndb, note_context.img_cache, + note_context.jobs, txn, profile.pubkey(), ) @@ -223,6 +211,7 @@ fn render_undecorated_note_contents<'a>( let act = crate::Mention::new( note_context.ndb, note_context.img_cache, + note_context.jobs, txn, npub.pubkey(), ) @@ -355,7 +344,7 @@ fn render_undecorated_note_contents<'a>( }); let preview_note_action = inline_note.and_then(|(id, _)| { - render_note_preview(ui, note_context, txn, id, note_key, options, jobs) + render_note_preview(ui, note_context, txn, id, note_key, options) .action .map(|a| match a { NoteAction::Note { note_id, .. } => NoteAction::Note { @@ -375,8 +364,7 @@ fn render_undecorated_note_contents<'a>( media_action = image_carousel( ui, note_context.img_cache, - note_context.job_pool, - jobs, + note_context.jobs, &supported_medias, carousel_id, note_context.i18n, diff --git a/crates/notedeck_ui/src/note/media.rs b/crates/notedeck_ui/src/note/media.rs @@ -5,17 +5,19 @@ use egui::{ vec2, Button, Color32, Context, CornerRadius, FontId, Image, InnerResponse, Response, TextureHandle, Vec2, }; +use notedeck::media::latest::ObfuscatedTexture; use notedeck::{ - compute_blurhash, fonts::get_font_size, show_one_error_message, tr, BlurhashParams, - GifStateMap, Images, Job, JobIdOld, JobParams, JobPool, JobState, JobsCacheOld, Localization, - MediaAction, MediaCacheType, NotedeckTextStyle, ObfuscationType, PointDimensions, - RenderableMedia, TexturedImage, TexturesCacheOld, + compute_blurhash, BlurhashParams, Job, JobIdOld, JobParams, JobPool, JobState, JobsCacheOld, + MediaJobSender, ObfuscationType, PointDimensions, TexturedImage, TexturesCacheOld, +}; +use notedeck::{ + fonts::get_font_size, show_one_error_message, tr, Images, Localization, MediaAction, + MediaCacheType, NotedeckTextStyle, RenderableMedia, }; use crate::NoteOptions; -use notedeck::media::gif::ensure_latest_texture; -use notedeck::media::images::{fetch_no_pfp_promise, ImageType}; -use notedeck::media::AnimationMode; +use notedeck::media::images::ImageType; +use notedeck::media::{AnimationMode, MediaRenderState}; use notedeck::media::{MediaInfo, ViewMediaInfo}; use crate::{app_images, AnimationHelper, PulseAlpha}; @@ -30,8 +32,7 @@ pub enum MediaViewAction { pub fn image_carousel( ui: &mut egui::Ui, img_cache: &mut Images, - job_pool: &mut JobPool, - jobs: &mut JobsCacheOld, + jobs: &MediaJobSender, medias: &[RenderableMedia], carousel_id: egui::Id, i18n: &mut Localization, @@ -65,10 +66,10 @@ pub fn image_carousel( let media_response = render_media( ui, img_cache, - job_pool, jobs, media, - note_options.contains(NoteOptions::TrustMedia), + note_options.contains(NoteOptions::TrustMedia) + || img_cache.user_trusts_img(&media.url, media.media_type), i18n, size, if note_options.contains(NoteOptions::NoAnimations) { @@ -96,7 +97,6 @@ pub fn image_carousel( if let Some((i, media_action)) = media_action { action = media_action.into_media_action( - ui.ctx(), medias, media_infos, i, @@ -119,8 +119,7 @@ pub fn image_carousel( pub fn render_media( ui: &mut egui::Ui, img_cache: &mut Images, - job_pool: &mut JobPool, - jobs: &mut JobsCacheOld, + jobs: &MediaJobSender, media: &RenderableMedia, trusted_media: bool, i18n: &mut Localization, @@ -134,23 +133,6 @@ pub fn render_media( obfuscation_type: blur_type, } = media; - let cache = match media_type { - MediaCacheType::Image => &mut img_cache.static_imgs, - MediaCacheType::Gif => &mut img_cache.gifs, - }; - let media_state = get_content_media_render_state( - ui, - job_pool, - jobs, - trusted_media, - size, - &mut cache.textures_cache, - url, - *media_type, - &cache.cache_dir, - blur_type, - ); - let animation_mode = animation_mode.unwrap_or_else(|| { // if animations aren't disabled, we cap it at 24fps for gifs in carousels let fps = match media_type { @@ -159,17 +141,24 @@ pub fn render_media( }; AnimationMode::Continuous { fps } }); + let media_state = if trusted_media { + img_cache.trusted_texture_loader().latest( + jobs, + ui, + url, + *media_type, + ImageType::Content(None), + animation_mode, + blur_type, + size, + ) + } else { + img_cache + .untrusted_texture_loader() + .latest(jobs, ui, url, blur_type, size) + }; - render_media_internal( - ui, - &mut img_cache.gif_states, - media_state, - url, - size, - i18n, - scale_flags, - animation_mode, - ) + render_media_internal(ui, media_state, url, size, i18n, scale_flags) } pub enum MediaUIAction { @@ -182,7 +171,6 @@ pub enum MediaUIAction { impl MediaUIAction { pub fn into_media_action( self, - ctx: &egui::Context, medias: &[RenderableMedia], responses: Vec<MediaInfo>, selected: usize, @@ -203,17 +191,9 @@ impl MediaUIAction { let url = &medias[selected].url; let cache = img_cache.get_cache(medias[selected].media_type); let cache_type = cache.cache_type; - let no_pfp_promise = notedeck::media::images::fetch_img( - &cache.cache_dir, - ctx, - url, - img_type, - cache_type, - ); Some(MediaAction::FetchImage { url: url.to_owned(), cache_type, - no_pfp_promise, }) } @@ -227,7 +207,6 @@ impl MediaUIAction { Some(MediaAction::FetchImage { url: medias[selected].url.to_owned(), cache_type, - no_pfp_promise: fetch_no_pfp_promise(ctx, cache), }) } MediaUIAction::DoneLoading => Some(MediaAction::DoneLoading { @@ -366,65 +345,50 @@ fn copy_link(i18n: &mut Localization, url: &str, img_resp: &Response) { #[allow(clippy::too_many_arguments)] fn render_media_internal( ui: &mut egui::Ui, - gifs: &mut GifStateMap, - render_state: MediaRenderStateOld, + render_state: MediaRenderState, url: &str, size: egui::Vec2, i18n: &mut Localization, scale_flags: ScaledTextureFlags, - animation_mode: AnimationMode, ) -> egui::InnerResponse<Option<MediaUIAction>> { match render_state { - MediaRenderStateOld::ActualImage(image) => { - let resp = render_success_media( - ui, - url, - image, - gifs, - size, - i18n, - scale_flags, - animation_mode, - ); + MediaRenderState::ActualImage(image) => { + let resp = render_success_media(ui, url, image, size, i18n, scale_flags); if resp.clicked() { egui::InnerResponse::new(Some(MediaUIAction::Clicked), resp) } else { egui::InnerResponse::new(None, resp) } } - MediaRenderStateOld::Transitioning { image, obfuscation } => match obfuscation { - ObfuscatedTextureOld::Blur(texture) => { - let resp = render_blur_transition( - ui, - url, - size, - texture, - image.get_first_texture(), - scale_flags, - ); + MediaRenderState::Transitioning { + image: img_tex, + obfuscation, + } => match obfuscation { + ObfuscatedTexture::Blur(blur_tex) => { + let resp = render_blur_transition(ui, url, size, blur_tex, img_tex, scale_flags); if resp.inner { egui::InnerResponse::new(Some(MediaUIAction::DoneLoading), resp.response) } else { egui::InnerResponse::new(None, resp.response) } } - ObfuscatedTextureOld::Default => { - let scaled = ScaledTexture::new(image.get_first_texture(), size, scale_flags); + ObfuscatedTexture::Default => { + let scaled = ScaledTexture::new(img_tex, size, scale_flags); let resp = ui.add(scaled.get_image()); egui::InnerResponse::new(Some(MediaUIAction::DoneLoading), resp) } }, - MediaRenderStateOld::Error(e) => { + MediaRenderState::Error(e) => { let response = ui.allocate_response(size, egui::Sense::hover()); show_one_error_message(ui, &format!("Could not render media {url}: {e}")); egui::InnerResponse::new(Some(MediaUIAction::Error), response) } - MediaRenderStateOld::Shimmering(obfuscated_texture) => match obfuscated_texture { - ObfuscatedTextureOld::Blur(texture_handle) => egui::InnerResponse::new( + MediaRenderState::Shimmering(obfuscated_texture) => match obfuscated_texture { + ObfuscatedTexture::Blur(texture_handle) => egui::InnerResponse::new( None, shimmer_blurhash(texture_handle, ui, url, size, scale_flags), ), - ObfuscatedTextureOld::Default => { + ObfuscatedTexture::Default => { let shimmer = true; egui::InnerResponse::new( None, @@ -438,15 +402,15 @@ fn render_media_internal( ) } }, - MediaRenderStateOld::Obfuscated(obfuscated_texture) => { + MediaRenderState::Obfuscated(obfuscated_texture) => { let resp = match obfuscated_texture { - ObfuscatedTextureOld::Blur(texture_handle) => { + ObfuscatedTexture::Blur(texture_handle) => { let scaled = ScaledTexture::new(texture_handle, size, scale_flags); let resp = ui.add(scaled.get_image()); render_blur_text(ui, i18n, url, resp.rect) } - ObfuscatedTextureOld::Default => render_default_blur( + ObfuscatedTexture::Default => render_default_blur( ui, i18n, size, @@ -633,16 +597,12 @@ pub(crate) fn find_renderable_media<'a>( fn render_success_media( ui: &mut egui::Ui, url: &str, - tex: &mut TexturedImage, - gifs: &mut GifStateMap, + tex: &TextureHandle, size: Vec2, i18n: &mut Localization, scale_flags: ScaledTextureFlags, - animation_mode: AnimationMode, ) -> Response { - let texture = ensure_latest_texture(ui, url, gifs, tex, animation_mode); - - let scaled = ScaledTexture::new(&texture, size, scale_flags); + let scaled = ScaledTexture::new(tex, size, scale_flags); let img_resp = ui.add(Button::image(scaled.get_image()).frame(false)); diff --git a/crates/notedeck_ui/src/note/mod.rs b/crates/notedeck_ui/src/note/mod.rs @@ -9,15 +9,14 @@ use crate::{widgets::x_button, ProfilePic, ProfilePreview, PulseAlpha, Username} pub use contents::{render_note_preview, NoteContents}; pub use context::NoteContextButton; -use notedeck::get_current_wallet; use notedeck::note::{reaction_sent_id, ZapTargetAmount}; use notedeck::ui::is_narrow; use notedeck::Accounts; use notedeck::GlobalWallet; use notedeck::Images; -use notedeck::JobsCacheOld; use notedeck::Localization; use notedeck::MediaAction; +use notedeck::{get_current_wallet, MediaJobSender}; pub use options::NoteOptions; pub use reply_description::reply_desc; @@ -35,7 +34,6 @@ pub struct NoteView<'a, 'd> { parent: Option<NoteKey>, note: &'a nostrdb::Note<'a>, flags: NoteOptions, - jobs: &'a mut JobsCacheOld, } pub struct NoteResponse { @@ -83,7 +81,6 @@ impl<'a, 'd> NoteView<'a, 'd> { note_context: &'a mut NoteContext<'d>, note: &'a nostrdb::Note<'a>, flags: NoteOptions, - jobs: &'a mut JobsCacheOld, ) -> Self { let parent: Option<NoteKey> = None; @@ -92,7 +89,6 @@ impl<'a, 'd> NoteView<'a, 'd> { parent, note, flags, - jobs, } } @@ -256,7 +252,6 @@ impl<'a, 'd> NoteView<'a, 'd> { txn, self.note, self.flags, - self.jobs, )); //}); }) @@ -291,13 +286,19 @@ impl<'a, 'd> NoteView<'a, 'd> { Some(pic) => show_actual_pfp( ui, self.note_context.img_cache, + self.note_context.jobs, pic, pfp_size, note_key, profile, ), - None => show_fallback_pfp(ui, self.note_context.img_cache, pfp_size), + None => show_fallback_pfp( + ui, + self.note_context.img_cache, + self.note_context.jobs, + pfp_size, + ), } } @@ -423,22 +424,15 @@ impl<'a, 'd> NoteView<'a, 'd> { ui.horizontal_wrapped(|ui| { ui.spacing_mut().item_spacing.x = 0.0; - note_action = reply_desc( - ui, - txn, - &note_reply, - self.note_context, - self.flags, - self.jobs, - ) - .or(note_action.take()); + note_action = + reply_desc(ui, txn, &note_reply, self.note_context, self.flags) + .or(note_action.take()); }); }); }); } - let mut contents = - NoteContents::new(self.note_context, txn, self.note, self.flags, self.jobs); + let mut contents = NoteContents::new(self.note_context, txn, self.note, self.flags); ui.add(&mut contents); @@ -535,20 +529,13 @@ impl<'a, 'd> NoteView<'a, 'd> { return; } - note_action = reply_desc( - ui, - txn, - &note_reply, - self.note_context, - self.flags, - self.jobs, - ) - .or(note_action.take()); + note_action = + reply_desc(ui, txn, &note_reply, self.note_context, self.flags) + .or(note_action.take()); }); } - let mut contents = - NoteContents::new(self.note_context, txn, self.note, self.flags, self.jobs); + let mut contents = NoteContents::new(self.note_context, txn, self.note, self.flags); ui.add(&mut contents); note_action = contents.action.or(note_action); @@ -713,6 +700,7 @@ impl PfpResponse { fn show_actual_pfp( ui: &mut egui::Ui, images: &mut Images, + jobs: &MediaJobSender, pic: &str, pfp_size: i8, note_key: NoteKey, @@ -732,13 +720,13 @@ fn show_actual_pfp( let resp = resp.on_hover_cursor(egui::CursorIcon::PointingHand); - let mut pfp = ProfilePic::new(images, pic).size(size); + let mut pfp = ProfilePic::new(images, jobs, pic).size(size); let pfp_resp = ui.put(rect, &mut pfp); let action = pfp.action; pfp_resp.on_hover_ui_at_pointer(|ui| { ui.set_max_width(300.0); - ui.add(ProfilePreview::new(profile.as_ref().unwrap(), images)); + ui.add(ProfilePreview::new(profile.as_ref().unwrap(), images, jobs)); }); PfpResponse { @@ -748,14 +736,20 @@ fn show_actual_pfp( } } -fn show_fallback_pfp(ui: &mut egui::Ui, images: &mut Images, pfp_size: i8) -> PfpResponse { +fn show_fallback_pfp( + ui: &mut egui::Ui, + images: &mut Images, + jobs: &MediaJobSender, + pfp_size: i8, +) -> PfpResponse { let sense = Sense::click(); // This has to match the expand size from the above case to // prevent bounciness let size = (pfp_size + NoteView::expand_size()) as f32; let (rect, _response) = ui.allocate_exact_size(egui::vec2(size, size), sense); - let mut pfp = ProfilePic::new(images, notedeck::profile::no_pfp_url()).size(pfp_size as f32); + let mut pfp = + ProfilePic::new(images, jobs, notedeck::profile::no_pfp_url()).size(pfp_size as f32); let response = ui.put(rect, &mut pfp).interact(sense); PfpResponse { diff --git a/crates/notedeck_ui/src/note/reply_description.rs b/crates/notedeck_ui/src/note/reply_description.rs @@ -3,7 +3,7 @@ use nostrdb::{NoteReply, Transaction}; use super::NoteOptions; use crate::{note::NoteView, Mention}; -use notedeck::{tr, JobsCacheOld, NoteAction, NoteContext}; +use notedeck::{tr, NoteAction, NoteContext}; // Rich text segment types for internationalized rendering #[derive(Debug, Clone)] @@ -106,7 +106,6 @@ fn render_text_segments( txn: &Transaction, note_context: &mut NoteContext, note_options: NoteOptions, - jobs: &mut JobsCacheOld, size: f32, selectable: bool, ) -> Option<NoteAction> { @@ -126,6 +125,7 @@ fn render_text_segments( let action = Mention::new( note_context.ndb, note_context.img_cache, + note_context.jobs, txn, pubkey.expect("expected pubkey"), ) @@ -163,7 +163,7 @@ fn render_text_segments( if r.hovered() { r.on_hover_ui_at_pointer(|ui| { ui.set_max_width(400.0); - NoteView::new(note_context, &note, note_options, jobs) + NoteView::new(note_context, &note, note_options) .actionbar(false) .wide(true) .show(ui); @@ -197,7 +197,7 @@ fn render_text_segments( if r.hovered() { r.on_hover_ui_at_pointer(|ui| { ui.set_max_width(400.0); - NoteView::new(note_context, &note, note_options, jobs) + NoteView::new(note_context, &note, note_options) .actionbar(false) .wide(true) .show(ui); @@ -219,7 +219,6 @@ pub fn reply_desc( note_reply: &NoteReply, note_context: &mut NoteContext, note_options: NoteOptions, - jobs: &mut JobsCacheOld, ) -> Option<NoteAction> { let size = 10.0; let selectable = false; @@ -242,7 +241,6 @@ pub fn reply_desc( txn, note_context, note_options, - jobs, size, selectable, ); @@ -271,7 +269,6 @@ pub fn reply_desc( txn, note_context, note_options, - jobs, size, selectable, ) @@ -294,7 +291,6 @@ pub fn reply_desc( txn, note_context, note_options, - jobs, size, selectable, ) @@ -324,7 +320,6 @@ pub fn reply_desc( txn, note_context, note_options, - jobs, size, selectable, ) @@ -345,7 +340,6 @@ pub fn reply_desc( txn, note_context, note_options, - jobs, size, selectable, ) @@ -366,7 +360,6 @@ pub fn reply_desc( txn, note_context, note_options, - jobs, size, selectable, ) diff --git a/crates/notedeck_ui/src/profile/picture.rs b/crates/notedeck_ui/src/profile/picture.rs @@ -1,14 +1,15 @@ use egui::{vec2, InnerResponse, Sense, Stroke, TextureHandle}; -use notedeck::get_render_state; -use notedeck::media::gif::ensure_latest_texture; -use notedeck::media::images::{fetch_no_pfp_promise, ImageType}; +use notedeck::media::images::ImageType; +use notedeck::media::latest::LatestImageTex; use notedeck::media::AnimationMode; use notedeck::MediaAction; +use notedeck::MediaJobSender; use notedeck::{show_one_error_message, supported_mime_hosted_at_url, Images}; pub struct ProfilePic<'cache, 'url> { cache: &'cache mut Images, + jobs: &'cache MediaJobSender, url: &'url str, size: f32, sense: Sense, @@ -22,6 +23,7 @@ impl egui::Widget for &mut ProfilePic<'_, '_> { fn ui(self, ui: &mut egui::Ui) -> egui::Response { let inner = render_pfp( ui, + self.jobs, self.cache, self.url, self.size, @@ -37,12 +39,13 @@ impl egui::Widget for &mut ProfilePic<'_, '_> { } impl<'cache, 'url> ProfilePic<'cache, 'url> { - pub fn new(cache: &'cache mut Images, url: &'url str) -> Self { + pub fn new(cache: &'cache mut Images, jobs: &'cache MediaJobSender, url: &'url str) -> Self { let size = Self::default_size() as f32; let sense = Sense::hover(); ProfilePic { cache, + jobs, sense, url, size, @@ -68,17 +71,19 @@ impl<'cache, 'url> ProfilePic<'cache, 'url> { pub fn from_profile( cache: &'cache mut Images, + jobs: &'cache MediaJobSender, profile: &nostrdb::ProfileRecord<'url>, ) -> Option<Self> { profile .record() .profile() .and_then(|p| p.picture()) - .map(|url| ProfilePic::new(cache, url)) + .map(|url| ProfilePic::new(cache, jobs, url)) } pub fn from_profile_or_default( cache: &'cache mut Images, + jobs: &'cache MediaJobSender, profile: Option<&nostrdb::ProfileRecord<'url>>, ) -> Self { let url = profile @@ -87,7 +92,7 @@ impl<'cache, 'url> ProfilePic<'cache, 'url> { .and_then(|p| p.picture()) .unwrap_or(notedeck::profile::no_pfp_url()); - ProfilePic::new(cache, url) + ProfilePic::new(cache, jobs, url) } #[inline] @@ -119,8 +124,10 @@ impl<'cache, 'url> ProfilePic<'cache, 'url> { } #[profiling::function] +#[allow(clippy::too_many_arguments)] fn render_pfp( ui: &mut egui::Ui, + jobs: &MediaJobSender, img_cache: &mut Images, url: &str, ui_size: f32, @@ -134,38 +141,29 @@ fn render_pfp( let cache_type = supported_mime_hosted_at_url(&mut img_cache.urls, url) .unwrap_or(notedeck::MediaCacheType::Image); - let cur_state = get_render_state( + let cur_state = img_cache.no_img_loading_tex_loader().latest_state( + jobs, ui.ctx(), - img_cache, - cache_type, url, + cache_type, ImageType::Profile(img_size), + animation_mode, ); - match cur_state.texture_state { - notedeck::TextureStateOld::Pending => { + match cur_state { + LatestImageTex::Pending => { profiling::scope!("Render pending"); egui::InnerResponse::new(None, paint_circle(ui, ui_size, border, sense)) } - notedeck::TextureStateOld::Error(e) => { + LatestImageTex::Error(e) => { profiling::scope!("Render error"); let r = paint_circle(ui, ui_size, border, sense); show_one_error_message(ui, &format!("Failed to fetch profile at url {url}: {e}")); - egui::InnerResponse::new( - Some(MediaAction::FetchImage { - url: url.to_owned(), - cache_type, - no_pfp_promise: fetch_no_pfp_promise(ui.ctx(), img_cache.get_cache(cache_type)), - }), - r, - ) + egui::InnerResponse::new(None, r) } - notedeck::TextureStateOld::Loaded(textured_image) => { + LatestImageTex::Loaded(texture_handle) => { profiling::scope!("Render loaded"); - let texture_handle = - ensure_latest_texture(ui, url, cur_state.gifs, textured_image, animation_mode); - - egui::InnerResponse::new(None, pfp_image(ui, &texture_handle, ui_size, border, sense)) + egui::InnerResponse::new(None, pfp_image(ui, texture_handle, ui_size, border, sense)) } } } diff --git a/crates/notedeck_ui/src/profile/preview.rs b/crates/notedeck_ui/src/profile/preview.rs @@ -4,7 +4,8 @@ use egui_extras::Size; use nostrdb::ProfileRecord; use notedeck::{ - name::get_display_name, profile::get_profile_url, tr, Images, Localization, NotedeckTextStyle, + name::get_display_name, profile::get_profile_url, tr, Images, Localization, MediaJobSender, + NotedeckTextStyle, }; use super::{about_section_widget, banner, display_name_widget}; @@ -12,14 +13,20 @@ use super::{about_section_widget, banner, display_name_widget}; pub struct ProfilePreview<'a, 'cache> { profile: &'a ProfileRecord<'a>, cache: &'cache mut Images, + jobs: &'cache MediaJobSender, banner_height: Size, } impl<'a, 'cache> ProfilePreview<'a, 'cache> { - pub fn new(profile: &'a ProfileRecord<'a>, cache: &'cache mut Images) -> Self { + pub fn new( + profile: &'a ProfileRecord<'a>, + cache: &'cache mut Images, + jobs: &'cache MediaJobSender, + ) -> Self { let banner_height = Size::exact(80.0); ProfilePreview { profile, + jobs, cache, banner_height, } @@ -40,7 +47,7 @@ impl<'a, 'cache> ProfilePreview<'a, 'cache> { ui.put( pfp_rect, - &mut ProfilePic::new(self.cache, get_profile_url(Some(self.profile))) + &mut ProfilePic::new(self.cache, self.jobs, get_profile_url(Some(self.profile))) .size(size) .border(ProfilePic::border_stroke(ui)), ); @@ -72,6 +79,7 @@ pub struct SimpleProfilePreview<'a, 'cache> { profile: Option<&'a ProfileRecord<'a>>, pub i18n: &'cache mut Localization, cache: &'cache mut Images, + jobs: &'cache MediaJobSender, is_nsec: bool, } @@ -79,6 +87,7 @@ impl<'a, 'cache> SimpleProfilePreview<'a, 'cache> { pub fn new( profile: Option<&'a ProfileRecord<'a>>, cache: &'cache mut Images, + jobs: &'cache MediaJobSender, i18n: &'cache mut Localization, is_nsec: bool, ) -> Self { @@ -87,6 +96,7 @@ impl<'a, 'cache> SimpleProfilePreview<'a, 'cache> { cache, is_nsec, i18n, + jobs, } } } @@ -95,7 +105,10 @@ impl egui::Widget for SimpleProfilePreview<'_, '_> { fn ui(self, ui: &mut egui::Ui) -> egui::Response { Frame::new() .show(ui, |ui| { - ui.add(&mut ProfilePic::new(self.cache, get_profile_url(self.profile)).size(48.0)); + ui.add( + &mut ProfilePic::new(self.cache, self.jobs, get_profile_url(self.profile)) + .size(48.0), + ); ui.vertical(|ui| { ui.add(display_name_widget(&get_display_name(self.profile), true)); if !self.is_nsec {