Radish alpha
r
Radicle desktop app
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add `New Review` button to `Reviews` section in patch revisions
Sebastian Martinez committed 1 year ago
commit 0338e96d25e54bc33465af08cc6016a7cebba2e3
parent 450f6d6f0c0714940fe5a7dfeb5ad0b271161049
2 files changed +132 -34
modified src/components/ReviewTeaser.svelte
@@ -18,44 +18,89 @@

  const { rid, review }: Props = $props();

-
  const backgroundColor = {
-
    review: "var(--color-background-float)",
-
    accept: "var(--color-fill-diff-green-light)",
-
    reject: "var(--color-fill-diff-red-light)",
-
  };
+
  const backgroundColor = $derived.by(() => {
+
    if (review.verdict === "accept") {
+
      return "var(--color-fill-diff-green-light)";
+
    } else if (review.verdict === "reject") {
+
      return "var(--color-fill-diff-red-light)";
+
    } else {
+
      return "var(--color-fill-float-hover)";
+
    }
+
  });
+

+
  const header = $derived.by(() => {
+
    if (!review.verdict) {
+
      return "published a review";
+
    }
+

+
    return `${review.verdict ? `${review.verdict}ed` : "reviewed"} revision with a review`;
+
  });
+

+
  const icon = $derived.by(() => {
+
    if (review.verdict === "accept") {
+
      return "comment-checkmark";
+
    } else if (review.verdict === "reject") {
+
      return "comment-cross";
+
    } else {
+
      return "comment";
+
    }
+
  });
</script>

<style>
  .review {
    clip-path: var(--2px-corner-fill);
+
    display: flex;
+
    align-items: flex-start;
    padding: 0.5rem 0.75rem;
+
    gap: 1rem;
+
  }
+
  .review-content {
+
    width: 100%;
    font-size: var(--font-size-small);
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }
+
  .timestamp {
+
    color: var(--color-foreground-dim);
+
  }
+
  .review-header {
+
    display: flex;
+
    justify-content: space-between;
+
    align-items: center;
+
    width: 100%;
+
  }
+
  .icon {
+
    padding-top: 0.25rem;
+
  }
</style>

-
<div
-
  class="review"
-
  style:background-color={backgroundColor[review.verdict ?? "review"]}>
-
  <div class="global-flex">
-
    <NodeId {...authorForNodeId(review.author)} />
-
    {review.verdict ? `${review.verdict}ed` : "reviewed"} revision
-
    <div title={absoluteTimestamp(review.timestamp)}>
-
      {formatTimestamp(review.timestamp)}
-
    </div>
-
    {#if review.comments.length > 0}
-
      <div class="global-flex" style:gap="0.25rem" style:margin-left="auto">
-
        <Icon name="comment" />{review.comments.length}
-
      </div>
-
    {/if}
+
<div class="review" style:background-color={backgroundColor}>
+
  <div class="icon">
+
    <Icon name={icon} />
  </div>
-
  <div>
-
    {#if review.summary?.trim()}
-
      <Markdown {rid} breaks content={review.summary} />
-
    {:else}
-
      <span class="txt-missing">No summary.</span>
-
    {/if}
+
  <div class="review-content">
+
    <div class="review-header">
+
      <div class="global-flex">
+
        <NodeId {...authorForNodeId(review.author)} />
+
        <span>{header}</span>
+
        <div class="timestamp" title={absoluteTimestamp(review.timestamp)}>
+
          {formatTimestamp(review.timestamp)}
+
        </div>
+
        {#if review.comments.length > 0}
+
          <div class="global-flex" style:gap="0.25rem" style:margin-left="auto">
+
            <Icon name="comment" />{review.comments.length}
+
          </div>
+
        {/if}
+
      </div>
+
    </div>
+
    <div>
+
      {#if review.summary?.trim()}
+
        <Markdown {rid} breaks content={review.summary} />
+
      {:else}
+
        <span class="txt-missing">No summary.</span>
+
      {/if}
+
    </div>
  </div>
</div>
modified src/components/Revision.svelte
@@ -14,7 +14,11 @@
  import { announce } from "@app/components/AnnounceSwitch.svelte";
  import { invoke } from "@app/lib/invoke";
  import { nodeRunning } from "@app/lib/events";
-
  import { publicKeyFromDid, scrollIntoView } from "@app/lib/utils";
+
  import {
+
    didFromPublicKey,
+
    publicKeyFromDid,
+
    scrollIntoView,
+
  } from "@app/lib/utils";

  import Changeset from "@app/components/Changeset.svelte";
  import CobCommitTeaser from "./CobCommitTeaser.svelte";
@@ -41,6 +45,15 @@
    $props();
  /* eslint-enable prefer-const */

+
  const hasOwnReview = $derived(
+
    Boolean(
+
      revision.reviews &&
+
        revision.reviews.some(
+
          value => value.author.did === didFromPublicKey(config.publicKey),
+
        ),
+
    ),
+
  );
+

  let focusReply: boolean = $state(false);
  let hideChanges = $state(false);
  let hideDiscussion = $state(false);
@@ -128,6 +141,27 @@
    }
  }

+
  async function createReview() {
+
    try {
+
      await invoke("edit_patch", {
+
        rid: rid,
+
        cobId: patchId,
+
        action: {
+
          type: "review",
+
          revision: revision.id,
+
          // We need to pass an empty string to create a review without a verdict.
+
          summary: "",
+
          labels: [],
+
        },
+
        opts: { announce: $nodeRunning && $announce },
+
      });
+
    } catch (error) {
+
      console.error("Creating a review failed: ", error);
+
    } finally {
+
      await reload();
+
    }
+
  }
+

  async function editComment(commentId: string, body: string, embeds: Embed[]) {
    try {
      await invoke("edit_patch", {
@@ -143,7 +177,7 @@
        opts: { announce: $nodeRunning && $announce },
      });
    } catch (error) {
-
      console.error("Eediting comment failed: ", error);
+
      console.error("Editing comment failed: ", error);
    } finally {
      await reload();
    }
@@ -293,6 +327,11 @@
    left: -18.5px;
    background-color: var(--color-fill-separator);
  }
+
  .review-row {
+
    display: flex;
+
    align-items: center;
+
    justify-content: space-between;
+
  }
</style>

<div class="txt-small patch-body">
@@ -360,24 +399,38 @@
  </div>
</div>

-
{#if revision.reviews && revision.reviews.length}
-
  <div style:margin="1rem 0">
-
    <div class="global-flex" style:margin-bottom="1rem">
+
<div style:margin="1rem 0">
+
  <div class="review-row">
+
    <div
+
      class="global-flex"
+
      style:margin-bottom={hideReviews || revision.reviews?.length === 0
+
        ? undefined
+
        : "1rem"}>
      <NakedButton
        stylePadding="0 4px"
        variant="ghost"
        onclick={() => (hideReviews = !hideReviews)}>
-
        <Icon name={hideReviews ? "chevron-right" : "chevron-down"} />
+
        <Icon
+
          name={hideReviews || revision.reviews?.length === 0
+
            ? "chevron-right"
+
            : "chevron-down"} />
        <div class="txt-semibold global-flex txt-regular">Reviews</div>
      </NakedButton>
    </div>
-
    <div class:hide={hideReviews} style:margin-top="1rem">
+
    <NakedButton variant="ghost" disabled={hasOwnReview} onclick={createReview}>
+
      <Icon name="plus" />
+
      <span class="txt-small">New Review</span>
+
    </NakedButton>
+
  </div>
+

+
  {#if revision.reviews && revision.reviews.length}
+
    <div class:hide={hideReviews}>
      {#each revision.reviews as review}
        <ReviewTeaser {rid} {review} />
      {/each}
    </div>
-
  </div>
-
{/if}
+
  {/if}
+
</div>

<div
  class="txt-semibold global-flex"