Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Refactor some of the reaction components
Sebastian Martinez committed 2 years ago
commit aec0289f101f594c160c5b12402a4d28d6d90b15
parent 26447b3c59ff78902c1da40898bc3ff71c7dea24
7 files changed +205 -126
modified src/components/Comment.svelte
@@ -5,9 +5,10 @@
  import { httpdStore } from "@app/lib/httpd";

  import Authorship from "@app/components/Authorship.svelte";
-
  import Button from "@app/components/Button.svelte";
+
  import Floating, { closeFocused } from "@app/components/Floating.svelte";
  import Icon from "@app/components/Icon.svelte";
  import Markdown from "@app/components/Markdown.svelte";
+
  import ReactionSelector from "@app/components/ReactionSelector.svelte";
  import Reactions from "@app/components/Reactions.svelte";
  import Textarea from "@app/components/Textarea.svelte";

@@ -27,6 +28,11 @@
    toggleReply: null;
    react: { nids: string[]; id: string; reaction: string };
  }>();
+

+
  $: groupedReactions = reactions?.reduce(
+
    (acc, [nid, emoji]) => acc.set(emoji, [...(acc.get(emoji) ?? []), nid]),
+
    new Map<string, string[]>(),
+
  );
</script>

<style>
@@ -49,14 +55,19 @@
  }
  .actions {
    display: flex;
+
    flex-direction: row;
    justify-content: flex-end;
+
    align-items: center;
+
    gap: 0.5rem;
  }
-
  .reactions {
-
    margin-top: 1rem;
+
  .reaction-selector {
+
    position: absolute;
+
    top: 2rem;
+
    right: 0;
  }
-
  .action {
-
    display: flex;
-
    gap: 0.5rem;
+
  .toggle:hover {
+
    color: var(--color-foreground-5);
+
    cursor: pointer;
  }
</style>

@@ -70,15 +81,29 @@
      {timestamp} />
    <div class="actions">
      {#if showReplyIcon}
-
        <Button
-
          variant="text"
-
          size="tiny"
-
          on:click={() => dispatch("toggleReply")}>
-
          <div class="action">
-
            <Icon name="chat" />
-
            <span>reply</span>
-
          </div>
-
        </Button>
+
        <div class="toggle" title="toggle-reply">
+
          <Icon on:click={() => dispatch("toggleReply")} name="arrow-reply" />
+
        </div>
+
      {/if}
+
      {#if id && $httpdStore.state === "authenticated"}
+
        <div style:position="relative">
+
          <Floating>
+
            <div class="reaction-selector" slot="modal">
+
              <ReactionSelector
+
                nid={$httpdStore.session.publicKey}
+
                reactions={groupedReactions}
+
                on:select={event => {
+
                  if (id) {
+
                    dispatch("react", { id, ...event.detail });
+
                    closeFocused();
+
                  }
+
                }} />
+
            </div>
+
            <div class="toggle" title="toggle-reaction" slot="toggle">
+
              <Icon name="face" />
+
            </div>
+
          </Floating>
+
        </div>
      {/if}
    </div>
  </div>
@@ -94,9 +119,16 @@
    {:else}
      <Markdown {rawPath} content={body} />
    {/if}
-
    {#if id && (reactions.length > 0 || $httpdStore.state === "authenticated")}
-
      <div class="reactions">
-
        <Reactions {id} {reactions} on:react />
+
    {#if id && groupedReactions.size > 0}
+
      <div style:margin-top="1rem">
+
        <Reactions
+
          reactions={groupedReactions}
+
          clickable={Boolean($httpdStore.state === "authenticated")}
+
          on:remove={event => {
+
            if (id) {
+
              dispatch("react", { id, ...event.detail });
+
            }
+
          }} />
      </div>
    {/if}
  </div>
modified src/components/Icon.svelte
@@ -3,6 +3,7 @@

  export let size: "small" | "regular" = "regular";
  export let name:
+
    | "arrow-reply"
    | "browse"
    | "chat"
    | "checkmark"
@@ -50,7 +51,23 @@
  width={size === "regular" ? "24" : "16"}
  fill="currentColor"
  viewBox="0 0 24 24">
-
  {#if name === "browse"}
+
  {#if name === "arrow-reply"}
+
    <path
+
      fill-rule="evenodd"
+
      clip-rule="evenodd"
+
      d="M4.64645 10.6464C4.45118
+
      10.8417 4.45118 11.1583 4.64645 11.3536L9.64645 16.3536C9.84171 16.5488
+
      10.1583 16.5488 10.3536 16.3536C10.5488 16.1583 10.5488 15.8417 10.3536
+
      15.6464L5.70711 11L10.3536 6.35355C10.5488 6.15829 10.5488 5.84171 10.3536
+
      5.64645C10.1583 5.45118 9.84171 5.45118 9.64645 5.64645L4.64645 10.6464Z" />
+
    <path
+
      fill-rule="evenodd"
+
      clip-rule="evenodd"
+
      d="M4.5 11C4.5 10.7239
+
      4.72386 10.5 5 10.5H18C18.8284 10.5 19.5 11.1716 19.5 12V18C19.5 18.2761
+
      19.2761 18.5 19 18.5C18.7239 18.5 18.5 18.2761 18.5 18V12C18.5 11.7239
+
      18.2761 11.5 18 11.5H5C4.72386 11.5 4.5 11.2761 4.5 11Z" />
+
  {:else if name === "browse"}
    <path
      fill-rule="evenodd"
      clip-rule="evenodd"
added src/components/ReactionSelector.svelte
@@ -0,0 +1,54 @@
+
<script lang="ts">
+
  import { createEventDispatcher } from "svelte";
+

+
  import config from "@app/config.json";
+

+
  export let nid: string;
+
  export let reactions: Map<string, string[]>;
+

+
  const dispatch = createEventDispatcher<{
+
    select: { nids: string[]; reaction: string };
+
  }>();
+
</script>
+

+
<style>
+
  .selector {
+
    display: flex;
+
    align-items: center;
+
    background-color: var(--color-background-1);
+
    border-radius: var(--border-radius-small);
+
    border: 1px solid var(--color-foreground-3);
+
    box-shadow: var(--elevation-low);
+
    padding: 0.2rem;
+
    gap: 0.2rem;
+
  }
+
  .selector button {
+
    padding: 0.5rem;
+
    border: 0;
+
    background-color: transparent;
+
  }
+
  .selector button.active {
+
    border-radius: var(--border-radius-small);
+
    background-color: var(--color-background);
+
  }
+
  .selector button:hover {
+
    cursor: pointer;
+
    border-radius: var(--border-radius-small);
+
    background-color: var(--color-background);
+
  }
+
</style>
+

+
<div class="selector">
+
  {#each config.reactions as reaction}
+
    <button
+
      class:active={reactions.get(reaction)?.includes(nid)}
+
      on:click={() => {
+
        dispatch("select", {
+
          nids: reactions.get(reaction) ?? [],
+
          reaction,
+
        });
+
      }}>
+
      {reaction}
+
    </button>
+
  {/each}
+
</div>
modified src/components/Reactions.svelte
@@ -2,27 +2,17 @@
  import { createEventDispatcher } from "svelte";

  import Chip from "@app/components/Chip.svelte";
-
  import Floating, { closeFocused } from "@app/components/Floating.svelte";
-
  import Icon from "@app/components/Icon.svelte";
-
  import config from "@app/config.json";
-
  import { httpdStore } from "@app/lib/httpd";

-
  export let id: string;
-
  export let reactions: [string, string][];
+
  export let reactions: Map<string, string[]>;
+
  export let clickable: boolean = false;

  const dispatch = createEventDispatcher<{
-
    react: { nids: string[]; id: string; reaction: string };
+
    remove: { nids: string[]; reaction: string };
  }>();
-

-
  $: groupedReactions = reactions?.reduce(
-
    (acc, [nid, emoji]) => acc.set(emoji, [...(acc.get(emoji) ?? []), nid]),
-
    new Map<string, string[]>(),
-
  );
</script>

<style>
-
  section {
-
    position: relative;
+
  .reactions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
@@ -31,84 +21,23 @@
    display: inline-flex;
    flex-direction: row;
    gap: 0.5rem;
-
    font-size: var(--font-size-tiny);
-
  }
-
  .toggle {
-
    /* Height of one reaction */
-
    height: 1.525rem;
-
  }
-
  .toggle:hover {
-
    color: var(--color-foreground-5);
-
  }
-
  .reaction-selector {
-
    position: absolute;
-
    bottom: 2.2rem;
-
    display: flex;
-
    align-items: center;
-
    background-color: var(--color-background-1);
-
    border-radius: var(--border-radius-small);
-
    border: 1px solid var(--color-foreground-3);
-
    box-shadow: var(--elevation-low);
-
    padding: 0.2rem;
-
    gap: 0.2rem;
-
  }
-
  .reaction-selector button {
-
    padding: 0.5rem;
-
    border: 0;
-
    background-color: transparent;
-
  }
-
  .reaction-selector button.active {
-
    border-radius: var(--border-radius-small);
-
    background-color: var(--color-background);
-
  }
-
  .reaction-selector button:hover {
-
    cursor: pointer;
-
    border-radius: var(--border-radius-small);
-
    background-color: var(--color-background);
  }
</style>

-
<section>
-
  {#if $httpdStore.state === "authenticated"}
-
    <Floating>
-
      <div class="reaction-selector" slot="modal">
-
        {#each config.reactions as reaction}
-
          <button
-
            class:active={groupedReactions
-
              .get(reaction)
-
              ?.includes($httpdStore.session.publicKey)}
-
            on:click={() => {
-
              dispatch("react", {
-
                nids: groupedReactions.get(reaction) ?? [],
-
                id,
-
                reaction,
-
              });
-
              closeFocused();
-
            }}>
-
            {reaction}
-
          </button>
-
        {/each}
-
      </div>
-
      <div slot="toggle" class="toggle">
-
        <Icon name="face" />
+
<div class="reactions">
+
  {#each reactions as [reaction, nids], key}
+
    <Chip
+
      {key}
+
      {clickable}
+
      on:click={() => {
+
        if (clickable) {
+
          dispatch("remove", { nids, reaction });
+
        }
+
      }}>
+
      <div class="reaction txt-tiny">
+
        <span>{reaction}</span>
+
        <span title={nids.join("\n")}>{nids.length}</span>
      </div>
-
    </Floating>
-
  {/if}
-
  {#if groupedReactions.size > 0}
-
    {#each groupedReactions as [reaction, nids], key}
-
      <Chip
-
        {key}
-
        clickable={$httpdStore.state === "authenticated"}
-
        on:click={() => {
-
          if ($httpdStore.state === "authenticated") {
-
            dispatch("react", { nids, id, reaction });
-
          }
-
        }}>
-
        <div class="reaction">
-
          <span>{reaction}</span>
-
          <span title={nids.join("\n")}>{nids.length}</span>
-
        </div>
-
      </Chip>
-
    {/each}
-
  {/if}
-
</section>
+
    </Chip>
+
  {/each}
+
</div>
modified src/views/projects/Issue.svelte
@@ -18,13 +18,15 @@
  import CobHeader from "@app/views/projects/Cob/CobHeader.svelte";
  import CobStateButton from "@app/views/projects/Cob/CobStateButton.svelte";
  import ErrorModal from "@app/views/projects/Cob/ErrorModal.svelte";
+
  import Floating, { closeFocused } from "@app/components/Floating.svelte";
  import Icon from "@app/components/Icon.svelte";
  import LabelInput from "./Cob/LabelInput.svelte";
  import Layout from "./Layout.svelte";
  import Markdown from "@app/components/Markdown.svelte";
+
  import ReactionSelector from "@app/components/ReactionSelector.svelte";
+
  import Reactions from "@app/components/Reactions.svelte";
  import Textarea from "@app/components/Textarea.svelte";
  import ThreadComponent from "@app/components/Thread.svelte";
-
  import Reactions from "@app/components/Reactions.svelte";

  export let baseUrl: BaseUrl;
  export let issue: Issue;
@@ -81,8 +83,14 @@
  }

  async function handleReaction({
-
    detail: { nids, id, reaction },
-
  }: CustomEvent<{ nids: string[]; id: string; reaction: string }>) {
+
    nids,
+
    id,
+
    reaction,
+
  }: {
+
    nids: string[];
+
    id: string;
+
    reaction: string;
+
  }) {
    if ($httpdStore.state === "authenticated") {
      try {
        const status = await updateIssue(
@@ -263,6 +271,10 @@
          .sort((a, b) => a.timestamp - b.timestamp),
      };
    }, []);
+
  $: issueReactions = issue.discussion[0].reactions?.reduce(
+
    (acc, [nid, emoji]) => acc.set(emoji, [...(acc.get(emoji) ?? []), nid]),
+
    new Map<string, string[]>(),
+
  );

  let commentBody: string = "";
</script>
@@ -298,7 +310,11 @@
    gap: 0.5rem;
  }
  .reactions {
-
    margin-top: 1rem;
+
    position: relative;
+
    display: flex;
+
    align-items: center;
+
    flex-direction: row;
+
    gap: 0.5rem;
  }
  .thread {
    margin: 1rem 0;
@@ -309,6 +325,18 @@
  .closed {
    color: var(--color-negative-6);
  }
+
  .reaction-selector {
+
    position: absolute;
+
    bottom: 2rem;
+
    left: 0;
+
  }
+
  .toggle {
+
    margin-top: 1rem;
+
  }
+
  .toggle:hover {
+
    color: var(--color-foreground-5);
+
    cursor: pointer;
+
  }

  @media (max-width: 960px) {
    .issue {
@@ -354,14 +382,33 @@
          <Markdown
            content={issue.discussion[0].body}
            rawPath={utils.getRawBasePath(project.id, baseUrl, project.head)} />
-
          {#if issue.discussion[0].reactions.length > 0 || $httpdStore.state === "authenticated"}
-
            <div class="reactions">
-
              <Reactions
-
                id={issue.id}
-
                reactions={issue.discussion[0].reactions}
-
                on:react={handleReaction} />
-
            </div>
-
          {/if}
+
          <div class="reactions">
+
            {#if $httpdStore.state === "authenticated"}
+
              <Floating>
+
                <div class="reaction-selector" slot="modal">
+
                  <ReactionSelector
+
                    nid={$httpdStore.session.publicKey}
+
                    reactions={issueReactions}
+
                    on:select={async event => {
+
                      await handleReaction({ ...event.detail, id: issue.id });
+
                      closeFocused();
+
                    }} />
+
                </div>
+
                <div class="toggle" slot="toggle">
+
                  <Icon name="face" />
+
                </div>
+
              </Floating>
+
            {/if}
+
            {#if issueReactions.size > 0}
+
              <div style:margin-top="1rem">
+
                <Reactions
+
                  clickable={$httpdStore.state === "authenticated"}
+
                  reactions={issueReactions}
+
                  on:remove={event =>
+
                    handleReaction({ ...event.detail, id: issue.id })} />
+
              </div>
+
            {/if}
+
          </div>
        </div>
        <div class="author" slot="author">
          opened by <Authorship
@@ -376,7 +423,7 @@
            {thread}
            {rawPath}
            on:reply={createReply}
-
            on:react={handleReaction} />
+
            on:react={event => handleReaction(event.detail)} />
        </div>
      {/each}
      {#if $httpdStore.state === "authenticated"}
modified tests/e2e/project/issues.spec.ts
@@ -39,7 +39,7 @@ test("adding and removing reactions", async ({ page, authenticatedPeer }) => {
  await page.goto(
    `${authenticatedPeer.uiUrl()}/${rid}/issues/48af7d329e5b44ee8d348eeb7e341370243db9ad`,
  );
-
  const commentReactionToggle = page.locator(".card-body .toggle").first();
+
  const commentReactionToggle = page.getByTitle("toggle-reaction");
  await page.getByPlaceholder("Leave your comment").fill("This is a comment");
  await page.getByRole("button", { name: "Comment" }).click();
  await commentReactionToggle.click();
@@ -190,7 +190,7 @@ test("go through the entire ui issue flow", async ({
  await page.getByRole("button", { name: "Comment" }).click();
  await expect(page.getByText("This is a comment")).toBeVisible();

-
  await page.getByRole("button", { name: "reply" }).click();
+
  await page.getByTitle("toggle-reply").click();
  await page.getByPlaceholder("Leave your reply").fill("This is a reply");
  await page.getByRole("button", { name: "Reply", exact: true }).click();
  await expect(page.getByText("This is a reply")).toBeVisible();
modified tests/e2e/project/patches.spec.ts
@@ -68,7 +68,7 @@ test("adding and removing reactions", async ({ page, authenticatedPeer }) => {
  await page.goto(
    `${authenticatedPeer.uiUrl()}/${rid}/patches/bfc3bc2c6af29920283f83e7ada9d52b2d4d3a57`,
  );
-
  const commentReactionToggle = page.locator(".card-body .toggle").first();
+
  const commentReactionToggle = page.getByTitle("toggle-reaction");
  await commentReactionToggle.click();
  await page.getByRole("button", { name: "👍" }).click();
  await expect(page.locator("span").filter({ hasText: "👍 1" })).toBeVisible();