feat: add new first screen design #7
@@ -37,6 +37,11 @@ pub fn init(db_path: impl AsRef<Path>, cx: &mut App) -> Entity<Backend> {
|
|||||||
|
|
||||||
ProfileStore::set_global(cx.new(ProfileStore::new), cx);
|
ProfileStore::set_global(cx.new(ProfileStore::new), cx);
|
||||||
|
|
||||||
|
// Start the explore list from the local database before the first
|
||||||
|
// window opens; relay syncs continue in the background, so the list
|
||||||
|
// never waits for them.
|
||||||
|
RepoListStore::set_global(cx.new(|cx| RepoListStore::new(None, cx)), cx);
|
||||||
|
|
||||||
// The clone cache is only meaningful on native platforms; the wasm
|
// The clone cache is only meaningful on native platforms; the wasm
|
||||||
// build registers an empty store so `GitStore::global` still works.
|
// build registers an empty store so `GitStore::global` still works.
|
||||||
GitStore::set_global(PathBuf::new(), cx);
|
GitStore::set_global(PathBuf::new(), cx);
|
||||||
@@ -54,6 +59,11 @@ pub fn init(cx: &mut App) -> Entity<Backend> {
|
|||||||
|
|
||||||
ProfileStore::set_global(cx.new(ProfileStore::new), cx);
|
ProfileStore::set_global(cx.new(ProfileStore::new), cx);
|
||||||
|
|
||||||
|
// Start the explore list from the local database before the first
|
||||||
|
// window opens; relay syncs continue in the background, so the list
|
||||||
|
// never waits for them.
|
||||||
|
RepoListStore::set_global(cx.new(|cx| RepoListStore::new(None, cx)), cx);
|
||||||
|
|
||||||
GitStore::set_global(PathBuf::new(), cx);
|
GitStore::set_global(PathBuf::new(), cx);
|
||||||
|
|
||||||
entity
|
entity
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use gpui::{AppContext, Context, Subscription, Task};
|
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use signed_core::{Announcement, Deletions, RepoAddr, filters, repo_addr};
|
use signed_core::{Announcement, Deletions, RepoAddr, filters, repo_addr};
|
||||||
|
|
||||||
@@ -16,7 +16,15 @@ const REFRESH_DEBOUNCE: Duration = Duration::from_millis(300);
|
|||||||
/// How far back activity events count toward a repository's last activity.
|
/// How far back activity events count toward a repository's last activity.
|
||||||
const ACTIVITY_WINDOW: Duration = Duration::from_secs(90 * 86_400);
|
const ACTIVITY_WINDOW: Duration = Duration::from_secs(90 * 86_400);
|
||||||
|
|
||||||
|
struct GlobalRepoListStore(Entity<RepoListStore>);
|
||||||
|
|
||||||
|
impl Global for GlobalRepoListStore {}
|
||||||
|
|
||||||
/// Store listing repository announcements (global discovery or per-author).
|
/// Store listing repository announcements (global discovery or per-author).
|
||||||
|
///
|
||||||
|
/// The all-repos store (`author: None`) is created at startup by
|
||||||
|
/// [`crate::init`] and installed as a global, so the explore panel renders
|
||||||
|
/// what's in the local database without waiting for relays.
|
||||||
pub struct RepoListStore {
|
pub struct RepoListStore {
|
||||||
/// Shared so views can clone the list per frame without a deep copy.
|
/// Shared so views can clone the list per frame without a deep copy.
|
||||||
pub announcements: Arc<Vec<Announcement>>,
|
pub announcements: Arc<Vec<Announcement>>,
|
||||||
@@ -33,6 +41,16 @@ pub struct RepoListStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RepoListStore {
|
impl RepoListStore {
|
||||||
|
/// Retrieve the global explore store (all announcements, created at
|
||||||
|
/// startup by [`crate::init`]).
|
||||||
|
pub fn global(cx: &App) -> Entity<Self> {
|
||||||
|
cx.global::<GlobalRepoListStore>().0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_global(entity: Entity<Self>, cx: &mut App) {
|
||||||
|
cx.set_global(GlobalRepoListStore(entity));
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a store. If `author` is `None`, all announcements are listed.
|
/// Create a store. If `author` is `None`, all announcements are listed.
|
||||||
pub fn new(author: Option<PublicKey>, cx: &mut Context<Self>) -> Self {
|
pub fn new(author: Option<PublicKey>, cx: &mut Context<Self>) -> Self {
|
||||||
let backend = Backend::global(cx);
|
let backend = Backend::global(cx);
|
||||||
@@ -79,7 +97,9 @@ impl RepoListStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
store.subscribe_remote(cx);
|
store.subscribe_remote(cx);
|
||||||
store.refresh(cx);
|
// Query the local database right away; the list never waits for the
|
||||||
|
// relay syncs started above to finish.
|
||||||
|
store.refresh_initial(cx);
|
||||||
store
|
store
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +127,18 @@ impl RepoListStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One-shot initial load: query the local database immediately (no
|
||||||
|
/// debounce), so stored announcements appear as soon as the app opens.
|
||||||
|
/// Only called from [`Self::new`], before any refresh can be pending.
|
||||||
|
fn refresh_initial(&mut self, cx: &mut Context<Self>) {
|
||||||
|
debug_assert!(!self.debouncing);
|
||||||
|
if self.refreshing {
|
||||||
|
self.refresh_dirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.run_refresh(cx);
|
||||||
|
}
|
||||||
|
|
||||||
/// Re-query the local database. Latest announcement per repository wins.
|
/// Re-query the local database. Latest announcement per repository wins.
|
||||||
///
|
///
|
||||||
/// Debounced: a short delay collapses bursts of requests (e.g. sync
|
/// Debounced: a short delay collapses bursts of requests (e.g. sync
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ impl RepoListView {
|
|||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let store = cx.new(|cx| RepoListStore::new(None, cx));
|
// Created at startup by `signed_state::init`: the local database has
|
||||||
|
// already been queried, so stored repositories appear immediately
|
||||||
|
// without waiting for relays (which only add to the list).
|
||||||
|
let store = RepoListStore::global(cx);
|
||||||
|
|
||||||
let subscription = cx.observe(&store, |this, store, cx| {
|
let subscription = cx.observe(&store, |this, store, cx| {
|
||||||
// Each virtual list row holds `COLUMNS` repo cards.
|
// Each virtual list row holds `COLUMNS` repo cards.
|
||||||
@@ -48,12 +51,17 @@ impl RepoListView {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The store may already hold announcements (it loaded before the
|
||||||
|
// panel opened), so seed the row sizes right away.
|
||||||
|
let rows = store.read(cx).announcements.len().div_ceil(COLUMNS);
|
||||||
|
let item_sizes = Rc::new(vec![size(px(0.), px(CARD_HEIGHT)); rows]);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
store,
|
store,
|
||||||
dock_area,
|
dock_area,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
scroll_handle: VirtualListScrollHandle::new(),
|
scroll_handle: VirtualListScrollHandle::new(),
|
||||||
item_sizes: Rc::new(vec![]),
|
item_sizes,
|
||||||
_subscription: subscription,
|
_subscription: subscription,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,7 +238,7 @@ impl Render for RepoListView {
|
|||||||
div()
|
div()
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.text_color(cx.theme().muted_foreground)
|
.text_color(cx.theme().muted_foreground)
|
||||||
.child("No repositories found. Waiting for relays..."),
|
.child("No repositories found yet."),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user