Radish alpha
r
Radicle desktop app
Radicle
Git (anonymous pull)
Log in to clone via SSH
Start review from code-comment on revision diff
Brandon Oxendine committed 20 days ago
commit ec49ecbd36d057f0035dfa0c4933ec6a6f877709
parent e0aafa7de25ebf946ab26872d0bf411c2ab16675
7 files changed +132 -21
modified crates/radicle-types/bindings/cob/patch/Patch.ts
@@ -12,5 +12,5 @@ export type Patch = {
  assignees: Array<Author>;
  labels: Array<string>;
  timestamp: number;
-
  revisionCount: number;
+
  revisionIds: Array<string>;
};
modified crates/radicle-types/src/domain/patch/models/patch.rs
@@ -35,7 +35,8 @@ pub struct Patch {
    labels: Vec<cob::Label>,
    #[ts(type = "number")]
    timestamp: cob::Timestamp,
-
    revision_count: usize,
+
    #[ts(as = "Vec<String>")]
+
    revision_ids: Vec<patch::RevisionId>,
}

#[derive(Debug, thiserror::Error)]
@@ -63,7 +64,7 @@ impl Patch {
                .collect::<Vec<_>>(),
            labels: patch.labels().cloned().collect::<Vec<_>>(),
            timestamp: patch.timestamp(),
-
            revision_count: patch.revisions().count(),
+
            revision_ids: patch.revisions().map(|(id, _)| id).collect(),
        }
    }

modified src/components/PatchTeaser.svelte
@@ -2,6 +2,7 @@
  import type { PatchStatus } from "@app/views/repo/router";
  import type { Patch } from "@bindings/cob/patch/Patch";

+
  import { draftReviewStorage } from "@app/lib/draftReviewStorage";
  import { cachedDiffStats } from "@app/lib/invoke";
  import { push } from "@app/lib/router";
  import {
@@ -26,6 +27,10 @@
  }

  const { focussed, patch, rid, status }: Props = $props();
+

+
  const hasDraftReview = $derived(
+
    patch.revisionIds.some(id => draftReviewStorage.hasForRevision(id)),
+
  );
</script>

<style>
@@ -103,6 +108,18 @@
    </div>

    <div class="global-flex" style:margin-left="auto">
+
      {#if hasDraftReview}
+
        <div
+
          class="txt-body-m-regular"
+
          style:white-space="nowrap"
+
          style:border="1px solid var(--color-border-subtle)"
+
          style:border-radius="var(--border-radius-sm)"
+
          style:padding="0.125rem 0.5rem"
+
          style:color="var(--color-text-primary)">
+
          Review in progress
+
        </div>
+
      {/if}
+

      {#await cachedDiffStats(rid, patch.base, patch.head) then stats}
        <DiffStatBadge {stats} />
      {/await}
@@ -120,7 +137,7 @@
        style:padding="0 0.5rem"
        style:color="var(--color-text-tertiary)">
        <Icon name="revision" />
-
        {patch.revisionCount}
+
        {patch.revisionIds.length}
      </div>
    </div>
  </div>
modified src/components/Review.svelte
@@ -148,7 +148,6 @@
      if ("draft" in review) {
        draftReviewStorage.addComment(review.id, {
          body,
-
          embeds,
          location: location!,
        });
      } else {
@@ -178,7 +177,6 @@
      if ("draft" in review) {
        draftReviewStorage.updateComment(review.id, commentId, {
          body,
-
          embeds,
        });
      } else {
        await invoke("edit_patch", {
modified src/components/Revision.svelte
@@ -1,14 +1,17 @@
<script lang="ts">
+
  import type { CodeComments } from "@app/components/Diff.svelte";
  import type { Author } from "@bindings/cob/Author";
  import type { Revision } from "@bindings/cob/patch/Revision";
+
  import type { CodeLocation } from "@bindings/cob/thread/CodeLocation";
  import type { Embed } from "@bindings/cob/thread/Embed";
  import type { Thread } from "@bindings/cob/thread/Thread";
  import type { Config } from "@bindings/config/Config";

+
  import { draftReviewStorage } from "@app/lib/draftReviewStorage";
  import { nodeRunning } from "@app/lib/events";
  import { invoke } from "@app/lib/invoke";
  import * as roles from "@app/lib/roles";
-
  import { publicKeyFromDid } from "@app/lib/utils";
+
  import { didFromPublicKey, publicKeyFromDid } from "@app/lib/utils";

  import { announce } from "@app/components/AnnounceSwitch.svelte";
  import Changes from "@app/components/Changes.svelte";
@@ -35,6 +38,85 @@
    hideDescription = false,
  }: Props = $props();

+
  const currentUserAuthor: Author = $derived({
+
    did: didFromPublicKey(config.publicKey),
+
    alias: config.alias ?? undefined,
+
  });
+

+
  const draftReview = $derived(
+
    draftReviewStorage.getForRevision(revision.id, currentUserAuthor),
+
  );
+

+
  const hasPublishedReview = $derived(
+
    revision.reviews?.some(r => r.author.did === currentUserAuthor.did) ??
+
      false,
+
  );
+

+
  const codeCommentThreads: Thread<CodeLocation>[] = $derived(
+
    draftReview
+
      ? (draftReview.comments
+
          .filter(c => c.location && !c.replyTo)
+
          .map(root => ({
+
            root,
+
            replies: draftReview.comments.filter(c => c.replyTo === root.id),
+
          })) as Thread<CodeLocation>[])
+
      : [],
+
  );
+

+
  async function createCodeComment(
+
    body: string,
+
    _embeds: Embed[],
+
    _replyTo?: string,
+
    location?: CodeLocation,
+
  ) {
+
    if (!location) return;
+
    try {
+
      let draftId = draftReview?.id;
+
      if (!draftId) {
+
        draftId = draftReviewStorage.create(rid, revision.id);
+
      }
+
      draftReviewStorage.addComment(draftId, { body, location });
+
    } catch (error) {
+
      console.error("Creating code comment failed", error);
+
    } finally {
+
      await loadPatch();
+
    }
+
  }
+

+
  async function editCodeComment(
+
    commentId: string,
+
    body: string,
+
    _embeds: Embed[],
+
  ) {
+
    if (!draftReview) return;
+
    draftReviewStorage.updateComment(draftReview.id, commentId, {
+
      body,
+
    });
+
    await loadPatch();
+
  }
+

+
  async function deleteCodeComment(commentId: string) {
+
    if (!draftReview) return;
+
    draftReviewStorage.deleteComment(draftReview.id, commentId);
+
    await loadPatch();
+
  }
+

+
  const codeComments: CodeComments | undefined = $derived(
+
    hasPublishedReview
+
      ? undefined
+
      : {
+
          config,
+
          createComment: createCodeComment,
+
          editComment: editCodeComment,
+
          deleteComment: deleteCodeComment,
+
          repoDelegates,
+
          rid,
+
          threads: codeCommentThreads,
+
          canReply: true,
+
          disableAttachments: "Publish your review to attach files",
+
        },
+
  );
+

  const commentThreads = $derived(
    ((revision.discussion &&
      revision.discussion
@@ -206,4 +288,4 @@
  {repoDelegates}
  {rid} />

-
<Changes {rid} {patchId} {revision} />
+
<Changes {rid} {patchId} {revision} {codeComments} />
modified src/lib/draftReviewStorage.ts
@@ -5,7 +5,6 @@ import type { Verdict } from "@bindings/cob/patch/Verdict";
import type { CodeLocation } from "@bindings/cob/thread/CodeLocation";
import type { CodeRange } from "@bindings/cob/thread/CodeRange";
import type { Comment } from "@bindings/cob/thread/Comment";
-
import type { Embed } from "@bindings/cob/thread/Embed";

import { z } from "zod";

@@ -106,6 +105,12 @@ export const draftReviewStorage = {
    return id;
  },

+
  hasForRevision(revisionId: string): boolean {
+
    return Object.values(storage.value).some(
+
      review => review.revision === revisionId,
+
    );
+
  },
+

  update(
    id: string,
    props: { summary: string; verdict: Verdict | undefined; labels: string[] },
@@ -138,7 +143,6 @@ export const draftReviewStorage = {
    id: string,
    comment: {
      body: string;
-
      embeds: Embed[];
      location: CodeLocation;
    },
  ): string {
@@ -159,7 +163,6 @@ export const draftReviewStorage = {
    commentId: string,
    comment: {
      body: string;
-
      embeds: Embed[];
    },
  ) {
    updateStoredDraftReview(id, review => {
modified src/views/repo/Patch.svelte
@@ -171,9 +171,18 @@
      ...(selectedRevision.reviews ?? []),
    ].filter((review): review is Review | DraftReview => Boolean(review)),
  );
-
  const hasOwnReview = $derived(
+
  const ownDraftReview = $derived(
+
    reviewsOfSelectedRevision.find(
+
      value =>
+
        value.author.did === didFromPublicKey(config.publicKey) &&
+
        "draft" in value,
+
    ),
+
  );
+
  const hasOwnPublishedReview = $derived(
    reviewsOfSelectedRevision.some(
-
      value => value.author.did === didFromPublicKey(config.publicKey),
+
      value =>
+
        value.author.did === didFromPublicKey(config.publicKey) &&
+
        !("draft" in value),
    ),
  );
  const patchDescription = $derived(
@@ -327,12 +336,11 @@
                  patchId={patch.id} />
                <Button
                  variant="secondary"
-
                  disabled={hasOwnReview}
+
                  disabled={hasOwnPublishedReview}
                  onclick={() => {
-
                    const id = draftReviewStorage.create(
-
                      repo.rid,
-
                      selectedRevision.id,
-
                    );
+
                    const id =
+
                      ownDraftReview?.id ??
+
                      draftReviewStorage.create(repo.rid, selectedRevision.id);
                    void router.push({
                      resource: "repo.patch",
                      rid: repo.rid,
@@ -341,13 +349,15 @@
                      status,
                    });
                  }}
-
                  title={hasOwnReview
+
                  title={hasOwnPublishedReview
                    ? "You already created a review for this revision"
-
                    : "Review revision"}>
+
                    : ownDraftReview
+
                      ? "Continue review"
+
                      : "Review revision"}>
                  <Icon name="comment" />
                  <span
                    class="txt-body-m-regular global-hide-on-medium-desktop-down">
-
                    Review revision
+
                    {ownDraftReview ? "Continue review" : "Review revision"}
                  </span>
                </Button>
              </div>