diff --git a/src-tauri/bindings/PaginatedQuery.ts b/src-tauri/bindings/PaginatedQuery.ts new file mode 100644 index 000000000..e2ef2fbda --- /dev/null +++ b/src-tauri/bindings/PaginatedQuery.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PaginatedQuery = { cursor: number; more: boolean; content: T }; diff --git a/src-tauri/src/commands/cob.rs b/src-tauri/src/commands/cob.rs index d21c3c3bc..786583552 100644 --- a/src-tauri/src/commands/cob.rs +++ b/src-tauri/src/commands/cob.rs @@ -34,17 +34,25 @@ mod query { Draft, Archived, Merged, - All, } - impl PatchStatus { - pub fn matches(&self, patch: &patch::State) -> bool { - match self { - Self::Open => matches!(patch, patch::State::Open { .. }), - Self::Draft => matches!(patch, patch::State::Draft), - Self::Archived => matches!(patch, patch::State::Archived), - Self::Merged => matches!(patch, patch::State::Merged { .. }), - Self::All => true, + impl From for PatchStatus { + fn from(value: patch::Status) -> Self { + match value { + patch::Status::Archived => Self::Archived, + patch::Status::Draft => Self::Draft, + patch::Status::Merged => Self::Merged, + patch::Status::Open => Self::Open, + } + } + } + impl From for patch::Status { + fn from(value: PatchStatus) -> Self { + match value { + PatchStatus::Archived => Self::Archived, + PatchStatus::Draft => Self::Draft, + PatchStatus::Merged => Self::Merged, + PatchStatus::Open => Self::Open, } } } diff --git a/src-tauri/src/commands/cob/patch.rs b/src-tauri/src/commands/cob/patch.rs index 75dd9ba6e..59ad1f98c 100644 --- a/src-tauri/src/commands/cob/patch.rs +++ b/src-tauri/src/commands/cob/patch.rs @@ -2,36 +2,59 @@ use radicle::git; use radicle::identity::RepoId; use radicle::patch::cache::Patches; use radicle::storage::ReadStorage; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; -use crate::cob::query; use crate::error::Error; use crate::types::cobs; use crate::AppState; +use crate::cob::query; + +#[derive(Serialize, Deserialize, TS)] +#[ts(export)] +pub struct PaginatedQuery { + pub cursor: usize, + pub more: bool, + pub content: T, +} + #[tauri::command] -pub fn list_patches( - ctx: tauri::State, +pub async fn list_patches( + ctx: tauri::State<'_, AppState>, rid: RepoId, - status: query::PatchStatus, -) -> Result, Error> { + status: Option, + skip: Option, + take: Option, +) -> Result>, Error> { + let cursor = skip.unwrap_or(0); + let take = take.unwrap_or(20); let repo = ctx.profile.storage.repository(rid)?; - let patches = ctx.profile.patches(&repo)?; - let mut patches: Vec<_> = patches - .list()? - .filter_map(|r| { - let (id, patch) = r.ok()?; - (status.matches(patch.state())).then_some((id, patch)) - }) - .collect::>(); - - patches.sort_by(|(_, a), (_, b)| b.timestamp().cmp(&a.timestamp())); let aliases = &ctx.profile.aliases(); - let patches = patches + let cache = ctx.profile.patches(&repo)?; + let patches = match status { + None => cache.list()?.collect::>(), + Some(s) => cache.list_by_status(&s.into())?.collect::>(), + }; + let more = cursor + take < patches.len(); + + let mut patches = patches .into_iter() - .map(|(id, patch)| cobs::Patch::new(id, patch, aliases)) + .filter_map(|p| { + p.map(|(id, patch)| cobs::Patch::new(id, patch, aliases)) + .ok() + }) + .skip(cursor) + .take(take) .collect::>(); - Ok::<_, Error>(patches) + patches.sort_by_key(|b| std::cmp::Reverse(b.timestamp())); + + Ok::<_, Error>(PaginatedQuery { + cursor, + more, + content: patches, + }) } #[tauri::command] diff --git a/src-tauri/src/types/cobs.rs b/src-tauri/src/types/cobs.rs index ec312008e..90cb96f0b 100644 --- a/src-tauri/src/types/cobs.rs +++ b/src-tauri/src/types/cobs.rs @@ -119,6 +119,10 @@ impl Patch { revision_count: patch.revisions().count(), } } + + pub fn timestamp(&self) -> u64 { + self.timestamp.as_millis() + } } #[derive(Serialize, TS)] diff --git a/src/components/PatchTeaser.svelte b/src/components/PatchTeaser.svelte index 7ecdc743b..530fee5c4 100644 --- a/src/components/PatchTeaser.svelte +++ b/src/components/PatchTeaser.svelte @@ -9,6 +9,7 @@ patchStatusColor, } from "@app/lib/utils"; import { invoke } from "@tauri-apps/api/core"; + import { onMount } from "svelte"; import { push } from "@app/lib/router"; import DiffStatBadge from "./DiffStatBadge.svelte"; @@ -16,6 +17,16 @@ import InlineTitle from "./InlineTitle.svelte"; import NodeId from "./NodeId.svelte"; + let stats: Stats | undefined = undefined; + + onMount(async () => { + stats = await invoke("diff_stats", { + rid, + base: patch.base, + head: patch.head, + }); + }); + export let patch: Patch; export let rid: string; @@ -81,9 +92,9 @@
- {#await invoke( "diff_stats", { rid, base: patch.base, head: patch.head }, ) then stats} + {#if stats} - {/await} + {/if} {#each patch.labels as label}
{label}
{/each} diff --git a/src/views/repo/Issue.svelte b/src/views/repo/Issue.svelte index e7d634a73..ef5d0856f 100644 --- a/src/views/repo/Issue.svelte +++ b/src/views/repo/Issue.svelte @@ -52,7 +52,7 @@ } - +
- +
Patches
{project.meta.patches.draft + diff --git a/src/views/repo/Layout.svelte b/src/views/repo/Layout.svelte index 0da23ed82..3a1a11736 100644 --- a/src/views/repo/Layout.svelte +++ b/src/views/repo/Layout.svelte @@ -1,9 +1,30 @@ - + import type { Config } from "@bindings/Config"; + import type { PaginatedQuery } from "@bindings/PaginatedQuery"; import type { Patch } from "@bindings/Patch"; import type { PatchStatus } from "./router"; import type { RepoInfo } from "@bindings/RepoInfo"; - import Layout from "./Layout.svelte"; + import { invoke } from "@tauri-apps/api/core"; + import Layout from "./Layout.svelte"; import Border from "@app/components/Border.svelte"; import CopyableId from "@app/components/CopyableId.svelte"; import Icon from "@app/components/Icon.svelte"; @@ -15,9 +17,28 @@ import RepoHeader from "@app/components/RepoHeader.svelte"; export let repo: RepoInfo; - export let patches: Patch[]; + export let patches: PaginatedQuery; export let config: Config; - export let status: PatchStatus; + export let status: PatchStatus | undefined = undefined; + + $: items = patches.content; + $: more = patches.more; + $: cursor = patches.cursor; + + async function loadMore() { + if (more) { + const p = await invoke>("list_patches", { + rid: repo.rid, + skip: cursor + 20, + status, + take: 20, + }); + + cursor = p.cursor; + more = p.more; + items = [...items, ...p.content]; + } + } $: project = repo.payloads["xyz.radicle.project"]!; @@ -31,7 +52,7 @@ } - +
+ variant={status === undefined ? "active" : "tab"} + route={{ resource: "repo.patches", rid: repo.rid }}>
Patches
{project.meta.patches.draft + @@ -133,13 +154,13 @@
- {#each patches as patch} + {#each items as patch} {/each} - {#if patches.length === 0} + {#if patches.content.length === 0}
- {#if status === "all"} + {#if status === undefined} No patches. {:else} No {status} patches. diff --git a/src/views/repo/router.ts b/src/views/repo/router.ts index 0cc10e303..4238859ec 100644 --- a/src/views/repo/router.ts +++ b/src/views/repo/router.ts @@ -1,4 +1,5 @@ import type { Config } from "@bindings/Config"; +import type { PaginatedQuery } from "@bindings/PaginatedQuery"; import type { Issue } from "@bindings/Issue"; import type { Patch } from "@bindings/Patch"; import type { RepoInfo } from "@bindings/RepoInfo"; @@ -41,7 +42,7 @@ export interface LoadedRepoIssuesRoute { }; } -export type PatchStatus = "all" | Patch["state"]["status"]; +export type PatchStatus = Patch["state"]["status"]; export interface RepoPatchRoute { resource: "repo.patch"; @@ -63,7 +64,7 @@ export interface LoadedRepoPatchRoute { export interface RepoPatchesRoute { resource: "repo.patches"; rid: string; - status: PatchStatus; + status?: PatchStatus; } export interface LoadedRepoPatchesRoute { @@ -71,8 +72,8 @@ export interface LoadedRepoPatchesRoute { params: { repo: RepoInfo; config: Config; - patches: Patch[]; - status: PatchStatus; + patches: PaginatedQuery; + status?: PatchStatus; }; } @@ -94,9 +95,8 @@ export async function loadPatch( rid: route.rid, }); const config: Config = await invoke("config"); - const patches: Patch[] = await invoke("list_patches", { + const patches: PaginatedQuery = await invoke("list_patches", { rid: route.rid, - status: "all", }); const patch: Patch = await invoke("patch_by_id", { rid: route.rid, @@ -109,7 +109,7 @@ export async function loadPatch( return { resource: "repo.patch", - params: { repo, config, patch, patches, revisions }, + params: { repo, config, patch, patches: patches.content, revisions }, }; } @@ -120,7 +120,7 @@ export async function loadPatches( rid: route.rid, }); const config: Config = await invoke("config"); - const patches: Patch[] = await invoke("list_patches", { + const patches: PaginatedQuery = await invoke("list_patches", { rid: route.rid, status: route.status, }); @@ -243,7 +243,7 @@ export function repoUrlToRoute( ) { return { resource: "repo.patches", rid, status }; } else { - return { resource: "repo.patches", rid, status: "all" }; + return { resource: "repo.patches", rid }; } } } else {