diff --git a/Cargo.lock b/Cargo.lock index b96413579..febcef896 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3975,8 +3975,7 @@ dependencies = [ [[package]] name = "radicle" version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd823aeed3ffe73eb82a213e62cb3811f9bdf453844d6e0b14684e0757fb389b" +source = "git+https://ash.radicle.garden/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git?rev=7c902b6905724345ba850eb6cca8f8becc9a9c72#7c902b6905724345ba850eb6cca8f8becc9a9c72" dependencies = [ "amplify", "base64 0.21.7", @@ -4007,8 +4006,7 @@ dependencies = [ [[package]] name = "radicle-cob" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90581a9508ccc310998e991d7acf139d2991297d3fb37d30de07536e10256afb" +source = "git+https://ash.radicle.garden/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git?rev=7c902b6905724345ba850eb6cca8f8becc9a9c72#7c902b6905724345ba850eb6cca8f8becc9a9c72" dependencies = [ "fastrand", "git2", @@ -4026,8 +4024,7 @@ dependencies = [ [[package]] name = "radicle-crypto" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1d6a67969719841ad06049597006368eb4238ca63a02d20207654dfd1d2d6ad" +source = "git+https://ash.radicle.garden/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git?rev=7c902b6905724345ba850eb6cca8f8becc9a9c72#7c902b6905724345ba850eb6cca8f8becc9a9c72" dependencies = [ "amplify", "cyphernet", @@ -4047,8 +4044,7 @@ dependencies = [ [[package]] name = "radicle-dag" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb41c7e10ada3a4df960190a96bfb4af56d33ada890f917acc8e3b122b614875" +source = "git+https://ash.radicle.garden/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git?rev=7c902b6905724345ba850eb6cca8f8becc9a9c72#7c902b6905724345ba850eb6cca8f8becc9a9c72" dependencies = [ "fastrand", ] @@ -4070,8 +4066,7 @@ dependencies = [ [[package]] name = "radicle-ssh" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbee758010fb64482be4b18591fbeb3cbc15b16450d143edf4edb5484c7366c6" +source = "git+https://ash.radicle.garden/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git?rev=7c902b6905724345ba850eb6cca8f8becc9a9c72#7c902b6905724345ba850eb6cca8f8becc9a9c72" dependencies = [ "byteorder", "log", diff --git a/crates/radicle-tauri/Cargo.toml b/crates/radicle-tauri/Cargo.toml index 8f672758a..1a1ece99d 100644 --- a/crates/radicle-tauri/Cargo.toml +++ b/crates/radicle-tauri/Cargo.toml @@ -18,7 +18,7 @@ tauri-build = { version = "2.0.1", features = ["isolation"] } anyhow = { version = "1.0.90" } base64 = { version = "0.22.1" } log = { version = "0.4.22" } -radicle = { version = "0.14.0" } +radicle = { git = "https://ash.radicle.garden/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git", package = "radicle", rev = "7c902b6905724345ba850eb6cca8f8becc9a9c72" } radicle-types = { version = "0.1.0", path = "../radicle-types" } radicle-surf = { version = "0.22.1", features = ["serde"] } serde = { version = "1.0.210", features = ["derive"] } diff --git a/crates/radicle-types/Cargo.toml b/crates/radicle-types/Cargo.toml index 1fc558f27..e906562ee 100644 --- a/crates/radicle-types/Cargo.toml +++ b/crates/radicle-types/Cargo.toml @@ -11,7 +11,7 @@ localtime = { version = "1.3.1" } log = { version = "0.4.22" } infer = { version = "0.3" } mime-infer = { version = "3.0.0" } -radicle = { version = "0.14.0", features = ["test"] } +radicle = { git = "https://ash.radicle.garden/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git", package = "radicle", rev = "7c902b6905724345ba850eb6cca8f8becc9a9c72", features = ["test"] } radicle-surf = { version = "0.22.1", features = ["serde"] } serde = { version = "1.0.210", features = ["derive"] } serde_json = { version = "1.0.132" } diff --git a/crates/radicle-types/bindings/cob/patch/Review.ts b/crates/radicle-types/bindings/cob/patch/Review.ts index 6428c10f0..93ec0f27b 100644 --- a/crates/radicle-types/bindings/cob/patch/Review.ts +++ b/crates/radicle-types/bindings/cob/patch/Review.ts @@ -11,4 +11,5 @@ export type Review = { summary?: string; comments: Array>; timestamp: number; + labels: Array; }; diff --git a/crates/radicle-types/src/domain/patch/models/patch.rs b/crates/radicle-types/src/domain/patch/models/patch.rs index b931f9cb6..3c43b1379 100644 --- a/crates/radicle-types/src/domain/patch/models/patch.rs +++ b/crates/radicle-types/src/domain/patch/models/patch.rs @@ -253,6 +253,8 @@ pub struct Review { comments: Vec>, #[ts(type = "number")] timestamp: cob::common::Timestamp, + #[ts(as = "Vec")] + labels: Vec, } impl Review { @@ -262,6 +264,7 @@ impl Review { author: cobs::Author::new(&review.author().id, aliases), verdict: review.verdict().map(|v| v.into()), summary: review.summary().map(|s| s.to_string()), + labels: review.labels().cloned().collect::>(), comments: review .comments() .map(|(id, c)| { diff --git a/crates/test-http-api/Cargo.toml b/crates/test-http-api/Cargo.toml index 778525adc..784aa3192 100644 --- a/crates/test-http-api/Cargo.toml +++ b/crates/test-http-api/Cargo.toml @@ -10,7 +10,7 @@ anyhow = { version = "1.0.90" } axum = { version = "0.7.5", default-features = false, features = ["json", "query", "tokio", "http1"] } hyper = { version = "1.4", default-features = false } lexopt = { version = "0.3.0" } -radicle = { version = "0.14.0" } +radicle = { git = "https://ash.radicle.garden/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git", package = "radicle", rev = "7c902b6905724345ba850eb6cca8f8becc9a9c72" } radicle-surf = { version = "0.22.1", default-features = false, features = ["serde"] } radicle-types = { path = "../radicle-types" } serde = { version = "1", features = ["derive"] } diff --git a/src/components/Border.svelte b/src/components/Border.svelte index 0029ca7ac..a2a1b99ae 100644 --- a/src/components/Border.svelte +++ b/src/components/Border.svelte @@ -3,7 +3,7 @@ interface Props { children: Snippet; - variant: "primary" | "secondary" | "ghost" | "float" | "danger"; + variant: "primary" | "secondary" | "ghost" | "float" | "danger" | "success"; hoverable?: boolean; onclick?: () => void; stylePosition?: string; diff --git a/src/components/Comment.svelte b/src/components/Comment.svelte index b167e62c2..c7a69860e 100644 --- a/src/components/Comment.svelte +++ b/src/components/Comment.svelte @@ -20,23 +20,27 @@ interface Props { actions?: Snippet; + beforeTimestamp?: Snippet; id?: string; rid: string; author: Author; - body: string; + body?: string; reactions?: Reaction[]; embeds?: Map; caption?: string; timestamp: number; lastEdit?: Edit; disallowEmptyBody?: boolean; + emptyBodyTooltip?: string; editComment?: (body: string, embeds: Embed[]) => Promise; reactOnComment?: (authors: Author[], reaction: string) => Promise; + styleWidth?: string; } /* eslint-disable prefer-const */ let { actions, + beforeTimestamp, id, rid, author, @@ -49,6 +53,8 @@ disallowEmptyBody = false, editComment, reactOnComment, + styleWidth, + emptyBodyTooltip, }: Props = $props(); /* eslint-enable prefer-const */ @@ -120,7 +126,7 @@ } -
+
@@ -128,6 +134,9 @@ {#if id} {/if} + {#if beforeTimestamp} + {@render beforeTimestamp()} + {/if} {utils.formatTimestamp(timestamp)} @@ -165,7 +174,7 @@
- {#if body.trim() === "" && state === "read"} + {#if (body === undefined || body?.trim() === "") && state === "read"}
No description. @@ -181,6 +190,7 @@ {rid} {embeds} {disallowEmptyBody} + {emptyBodyTooltip} borderVariant="ghost" submitInProgress={state === "submit"} submitCaption="Save" @@ -202,7 +212,7 @@ {:else}
- +
{/if} diff --git a/src/components/DropdownListItem.svelte b/src/components/DropdownListItem.svelte index dd8d6eeaf..039a7da1e 100644 --- a/src/components/DropdownListItem.svelte +++ b/src/components/DropdownListItem.svelte @@ -67,6 +67,11 @@ class:disabled {style} {title} - {onclick}> + onclick={() => { + if (disabled) { + return; + } + onclick(); + }}> {@render children()}
diff --git a/src/components/ExtendedTextarea.svelte b/src/components/ExtendedTextarea.svelte index fd082dc97..17e9bde3e 100644 --- a/src/components/ExtendedTextarea.svelte +++ b/src/components/ExtendedTextarea.svelte @@ -29,6 +29,7 @@ submitInProgress?: boolean; disableSubmit?: boolean; disallowEmptyBody?: boolean; + emptyBodyTooltip?: string; isValid?: () => boolean; preview?: boolean; stylePadding?: string; @@ -55,6 +56,7 @@ submitInProgress = false, disableSubmit = false, disallowEmptyBody = false, + emptyBodyTooltip, isValid = () => true, stylePadding, borderVariant = "float", @@ -293,6 +295,7 @@ + {/snippet} + {#snippet popover()} + + + {#snippet item(action)} + { + await onSelect(action); + closeFocused(); + }}> + + + {action ? capitalize(`${action}ed`) : "None"} + + + {/snippet} + + + {/snippet} + diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 10c8616b4..70b518af6 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -3,6 +3,7 @@ import type { ComponentProps } from "svelte"; import type { Author } from "@bindings/cob/Author"; import type { Issue } from "@bindings/cob/issue/Issue"; import type { Patch } from "@bindings/cob/patch/Patch"; +import type { Review } from "@bindings/cob/patch/Review"; import bs58 from "bs58"; import twemojiModule from "twemoji"; @@ -237,3 +238,23 @@ export function gravatarURL(email: string): string { return `https://www.gravatar.com/avatar/${hash}`; } + +export function verdictIcon(verdict: Review["verdict"]) { + if (verdict === "accept") { + return "comment-checkmark"; + } else if (verdict === "reject") { + return "comment-cross"; + } else { + return "comment"; + } +} + +export function verdictIconColor(verdict: Review["verdict"]) { + if (verdict === "accept") { + return "var(--color-foreground-success)"; + } else if (verdict === "reject") { + return "var(--color-foreground-red)"; + } else { + return "var(--color-foreground-dim)"; + } +} diff --git a/src/views/repo/Patch.svelte b/src/views/repo/Patch.svelte index 540098780..0a0e05666 100644 --- a/src/views/repo/Patch.svelte +++ b/src/views/repo/Patch.svelte @@ -7,6 +7,7 @@ import type { Patch } from "@bindings/cob/patch/Patch"; import type { PatchStatus } from "./router"; import type { RepoInfo } from "@bindings/repo/RepoInfo"; + import type { Review } from "@bindings/cob/patch/Review"; import type { Revision } from "@bindings/cob/patch/Revision"; import capitalize from "lodash/capitalize"; @@ -37,6 +38,7 @@ import PatchTeaser from "@app/components/PatchTeaser.svelte"; import PatchTimeline from "@app/components/PatchTimeline.svelte"; import Popover, { closeFocused } from "@app/components/Popover.svelte"; + import ReviewComponent from "@app/components/Review.svelte"; import RevisionBadges from "@app/components/RevisionBadges.svelte"; import RevisionComponent from "@app/components/Revision.svelte"; import RevisionSelector from "@app/components/RevisionSelector.svelte"; @@ -52,6 +54,7 @@ config: Config; activity: Operation[]; status: PatchStatus | undefined; + review: Review | undefined; } /* eslint-disable prefer-const */ @@ -63,6 +66,7 @@ config, status: initialStatus, activity, + review, }: Props = $props(); /* eslint-enable prefer-const */ @@ -110,6 +114,7 @@ rid: repo.rid, id: patch.id, }); + review = undefined; } async function editTitle(rid: string, patchId: string, title: string) { @@ -213,7 +218,7 @@ } } - async function reload() { + async function reload(reviewId?: string) { [config, repo, patches, patch, revisions, activity] = await Promise.all([ invoke("config"), invoke("repo_by_id", { @@ -236,6 +241,9 @@ id: patch.id, }), ]); + review = revisions + .flatMap(r => r.reviews || []) + .find(review => review.id === reviewId); } async function loadPatches(filter: PatchStatus | undefined) { @@ -430,55 +438,22 @@
{/snippet} -
-
- {#if editingTitle} -
-
- -
- - 0} - bind:value={updatedTitle} - autofocus - onSubmit={async () => { - if (updatedTitle.trim().length > 0) { - await editTitle(repo.rid, patch.id, updatedTitle); - } - }} - onDismiss={() => { - updatedTitle = patch.title; - editingTitle = !editingTitle; - }} /> -
- { - if (updatedTitle.trim().length > 0) { - await editTitle(repo.rid, patch.id, updatedTitle); - } - }} /> - { - updatedTitle = patch.title; - editingTitle = !editingTitle; - }} /> - -
-
- {:else} -
-
+ {#if review} + { + review = undefined; + }} /> + {:else} +
+
+ {#if editingTitle} +
- -
- {#if roles.isDelegateOrAuthor( config.publicKey, repo.delegates.map(delegate => delegate.did), patch.author.did, )} + + 0} + bind:value={updatedTitle} + autofocus + onSubmit={async () => { + if (updatedTitle.trim().length > 0) { + await editTitle(repo.rid, patch.id, updatedTitle); + } + }} + onDismiss={() => { + updatedTitle = patch.title; + editingTitle = !editingTitle; + }} />
- (editingTitle = !editingTitle)} /> + { + if (updatedTitle.trim().length > 0) { + await editTitle(repo.rid, patch.id, updatedTitle); + } + }} /> + { + updatedTitle = patch.title; + editingTitle = !editingTitle; + }} />
- {/if} -
- {/if} -
- - - - - - - - - - - - -
- -
- - Revisions - - { - tab = "patch"; - }}> - {formatOid(patch.id)} - - Initial - - - {#if revisions.length > 1} - { - tab = "revisions"; - }}> - {formatOid(selectedRevision.id)} -
- - { - selectedRevision = rev; - tab = "revisions"; - }} /> -
-
- {/if} - -
- { - tab = "timeline"; - }}> - - Timeline -
+ {:else} +
+
+
+ +
+ +
+ {#if roles.isDelegateOrAuthor( config.publicKey, repo.delegates.map(delegate => delegate.did), patch.author.did, )} +
+ (editingTitle = !editingTitle)} /> + +
+ {/if} +
+ {/if} +
+ + + + + + + + + + -
- - {#if tab === "patch"} - - {:else if tab === "timeline"} - - {:else} - - {/if} - -
+
+ +
+ + Revisions + + { + tab = "patch"; + }}> + {formatOid(patch.id)} + + Initial + + + {#if revisions.length > 1} + { + tab = "revisions"; + }}> + {formatOid(selectedRevision.id)} +
+ + { + selectedRevision = rev; + tab = "revisions"; + }} /> +
+
+ {/if} + +
+ { + tab = "timeline"; + }}> + + Timeline + +
+
+
+
+ + + {#if tab === "patch"} + + {:else if tab === "timeline"} + + {:else} + + {/if} + +
+ {/if} diff --git a/src/views/repo/router.ts b/src/views/repo/router.ts index 5dbf11686..e6e1e612d 100644 --- a/src/views/repo/router.ts +++ b/src/views/repo/router.ts @@ -1,13 +1,14 @@ import type { Action as IssueAction } from "@bindings/cob/issue/Action"; import type { Action as PatchAction } from "@bindings/cob/patch/Action"; import type { Config } from "@bindings/config/Config"; -import type { Thread } from "@bindings/cob/thread/Thread"; import type { Issue } from "@bindings/cob/issue/Issue"; import type { Operation } from "@bindings/cob/Operation"; import type { PaginatedQuery } from "@bindings/cob/PaginatedQuery"; import type { Patch } from "@bindings/cob/patch/Patch"; import type { RepoInfo } from "@bindings/repo/RepoInfo"; +import type { Review } from "@bindings/cob/patch/Review"; import type { Revision } from "@bindings/cob/patch/Revision"; +import type { Thread } from "@bindings/cob/thread/Thread"; import { invoke } from "@app/lib/invoke"; import { unreachable } from "@app/lib/utils"; @@ -73,6 +74,7 @@ export interface RepoPatchRoute { rid: string; patch: string; status: PatchStatus | undefined; + reviewId: string | undefined; } export interface LoadedRepoPatchRoute { @@ -83,6 +85,7 @@ export interface LoadedRepoPatchRoute { patch: Patch; patches: PaginatedQuery; status: PatchStatus | undefined; + review: Review | undefined; revisions: Revision[]; activity: Operation[]; }; @@ -145,6 +148,10 @@ export async function loadPatch( ], ); + const review = revisions + .flatMap(r => r.reviews || []) + .find(review => review.id === route.reviewId); + return { resource: "repo.patch", params: { @@ -154,6 +161,7 @@ export async function loadPatch( patches, revisions, status: route.status, + review, activity, }, }; @@ -332,12 +340,14 @@ export function repoUrlToRoute( const status = (searchParams.get("status") ?? undefined) as | PatchStatus | undefined; + const reviewId = searchParams.get("reviewId") ?? undefined; if (id) { return { resource: "repo.patch", rid, patch: id, status, + reviewId, }; } else { return { resource: "repo.patches", rid, status };