feat: improve repository history navigation
This commit is contained in:
@@ -390,6 +390,34 @@ fn wire_callbacks(ui: &AppWindow, state: Arc<Mutex<State>>) {
|
||||
}
|
||||
});
|
||||
|
||||
ui.on_select_branch({
|
||||
let weak = ui.as_weak();
|
||||
let state = state.clone();
|
||||
move |selection| {
|
||||
let target = {
|
||||
let mut state = state.lock().unwrap();
|
||||
let branch = (selection.as_str() != "All").then(|| selection.to_string());
|
||||
if branch
|
||||
.as_ref()
|
||||
.is_some_and(|branch| !state.branches.contains(branch))
|
||||
{
|
||||
return;
|
||||
}
|
||||
state.active_branch = branch;
|
||||
state.commits.clear();
|
||||
state
|
||||
.active_server
|
||||
.and_then(|index| state.preferences.servers.get(index))
|
||||
.cloned()
|
||||
.zip(state.active_repository.clone())
|
||||
};
|
||||
if let (Some(ui), Some((server, (owner, repository)))) = (weak.upgrade(), target) {
|
||||
ui.set_commits(empty_model());
|
||||
fetch_commits(&ui, state.clone(), server, owner, repository, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.on_select_file({
|
||||
let weak = ui.as_weak();
|
||||
let state = state.clone();
|
||||
@@ -410,6 +438,26 @@ fn wire_callbacks(ui: &AppWindow, state: Arc<Mutex<State>>) {
|
||||
}
|
||||
});
|
||||
|
||||
ui.on_select_pull_file({
|
||||
let weak = ui.as_weak();
|
||||
let state = state.clone();
|
||||
move |path| {
|
||||
let target = {
|
||||
let state = state.lock().unwrap();
|
||||
state
|
||||
.active_server
|
||||
.and_then(|index| state.preferences.servers.get(index))
|
||||
.cloned()
|
||||
.zip(state.active_pull.clone())
|
||||
};
|
||||
if let (Some(ui), Some((server, (owner, repository, number)))) =
|
||||
(weak.upgrade(), target)
|
||||
{
|
||||
fetch_pull_file_diff(&ui, server, owner, repository, number, path.to_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.on_select_activity({
|
||||
let weak = ui.as_weak();
|
||||
let state = state.clone();
|
||||
@@ -427,6 +475,15 @@ fn wire_callbacks(ui: &AppWindow, state: Arc<Mutex<State>>) {
|
||||
return;
|
||||
};
|
||||
match target.as_str() {
|
||||
"repository" => {
|
||||
ui.set_tab("repositories".into());
|
||||
open_commit_history(
|
||||
&ui,
|
||||
state.clone(),
|
||||
owner.to_string(),
|
||||
repository.to_string(),
|
||||
);
|
||||
}
|
||||
"issue" => {
|
||||
state.lock().unwrap().active_issue = Some(number as i64);
|
||||
ui.set_tab("issues".into());
|
||||
@@ -525,6 +582,7 @@ fn wire_callbacks(ui: &AppWindow, state: Arc<Mutex<State>>) {
|
||||
}
|
||||
ui.set_page(match (ui.get_tab().as_str(), ui.get_page().as_str()) {
|
||||
("pulls", "pull") => "pulls".into(),
|
||||
("pulls", "diff") => "pull".into(),
|
||||
("repositories", "diff") => "files".into(),
|
||||
("repositories", "files") => "commits".into(),
|
||||
("repositories", "commits") => "repositories".into(),
|
||||
@@ -802,7 +860,7 @@ fn fetch_pull(
|
||||
let client =
|
||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||
let configuration = client.configuration();
|
||||
let (pull, comments) = tokio::join!(
|
||||
let (pull, comments, files) = tokio::join!(
|
||||
apis::repository_api::repo_get_pull_request(
|
||||
&configuration,
|
||||
&owner,
|
||||
@@ -817,10 +875,12 @@ fn fetch_pull(
|
||||
None,
|
||||
None,
|
||||
),
|
||||
load_pull_files(&configuration, &owner, &repository, number,),
|
||||
);
|
||||
Ok::<_, String>(PullDetails {
|
||||
pull: pull.map_err(|error| error.to_string())?,
|
||||
comments: comments.map_err(|error| error.to_string())?,
|
||||
files: files.map_err(|error| error.to_string())?,
|
||||
})
|
||||
}
|
||||
.await;
|
||||
@@ -847,8 +907,18 @@ fn open_commit_history(
|
||||
) {
|
||||
let server = {
|
||||
let mut state = state.lock().unwrap();
|
||||
let default_branch = state
|
||||
.repositories
|
||||
.iter()
|
||||
.find(|candidate| candidate.owner == owner && candidate.name == repository)
|
||||
.map(|candidate| candidate.default_branch.clone())
|
||||
.unwrap_or_else(|| "main".into());
|
||||
state.active_repository = Some((owner.clone(), repository.clone()));
|
||||
state.active_branch = Some(default_branch.clone());
|
||||
state.branches.clear();
|
||||
state.commits.clear();
|
||||
ui.set_branch_choices(model(vec!["All".into(), default_branch.into()]));
|
||||
ui.set_branch_index(1);
|
||||
state
|
||||
.active_server
|
||||
.and_then(|index| state.preferences.servers.get(index))
|
||||
@@ -876,8 +946,29 @@ fn fetch_commits(
|
||||
ui.set_loading(true);
|
||||
}
|
||||
let weak = ui.as_weak();
|
||||
let (active_branch, branches) = {
|
||||
let state = state.lock().unwrap();
|
||||
(state.active_branch.clone(), state.branches.clone())
|
||||
};
|
||||
spawn(async move {
|
||||
let result = load_commits(&server, &owner, &repository).await;
|
||||
let requested_branch = active_branch.clone();
|
||||
let result = async {
|
||||
let default_branch = active_branch
|
||||
.as_deref()
|
||||
.or(branches.first().map(String::as_str))
|
||||
.unwrap_or("main");
|
||||
let branches = if branches.is_empty() {
|
||||
load_branches(&server, &owner, &repository, default_branch).await?
|
||||
} else {
|
||||
branches
|
||||
};
|
||||
let commits = match active_branch.as_deref() {
|
||||
Some(branch) => load_branch_commits(&server, &owner, &repository, branch).await?,
|
||||
None => load_all_commits(&server, &owner, &repository, &branches).await?,
|
||||
};
|
||||
Ok::<_, String>((requested_branch, branches, commits))
|
||||
}
|
||||
.await;
|
||||
post(move || {
|
||||
let Some(ui) = weak.upgrade() else { return };
|
||||
if refreshing {
|
||||
@@ -886,9 +977,43 @@ fn fetch_commits(
|
||||
ui.set_loading(false);
|
||||
}
|
||||
match result {
|
||||
Ok(commits) => {
|
||||
state.lock().unwrap().commits = commits;
|
||||
refresh_commits(&ui, &state.lock().unwrap().commits);
|
||||
Ok((requested_branch, branches, commits)) => {
|
||||
let mut state = state.lock().unwrap();
|
||||
if state.active_branch != requested_branch {
|
||||
return;
|
||||
}
|
||||
state.branches = branches;
|
||||
state.commits = commits;
|
||||
let mut choices: Vec<slint::SharedString> = vec!["All".into()];
|
||||
choices.extend(state.branches.iter().map(Into::into));
|
||||
ui.set_branch_choices(model(choices));
|
||||
ui.set_branch_index(
|
||||
state
|
||||
.active_branch
|
||||
.as_ref()
|
||||
.and_then(|active| {
|
||||
state.branches.iter().position(|branch| branch == active)
|
||||
})
|
||||
.map_or(0, |index| index as i32 + 1),
|
||||
);
|
||||
let lane_count = if state.active_branch.is_none() {
|
||||
state
|
||||
.commits
|
||||
.iter()
|
||||
.flat_map(|commit| {
|
||||
commit
|
||||
.top_lanes
|
||||
.iter()
|
||||
.chain(commit.bottom_lanes.iter())
|
||||
.chain(commit.connections.iter())
|
||||
.chain(commit.node_lane.iter())
|
||||
})
|
||||
.max()
|
||||
.map_or(0, |lane| lane + 1)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
refresh_commits(&ui, &state.commits, lane_count);
|
||||
}
|
||||
Err(error) => ui.set_error(error.into()),
|
||||
}
|
||||
@@ -972,28 +1097,68 @@ fn fetch_file_diff(
|
||||
let Some(ui) = weak.upgrade() else { return };
|
||||
ui.set_loading(false);
|
||||
match result {
|
||||
Ok(diff) => {
|
||||
ui.set_detail_title(path.rsplit('/').next().unwrap_or(&path).into());
|
||||
ui.set_diff_columns(diff.columns.min(i32::MAX as usize) as i32);
|
||||
ui.set_diff_lines(model(
|
||||
diff.lines
|
||||
.into_iter()
|
||||
.map(|line| DiffLine {
|
||||
old_number: line.old_number.into(),
|
||||
new_number: line.new_number.into(),
|
||||
text: line.text.into(),
|
||||
kind: line.kind.into(),
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
ui.set_page("diff".into());
|
||||
}
|
||||
Ok(diff) => show_file_diff(&ui, &path, diff),
|
||||
Err(error) => ui.set_error(error.into()),
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn fetch_pull_file_diff(
|
||||
ui: &AppWindow,
|
||||
server: Server,
|
||||
owner: String,
|
||||
repository: String,
|
||||
number: i64,
|
||||
path: String,
|
||||
) {
|
||||
ui.set_loading(true);
|
||||
let weak = ui.as_weak();
|
||||
spawn(async move {
|
||||
let result = async {
|
||||
let client =
|
||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||
apis::repository_api::repo_download_pull_diff_or_patch(
|
||||
&client.configuration(),
|
||||
&owner,
|
||||
&repository,
|
||||
number,
|
||||
"diff",
|
||||
Some(false),
|
||||
)
|
||||
.await
|
||||
.map(|text| diff::parse_file(&text, &path))
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
.await;
|
||||
post(move || {
|
||||
let Some(ui) = weak.upgrade() else { return };
|
||||
ui.set_loading(false);
|
||||
match result {
|
||||
Ok(diff) => show_file_diff(&ui, &path, diff),
|
||||
Err(error) => ui.set_error(error.into()),
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn show_file_diff(ui: &AppWindow, path: &str, diff: diff::Parsed) {
|
||||
ui.set_detail_title(path.rsplit('/').next().unwrap_or(path).into());
|
||||
ui.set_diff_columns(diff.columns.min(i32::MAX as usize) as i32);
|
||||
ui.set_diff_lines(model(
|
||||
diff.lines
|
||||
.into_iter()
|
||||
.map(|line| DiffLine {
|
||||
old_number: line.old_number.into(),
|
||||
new_number: line.new_number.into(),
|
||||
text: line.text.into(),
|
||||
kind: line.kind.into(),
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
ui.set_page("diff".into());
|
||||
}
|
||||
|
||||
fn runtime() -> &'static tokio::runtime::Runtime {
|
||||
RUNTIME.get_or_init(|| tokio::runtime::Runtime::new().expect("Tokio runtime"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user