chore: remove unnecessary optimization (#20)
Reviewed-on: #20
This commit was merged in pull request #20.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Error;
|
||||
use dock::{BasePanel, DockArea, Panel, PanelEvent};
|
||||
@@ -21,7 +20,6 @@ use utils::relative_time;
|
||||
|
||||
use super::{RepoItem, open_repo_item};
|
||||
|
||||
const REFRESH_DEBOUNCE: Duration = Duration::from_millis(300);
|
||||
const LIST_OVERDRAW: Pixels = px(400.);
|
||||
const MAX_SUB_ACTIVITIES: usize = 5;
|
||||
|
||||
@@ -179,7 +177,6 @@ impl InboxView {
|
||||
}
|
||||
|
||||
fn refresh_initial(&mut self, cx: &mut Context<Self>) {
|
||||
debug_assert!(!self.refresh.debouncing());
|
||||
if self.refresh.running() {
|
||||
self.refresh.request();
|
||||
return;
|
||||
@@ -196,10 +193,7 @@ impl InboxView {
|
||||
return;
|
||||
}
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
cx.background_executor().timer(REFRESH_DEBOUNCE).await;
|
||||
this.update(cx, |this, cx| this.run_refresh(cx))
|
||||
}));
|
||||
self.run_refresh(cx);
|
||||
}
|
||||
|
||||
fn run_refresh(&mut self, cx: &mut Context<Self>) {
|
||||
@@ -408,7 +402,7 @@ impl InboxView {
|
||||
};
|
||||
|
||||
let root = item.root;
|
||||
let kind = item.root_kind;
|
||||
let kind = item.root_event.as_ref().map(|event| event.kind);
|
||||
let address = section.address.clone();
|
||||
let first = entry_ix == 0;
|
||||
let last = entry_ix + 1 == section.entries.len();
|
||||
|
||||
@@ -24,6 +24,7 @@ use utils::relative_time;
|
||||
pub(super) mod detail;
|
||||
|
||||
use self::detail::IssueDetailView;
|
||||
use super::status_list::{StatusCounts, filter_by_status};
|
||||
|
||||
const ISSUE_ROW_HEIGHT: f32 = 73.;
|
||||
|
||||
@@ -52,7 +53,7 @@ pub struct IssuesView {
|
||||
filter: IssueFilter,
|
||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||
visible_issues: Vec<usize>,
|
||||
counts: (usize, usize, usize),
|
||||
counts: StatusCounts,
|
||||
// A filter change notifies even when the visible rows are unchanged,
|
||||
// e.g. switching between two empty filters.
|
||||
synced_filter: IssueFilter,
|
||||
@@ -85,7 +86,7 @@ impl IssuesView {
|
||||
filter: IssueFilter::Open,
|
||||
item_sizes: Rc::new(Vec::new()),
|
||||
visible_issues: Vec::new(),
|
||||
counts: (0, 0, 0),
|
||||
counts: StatusCounts::default(),
|
||||
synced_filter: IssueFilter::Open,
|
||||
scroll_handle: VirtualListScrollHandle::new(),
|
||||
_subscription: subscription,
|
||||
@@ -97,25 +98,11 @@ impl IssuesView {
|
||||
|
||||
let (visible_issues, counts) = {
|
||||
let store = self.store.read(cx);
|
||||
let mut counts = (0usize, 0usize, 0usize);
|
||||
|
||||
let visible_issues: Vec<usize> = store
|
||||
.issues
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(ix, issue)| {
|
||||
let status = store.status_of(issue);
|
||||
counts.0 += 1;
|
||||
match status {
|
||||
RepoStatus::Open => counts.1 += 1,
|
||||
RepoStatus::Closed => counts.2 += 1,
|
||||
RepoStatus::Draft | RepoStatus::Applied => {}
|
||||
}
|
||||
filter.matches(status).then_some(ix)
|
||||
})
|
||||
.collect();
|
||||
|
||||
(visible_issues, counts)
|
||||
filter_by_status(
|
||||
&store.issues,
|
||||
|issue| store.status_of(issue),
|
||||
|status| filter.matches(status),
|
||||
)
|
||||
};
|
||||
|
||||
let filter_changed = self.synced_filter != filter;
|
||||
@@ -218,7 +205,7 @@ impl IssuesView {
|
||||
}
|
||||
|
||||
fn render_header(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
let (total, open, closed) = self.counts;
|
||||
let counts = self.counts;
|
||||
|
||||
h_flex()
|
||||
.px_4()
|
||||
@@ -234,7 +221,7 @@ impl IssuesView {
|
||||
.child(
|
||||
SegmentButton::new("all", "All")
|
||||
.icon(Icon::new(CustomIconName::GitIssueDone))
|
||||
.count(total)
|
||||
.count(counts.total)
|
||||
.selected(self.filter == IssueFilter::All)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.filter = IssueFilter::All;
|
||||
@@ -244,7 +231,7 @@ impl IssuesView {
|
||||
.child(
|
||||
SegmentButton::new("open", "Open")
|
||||
.icon(Icon::new(CustomIconName::GitIssueOpen))
|
||||
.count(open)
|
||||
.count(counts.open)
|
||||
.selected(self.filter == IssueFilter::Open)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.filter = IssueFilter::Open;
|
||||
@@ -254,7 +241,7 @@ impl IssuesView {
|
||||
.child(
|
||||
SegmentButton::new("closed", "Closed")
|
||||
.icon(Icon::new(CustomIconName::GitIssueClosed))
|
||||
.count(closed)
|
||||
.count(counts.closed)
|
||||
.selected(self.filter == IssueFilter::Closed)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.filter = IssueFilter::Closed;
|
||||
|
||||
@@ -8,6 +8,7 @@ mod repo;
|
||||
mod repo_list;
|
||||
mod send_patch;
|
||||
pub(crate) mod sidebar;
|
||||
mod status_list;
|
||||
pub(crate) mod tree;
|
||||
|
||||
pub use inbox::InboxView;
|
||||
|
||||
@@ -26,7 +26,7 @@ use signed_core::{
|
||||
merge_base_of, pull_request_patch,
|
||||
};
|
||||
use signed_git::{FileCommit, patch_commits, patch_diffs};
|
||||
use signed_state::{Backend, GitStore, ProfileStore, RepoStore};
|
||||
use signed_state::{Backend, ProfileStore, RepoStore, ensure_repo_mirror};
|
||||
use signed_ui::{CountBadge, UserAvatar, placeholder, status_badge};
|
||||
use utils::{relative_time, relative_time_secs};
|
||||
|
||||
@@ -217,8 +217,6 @@ impl PullRequestDetailView {
|
||||
self.current_commit = binding.tip.clone().map(SharedString::from);
|
||||
cx.notify();
|
||||
|
||||
let cache = GitStore::global(cx).cache().clone();
|
||||
|
||||
self.load_generation = self.load_generation.wrapping_add(1);
|
||||
let generation = self.load_generation;
|
||||
|
||||
@@ -257,7 +255,6 @@ impl PullRequestDetailView {
|
||||
let git = if use_nostr {
|
||||
None
|
||||
} else {
|
||||
let cache = cache.clone();
|
||||
let addr = addr.clone();
|
||||
let clone_urls = clone_urls.clone();
|
||||
let base = base.clone();
|
||||
@@ -265,7 +262,7 @@ impl PullRequestDetailView {
|
||||
|
||||
Some(
|
||||
cx.background_spawn(async move {
|
||||
let repo = cache.ensure_clone(&addr, &clone_urls)?;
|
||||
let repo = ensure_repo_mirror(&addr, &clone_urls)?;
|
||||
|
||||
let workdir = repo
|
||||
.workdir()
|
||||
|
||||
@@ -25,6 +25,7 @@ pub(super) mod new;
|
||||
use self::detail::PullRequestDetailView;
|
||||
use self::new::open_new_pull_panel;
|
||||
use super::send_patch::open_send_patch_panel;
|
||||
use super::status_list::{StatusCounts, filter_by_status};
|
||||
use crate::views::repo::RepoAction;
|
||||
|
||||
const ROW_HEIGHT: f32 = 73.;
|
||||
@@ -59,8 +60,7 @@ pub struct PullRequestsView {
|
||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||
/// Indices into the store's `pull_requests` matching [`Self::filter`].
|
||||
visible_prs: Vec<usize>,
|
||||
/// Header counts `(total, open, closed, draft, merged)`.
|
||||
counts: (usize, usize, usize, usize, usize),
|
||||
counts: StatusCounts,
|
||||
// A filter change notifies even when the visible rows are unchanged,
|
||||
// e.g. switching between two empty filters.
|
||||
synced_filter: PullRequestFilter,
|
||||
@@ -93,7 +93,7 @@ impl PullRequestsView {
|
||||
filter: PullRequestFilter::Open,
|
||||
item_sizes: Rc::new(Vec::new()),
|
||||
visible_prs: Vec::new(),
|
||||
counts: (0, 0, 0, 0, 0),
|
||||
counts: StatusCounts::default(),
|
||||
synced_filter: PullRequestFilter::Open,
|
||||
scroll_handle: VirtualListScrollHandle::new(),
|
||||
_subscription: subscription,
|
||||
@@ -105,32 +105,15 @@ impl PullRequestsView {
|
||||
|
||||
let (visible_prs, counts) = {
|
||||
let store = self.store.read(cx);
|
||||
let mut counts = (0usize, 0usize, 0usize, 0usize, 0usize);
|
||||
|
||||
let visible_prs: Vec<usize> = store
|
||||
let roots = store
|
||||
.pull_requests
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(ix, pr)| {
|
||||
if pr.kind != Kind::GitPullRequest {
|
||||
return None;
|
||||
}
|
||||
|
||||
let status = store.status_of(pr);
|
||||
counts.0 += 1;
|
||||
|
||||
match status {
|
||||
RepoStatus::Open => counts.1 += 1,
|
||||
RepoStatus::Closed => counts.2 += 1,
|
||||
RepoStatus::Draft => counts.3 += 1,
|
||||
RepoStatus::Applied => counts.4 += 1,
|
||||
}
|
||||
|
||||
filter.matches(status).then_some(ix)
|
||||
})
|
||||
.collect();
|
||||
|
||||
(visible_prs, counts)
|
||||
.filter(|pr| pr.kind == Kind::GitPullRequest);
|
||||
filter_by_status(
|
||||
roots,
|
||||
|pr| store.status_of(pr),
|
||||
|status| filter.matches(status),
|
||||
)
|
||||
};
|
||||
|
||||
let filter_changed = self.synced_filter != filter;
|
||||
@@ -236,7 +219,7 @@ impl PullRequestsView {
|
||||
}
|
||||
|
||||
fn render_header(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
let (total, open, closed, draft, merged) = self.counts;
|
||||
let counts = self.counts;
|
||||
|
||||
h_flex()
|
||||
.px_4()
|
||||
@@ -252,7 +235,7 @@ impl PullRequestsView {
|
||||
.child(
|
||||
SegmentButton::new("all", "All")
|
||||
.icon(Icon::new(CustomIconName::GitPullRequest))
|
||||
.count(total)
|
||||
.count(counts.total)
|
||||
.selected(self.filter == PullRequestFilter::All)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.filter = PullRequestFilter::All;
|
||||
@@ -262,7 +245,7 @@ impl PullRequestsView {
|
||||
.child(
|
||||
SegmentButton::new("open", "Open")
|
||||
.icon(Icon::new(CustomIconName::GitPullRequest))
|
||||
.count(open)
|
||||
.count(counts.open)
|
||||
.selected(self.filter == PullRequestFilter::Open)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.filter = PullRequestFilter::Open;
|
||||
@@ -272,7 +255,7 @@ impl PullRequestsView {
|
||||
.child(
|
||||
SegmentButton::new("closed", "Closed")
|
||||
.icon(Icon::new(CustomIconName::GitPullRequestClosed))
|
||||
.count(closed)
|
||||
.count(counts.closed)
|
||||
.selected(self.filter == PullRequestFilter::Closed)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.filter = PullRequestFilter::Closed;
|
||||
@@ -282,7 +265,7 @@ impl PullRequestsView {
|
||||
.child(
|
||||
SegmentButton::new("draft", "Draft")
|
||||
.icon(Icon::new(CustomIconName::GitPullRequestDraft))
|
||||
.count(draft)
|
||||
.count(counts.draft)
|
||||
.selected(self.filter == PullRequestFilter::Draft)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.filter = PullRequestFilter::Draft;
|
||||
@@ -292,7 +275,7 @@ impl PullRequestsView {
|
||||
.child(
|
||||
SegmentButton::new("merged", "Merged")
|
||||
.icon(Icon::new(CustomIconName::GitPullRequestMerged))
|
||||
.count(merged)
|
||||
.count(counts.applied)
|
||||
.selected(self.filter == PullRequestFilter::Merged)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.filter = PullRequestFilter::Merged;
|
||||
|
||||
@@ -26,7 +26,9 @@ use signed_git::{
|
||||
delete_refs_with_prefix, fetch_repo_refs, fork_namespace, merge_base, refs_with_prefix,
|
||||
worktree_commit_range_commits, worktree_commit_range_diff,
|
||||
};
|
||||
use signed_state::{Backend, CheckoutsStore, GitStore, RepoListStore, RepoStore};
|
||||
use signed_state::{
|
||||
Backend, CheckoutsStore, RepoListStore, RepoStore, ensure_repo_mirror, repo_mirror_path,
|
||||
};
|
||||
use signed_ui::{CountBadge, placeholder, ref_selector_trigger};
|
||||
|
||||
use crate::views::commit_diff::{COMMIT_ROW_HEIGHT, CommitDiffView, DiffPane, commit_row};
|
||||
@@ -73,7 +75,7 @@ struct ForkCompare {
|
||||
announcement: Announcement,
|
||||
/// Import namespace of the form `<owner-hex>/<sanitized-id>`.
|
||||
namespace: String,
|
||||
/// Path of the target repository's GitCache mirror.
|
||||
/// Path of the target repository's mirror.
|
||||
mirror_path: PathBuf,
|
||||
}
|
||||
|
||||
@@ -533,8 +535,7 @@ impl NewPullRequestView {
|
||||
let Some((base, _euc)) = self.base_repo(cx) else {
|
||||
return;
|
||||
};
|
||||
let cache = GitStore::global(cx).cache().clone();
|
||||
let mirror_path = cache.repo_path(&base);
|
||||
let mirror_path = repo_mirror_path(&base);
|
||||
let namespace = fork_namespace(&announcement);
|
||||
let clone_urls = announcement.clone.clone();
|
||||
|
||||
@@ -561,24 +562,20 @@ impl NewPullRequestView {
|
||||
|
||||
let task: gpui::Task<Result<(), anyhow::Error>> =
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
// The fork and base must share history for a merge-base to exist.
|
||||
// The target's mirror is the object store both sides land in.
|
||||
// `ensure_clone` fetches `origin` when the mirror already exists.
|
||||
let result = cx
|
||||
.background_spawn({
|
||||
let cache = cache.clone();
|
||||
let base = base.clone();
|
||||
let base_clone_urls = base_clone_urls.clone();
|
||||
let namespace = namespace.clone();
|
||||
let clone_urls = clone_urls.clone();
|
||||
let mirror_path = mirror_path.clone();
|
||||
async move {
|
||||
cache.ensure_clone(&base, &base_clone_urls)?;
|
||||
ensure_repo_mirror(&base, &base_clone_urls)?;
|
||||
|
||||
// Prune stale imports of any fork.
|
||||
// Then import this fork's heads under its namespace.
|
||||
// Prune stale imports of any fork. Then import this fork's heads under its namespace.
|
||||
delete_refs_with_prefix(&mirror_path, "refs/fork")?;
|
||||
|
||||
// Fetch the fork's refs and import them under the fork's namespace.
|
||||
fetch_repo_refs(
|
||||
&mirror_path,
|
||||
&clone_urls,
|
||||
@@ -658,8 +655,6 @@ impl NewPullRequestView {
|
||||
let (base_branches, compare_branches) = match result {
|
||||
Ok(branches) => branches,
|
||||
Err(error) => {
|
||||
// Keep the previous source, if any.
|
||||
// The error shows inline next to the compare bar.
|
||||
self.error = Some(format!("Could not compare against the fork: {error}").into());
|
||||
cx.notify();
|
||||
return;
|
||||
@@ -673,8 +668,7 @@ impl NewPullRequestView {
|
||||
}
|
||||
|
||||
if base_branches.is_empty() {
|
||||
self.error =
|
||||
Some("Could not list the target repository's branches; try again later".into());
|
||||
self.error = Some("Could not list the target repository's branches.".into());
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
@@ -687,11 +681,8 @@ impl NewPullRequestView {
|
||||
.map(SharedString::from)
|
||||
.collect();
|
||||
|
||||
// Base defaults to the announced HEAD branch when the mirror has it.
|
||||
// Otherwise `main`, then the first branch.
|
||||
// The fork's `main` is the compare default, else the first branch.
|
||||
// A refresh keeps the previous selection when the branch still exists.
|
||||
let announced = self.store.read(cx).head.clone();
|
||||
|
||||
let contains =
|
||||
|name: &str, list: &[SharedString]| list.iter().any(|branch| branch.as_ref() == name);
|
||||
|
||||
@@ -790,16 +781,19 @@ impl NewPullRequestView {
|
||||
"{base_name} and {compare_name} share no common ancestor"
|
||||
)
|
||||
})?;
|
||||
|
||||
let commits = worktree_commit_range_commits(
|
||||
Path::new(&repo_path),
|
||||
&merge_base,
|
||||
&compare,
|
||||
)?;
|
||||
|
||||
let diff = worktree_commit_range_diff(
|
||||
Path::new(&repo_path),
|
||||
&merge_base,
|
||||
&compare,
|
||||
)?;
|
||||
|
||||
Ok::<_, anyhow::Error>((merge_base, commits, diff))
|
||||
}
|
||||
})
|
||||
@@ -923,6 +917,7 @@ impl NewPullRequestView {
|
||||
let Some(repo_path) = self.work_path() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(dock_area) = self.dock_area.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -26,8 +26,8 @@ use nostr::prelude::{RelayUrl, ToBech32, Url};
|
||||
use signed_core::{Announcement, RepoAddr, RepoStatus};
|
||||
use signed_git::FileCommit;
|
||||
use signed_state::{
|
||||
Backend, CheckoutStatus, CheckoutsStore, GitStore, LocalReposStore, ProfileStore,
|
||||
RepoListStore, RepoStore, pr_proposes_checkout,
|
||||
Backend, CheckoutStatus, CheckoutsStore, LocalReposStore, ProfileStore, RepoListStore,
|
||||
RepoStore, ensure_repo_mirror, open_repo_mirror, pr_proposes_checkout,
|
||||
};
|
||||
use signed_ui::{
|
||||
CountBadge, DropdownButton, PixelAvatar, UserAvatar, copy_row, menu_copy_row, middle_truncate,
|
||||
@@ -343,15 +343,13 @@ impl RepoDetailView {
|
||||
|
||||
self.repo_started = true;
|
||||
|
||||
let cache = GitStore::global(cx).cache().clone();
|
||||
let addr = announcement.addr();
|
||||
let clone_urls: Vec<Url> = announcement.clone.clone();
|
||||
|
||||
let disk = {
|
||||
let cache = cache.clone();
|
||||
let addr = addr.clone();
|
||||
cx.background_spawn(async move {
|
||||
match cache.open(&addr)? {
|
||||
match open_repo_mirror(&addr)? {
|
||||
Some(repo) => Ok(Some(load_repo_data(&repo)?)),
|
||||
None => Ok(None),
|
||||
}
|
||||
@@ -365,11 +363,10 @@ impl RepoDetailView {
|
||||
let data = match disk {
|
||||
Ok(Some(data)) => Ok(data),
|
||||
Ok(None) => {
|
||||
let cache = cache.clone();
|
||||
let addr = addr.clone();
|
||||
let clone_urls = clone_urls.clone();
|
||||
cx.background_spawn(async move {
|
||||
let repo = cache.ensure_clone(&addr, &clone_urls)?;
|
||||
let repo = ensure_repo_mirror(&addr, &clone_urls)?;
|
||||
load_repo_data(&repo)
|
||||
})
|
||||
.await
|
||||
@@ -393,11 +390,10 @@ impl RepoDetailView {
|
||||
}
|
||||
|
||||
let refresh = {
|
||||
let cache = cache.clone();
|
||||
let addr = addr.clone();
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let Some(repo) = cache.open(&addr)? else {
|
||||
let Some(repo) = open_repo_mirror(&addr)? else {
|
||||
return Ok::<_, Error>(None);
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ use nostr::prelude::*;
|
||||
use settings::{DEFAULT_GRASP_SERVERS, GraspServersSettings};
|
||||
use signed_state::Backend;
|
||||
|
||||
use super::{normalize_server, server_host};
|
||||
|
||||
/// State of the grasp-server section of a publish dialog, so async results can be rendered.
|
||||
#[derive(Default)]
|
||||
pub struct GraspServersState {
|
||||
@@ -155,7 +157,7 @@ fn render_server_row(
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.text_sm()
|
||||
.rounded(cx.theme().radius)
|
||||
.child(display_server(relay)),
|
||||
.child(server_host(relay)),
|
||||
)
|
||||
.child(
|
||||
Button::new(format!("remove-relay:{ix}"))
|
||||
@@ -174,14 +176,6 @@ fn render_server_row(
|
||||
)
|
||||
}
|
||||
|
||||
/// Shows only the host, since grasp servers are entered without a scheme.
|
||||
fn display_server(relay: &RelayUrl) -> SharedString {
|
||||
relay
|
||||
.domain()
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| SharedString::from(relay.to_string()))
|
||||
}
|
||||
|
||||
/// Accepts a bare host as well as a full URL.
|
||||
fn add_relay(
|
||||
state: &Entity<GraspServersState>,
|
||||
@@ -194,14 +188,8 @@ fn add_relay(
|
||||
return;
|
||||
}
|
||||
|
||||
let normalized = if value.contains("://") {
|
||||
value.clone()
|
||||
} else {
|
||||
format!("wss://{value}")
|
||||
};
|
||||
|
||||
match RelayUrl::parse(&normalized) {
|
||||
Ok(relay) => {
|
||||
match normalize_server(&value) {
|
||||
Some((_, relay)) => {
|
||||
state.update(cx, |state, _| {
|
||||
state.error = None;
|
||||
if !state.grasp_servers.contains(&relay) {
|
||||
@@ -210,7 +198,7 @@ fn add_relay(
|
||||
});
|
||||
input.update(cx, |input, cx| input.set_value("", window, cx));
|
||||
}
|
||||
Err(_) => {
|
||||
None => {
|
||||
state.update(cx, |state, _| {
|
||||
state.error = Some(format!("Invalid grasp server URL: {value}").into());
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ use gpui_base::Button as BaseButton;
|
||||
use gpui_component::button::{Button, ButtonVariants};
|
||||
use gpui_component::input::InputState;
|
||||
use gpui_component::{ActiveTheme, Icon, IconName, Sizable, StyledExt, h_flex, v_flex};
|
||||
use nostr::prelude::RelayUrl;
|
||||
use signed_core::{Announcement, RepoAddr, identifier_from_name};
|
||||
use signed_state::{
|
||||
Backend, BackendEvent, CheckoutsStore, LocalReposStore, Profile, ProfileStore, RepoListStore,
|
||||
@@ -542,6 +543,26 @@ impl SidebarPanel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalize a user-typed grasp server, adding a `wss://` scheme when none is given.
|
||||
///
|
||||
/// Returns the text to store and the parsed relay URL, or `None` when it is not a valid relay URL.
|
||||
pub(super) fn normalize_server(input: &str) -> Option<(String, RelayUrl)> {
|
||||
let text = if input.contains("://") {
|
||||
input.to_owned()
|
||||
} else {
|
||||
format!("wss://{input}")
|
||||
};
|
||||
RelayUrl::parse(&text).ok().map(|relay| (text, relay))
|
||||
}
|
||||
|
||||
/// The host of a relay URL, which is what the server lists show; the scheme is implied.
|
||||
pub(super) fn server_host(relay: &RelayUrl) -> SharedString {
|
||||
relay
|
||||
.domain()
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| SharedString::from(relay.to_string()))
|
||||
}
|
||||
|
||||
fn pick_banner() -> SharedString {
|
||||
let num = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
|
||||
@@ -18,10 +18,11 @@ use gpui_component::{
|
||||
ActiveTheme, IconName, IndexPath, Sizable, Theme, ThemeMode, ThemeRegistry, WindowExt, h_flex,
|
||||
v_flex,
|
||||
};
|
||||
use nostr::prelude::RelayUrl;
|
||||
use settings::{AppearanceMode, Settings, SettingsStore};
|
||||
use signed_ui::{SelectOption, setting_block, setting_row};
|
||||
|
||||
use super::{normalize_server, server_host};
|
||||
|
||||
/// Looks up the option index used to seed a [`SelectState`].
|
||||
fn selected_index(options: &[SelectOption], value: &str) -> Option<IndexPath> {
|
||||
options
|
||||
@@ -454,13 +455,11 @@ fn grasp_server_editor(
|
||||
}
|
||||
|
||||
/// Shows only the host, since grasp servers are entered without a scheme.
|
||||
/// Matches how the publish dialogs display servers.
|
||||
fn display_server(server: &str) -> SharedString {
|
||||
RelayUrl::parse(server)
|
||||
.ok()
|
||||
.and_then(|relay| relay.domain().map(|domain| domain.to_owned()))
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| SharedString::from(server.to_owned()))
|
||||
match normalize_server(server) {
|
||||
Some((_, relay)) => server_host(&relay),
|
||||
None => SharedString::from(server.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn repositories_section(
|
||||
@@ -568,14 +567,9 @@ fn add_server(input: &Entity<InputState>, window: &mut Window, cx: &mut App) {
|
||||
if value.is_empty() {
|
||||
return;
|
||||
}
|
||||
let normalized = if value.contains("://") {
|
||||
value
|
||||
} else {
|
||||
format!("wss://{value}")
|
||||
};
|
||||
if RelayUrl::parse(&normalized).is_err() {
|
||||
let Some((normalized, _)) = normalize_server(&value) else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let store = SettingsStore::global(cx);
|
||||
store.update(cx, |store, cx| {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
use nostr::prelude::Event;
|
||||
use signed_core::RepoStatus;
|
||||
|
||||
/// Root events counted by their resolved status.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub(crate) struct StatusCounts {
|
||||
pub(crate) total: usize,
|
||||
pub(crate) open: usize,
|
||||
pub(crate) closed: usize,
|
||||
pub(crate) draft: usize,
|
||||
pub(crate) applied: usize,
|
||||
}
|
||||
|
||||
impl StatusCounts {
|
||||
fn record(&mut self, status: RepoStatus) {
|
||||
self.total += 1;
|
||||
match status {
|
||||
RepoStatus::Open => self.open += 1,
|
||||
RepoStatus::Closed => self.closed += 1,
|
||||
RepoStatus::Draft => self.draft += 1,
|
||||
RepoStatus::Applied => self.applied += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indices of `roots` whose status `keep` accepts, counting every root's status.
|
||||
pub(crate) fn filter_by_status<'a>(
|
||||
roots: impl IntoIterator<Item = &'a Event>,
|
||||
status_of: impl Fn(&Event) -> RepoStatus,
|
||||
keep: impl Fn(RepoStatus) -> bool,
|
||||
) -> (Vec<usize>, StatusCounts) {
|
||||
let mut counts = StatusCounts::default();
|
||||
|
||||
let visible = roots
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, root)| {
|
||||
let status = status_of(root);
|
||||
counts.record(status);
|
||||
keep(status).then_some(index)
|
||||
})
|
||||
.collect();
|
||||
|
||||
(visible, counts)
|
||||
}
|
||||
Reference in New Issue
Block a user