From 1bc41a91469e6469d430da4886e1082bdaadc956 Mon Sep 17 00:00:00 2001 From: Kiril Videlov Date: Tue, 7 May 2024 00:43:45 +0200 Subject: [PATCH] snapshot list pagination --- app/src/lib/components/History.svelte | 37 ++++++++++++++----- crates/gitbutler-cli/src/main.rs | 2 +- .../gitbutler-core/src/snapshots/snapshot.rs | 27 ++++++++------ crates/gitbutler-tauri/src/snapshots.rs | 3 +- 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/app/src/lib/components/History.svelte b/app/src/lib/components/History.svelte index daf938ddc..5223233bf 100644 --- a/app/src/lib/components/History.svelte +++ b/app/src/lib/components/History.svelte @@ -3,16 +3,19 @@ import { invoke } from '$lib/backend/ipc'; import { getContext } from '$lib/utils/context'; import { VirtualBranchService } from '$lib/vbranches/virtualBranch'; - import { onMount } from 'svelte'; + import { onMount, onDestroy } from 'svelte'; import { goto } from '$app/navigation'; export let projectId: string; - const snapshotsLimit = 100; - const vbranchService = getContext(VirtualBranchService); vbranchService.activeBranches.subscribe(() => { - listSnapshots(projectId, snapshotsLimit); + // whenever virtual branches change, we need to reload the snapshots + // TODO: if the list has results from more pages, merge into it? + listSnapshots(projectId).then((rsp) => { + snapshots = rsp; + listElement?.scrollTo(0, 0); + }); }); type Trailer = { @@ -34,12 +37,13 @@ createdAt: number; }; let snapshots: Snapshot[] = []; - async function listSnapshots(projectId: string, limit: number) { + async function listSnapshots(projectId: string, sha?: string) { const resp = await invoke('list_snapshots', { projectId: projectId, - limit: limit + limit: 32, + sha: sha }); - snapshots = resp; + return resp; } async function restoreSnapshot(projectId: string, sha: string) { await invoke('restore_snapshot', { @@ -49,12 +53,27 @@ // TODO: is there a better way to update all the state? await goto(window.location.href, { replaceState: true }); } + function onLastInView() { + if (!listElement) return; + if (listElement.scrollTop + listElement.clientHeight >= listElement.scrollHeight) { + listSnapshots(projectId, snapshots[snapshots.length - 1].id).then((rsp) => { + snapshots = [...snapshots, ...rsp.slice(1)]; + }); + } + } + let listElement: HTMLElement | undefined = undefined; onMount(async () => { - listSnapshots(projectId, snapshotsLimit); + listSnapshots(projectId).then((rsp) => { + snapshots = rsp; + }); + if (listElement) listElement.addEventListener('scroll', onLastInView, true); + }); + onDestroy(() => { + listElement?.removeEventListener('scroll', onLastInView, true); }); -
+
{#each snapshots as entry, idx}
diff --git a/crates/gitbutler-cli/src/main.rs b/crates/gitbutler-cli/src/main.rs index 15c904089..fffc6c33f 100644 --- a/crates/gitbutler-cli/src/main.rs +++ b/crates/gitbutler-cli/src/main.rs @@ -49,7 +49,7 @@ fn main() -> Result<()> { fn list_snapshots(repo_dir: &str) -> Result<()> { let project = project_from_path(repo_dir); - let snapshots = project.list_snapshots(100)?; + let snapshots = project.list_snapshots(100, None)?; for snapshot in snapshots { let ts = chrono::DateTime::from_timestamp(snapshot.created_at / 1000, 0); let details = snapshot.details; diff --git a/crates/gitbutler-core/src/snapshots/snapshot.rs b/crates/gitbutler-core/src/snapshots/snapshot.rs index 016e30121..b5591df57 100644 --- a/crates/gitbutler-core/src/snapshots/snapshot.rs +++ b/crates/gitbutler-core/src/snapshots/snapshot.rs @@ -37,7 +37,7 @@ pub trait Oplog { /// An alternative way of retrieving the snapshots would be to manually the oplog head `git log ` available in `.git/gitbutler/oplog.toml`. /// /// If there are no snapshots, an empty list is returned. - fn list_snapshots(&self, limit: usize) -> Result>; + fn list_snapshots(&self, limit: usize, sha: Option) -> Result>; /// Reverts to a previous state of the working directory, virtual branches and commits. /// The provided sha must refer to a valid snapshot commit. /// Upon success, a new snapshot is created. @@ -146,17 +146,22 @@ impl Oplog for Project { Ok(Some(new_commit_oid.to_string())) } - fn list_snapshots(&self, limit: usize) -> Result> { + fn list_snapshots(&self, limit: usize, sha: Option) -> Result> { let repo_path = self.path.as_path(); let repo = git2::Repository::init(repo_path)?; - let oplog_state = OplogHandle::new(&self.gb_dir()); - let head_sha = oplog_state.get_oplog_head()?; - if head_sha.is_none() { - // there are no snapshots to return - return Ok(vec![]); - } - let head_sha = head_sha.unwrap(); + let head_sha = match sha { + Some(sha) => sha, + None => { + let oplog_state = OplogHandle::new(&self.gb_dir()); + if let Some(sha) = oplog_state.get_oplog_head()? { + sha + } else { + // there are no snapshots so return an empty list + return Ok(vec![]); + } + } + }; let oplog_head_commit = repo.find_commit(git2::Oid::from_str(&head_sha)?)?; @@ -525,7 +530,7 @@ mod tests { .create_snapshot(SnapshotDetails::new(OperationType::UpdateWorkspaceBase)) .unwrap(); - let initial_snapshot = &project.list_snapshots(10).unwrap()[1]; + let initial_snapshot = &project.list_snapshots(10, None).unwrap()[1]; assert_eq!( initial_snapshot.files_changed, vec![ @@ -536,7 +541,7 @@ mod tests { ); assert_eq!(initial_snapshot.lines_added, 3); assert_eq!(initial_snapshot.lines_removed, 0); - let second_snapshot = &project.list_snapshots(10).unwrap()[0]; + let second_snapshot = &project.list_snapshots(10, None).unwrap()[0]; assert_eq!( second_snapshot.files_changed, vec![ diff --git a/crates/gitbutler-tauri/src/snapshots.rs b/crates/gitbutler-tauri/src/snapshots.rs index ac92eefb1..d1bc8b635 100644 --- a/crates/gitbutler-tauri/src/snapshots.rs +++ b/crates/gitbutler-tauri/src/snapshots.rs @@ -13,12 +13,13 @@ pub async fn list_snapshots( handle: tauri::AppHandle, project_id: ProjectId, limit: usize, + sha: Option, ) -> Result, Error> { let project = handle .state::() .get(&project_id) .context("failed to get project")?; - let snapshots = project.list_snapshots(limit)?; + let snapshots = project.list_snapshots(limit, sha)?; Ok(snapshots) }