Radish alpha
r
rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5
Radicle web interface
Radicle
Git
radicle-explorer src views repos Issues.svelte
<script lang="ts">
  import type { BaseUrl, Issue, IssueState, Repo } from "@http-client";

  import { HttpdClient } from "@http-client";
  import { ISSUES_PER_PAGE } from "./router";
  import { baseUrlToString } from "@app/lib/utils";

  import Button from "@app/components/Button.svelte";
  import ErrorMessage from "@app/components/ErrorMessage.svelte";
  import Icon from "@app/components/Icon.svelte";
  import IssueTeaser from "@app/views/repos/Issue/IssueTeaser.svelte";
  import Layout from "./Layout.svelte";
  import Link from "@app/components/Link.svelte";
  import List from "@app/components/List.svelte";
  import Loading from "@app/components/Loading.svelte";
  import Placeholder from "@app/components/Placeholder.svelte";
  import Separator from "./Separator.svelte";

  export let baseUrl: BaseUrl;
  export let issues: Issue[];
  export let repo: Repo;
  export let status: IssueState["status"];
  export let nodeAvatarUrl: string | undefined;

  let loading = false;
  let page = 0;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  let error: any;
  let allIssues: Issue[];

  $: {
    allIssues = issues;
    page = 0;
  }

  const api = new HttpdClient(baseUrl);

  async function loadIssues(status: IssueState["status"]): Promise<void> {
    loading = true;
    page += 1;
    try {
      const response = await api.repo.getAllIssues(repo.rid, {
        status,
        page,
        perPage: ISSUES_PER_PAGE,
      });
      allIssues = [...allIssues, ...response];
    } catch (e) {
      error = e;
    } finally {
      loading = false;
    }
  }

  $: showMoreButton =
    !loading &&
    !error &&
    allIssues.length < repo.payloads["xyz.radicle.project"].meta.issues[status];
</script>

<style>
  .header {
    display: flex;
    gap: 0.25rem;
    padding: 1rem;
    border-bottom: 1px solid var(--color-border-subtle);
  }
  .more {
    margin-top: 2rem;
    min-height: 3rem;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .counter {
    border-radius: var(--border-radius-sm);
    background-color: var(--color-surface-mid);
    color: var(--color-text-tertiary);
    padding: 0 0.25rem;
    min-width: 1.5rem;
    text-align: center;
  }
  .selected {
    background-color: var(--color-surface-alpha-subtle);
    color: var(--color-text-primary);
  }
  .title-counter {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
  .placeholder {
    height: calc(100% - 4rem);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  @media (max-width: 719.98px) {
    .placeholder {
      height: calc(100vh - 10rem);
    }
  }
</style>

<Layout {nodeAvatarUrl} {baseUrl} {repo} activeTab="issues">
  <svelte:fragment slot="breadcrumb">
    <Separator />
    <Link
      route={{
        resource: "repo.issues",
        repo: repo.rid,
        node: baseUrl,
      }}>
      Issues
    </Link>
  </svelte:fragment>
  <div slot="header" class="header">
    <Link
      route={{
        resource: "repo.issues",
        repo: repo.rid,
        node: baseUrl,
        status: "open",
      }}>
      <Button variant={status === "open" ? "gray" : "background"}>
        <Icon name="issue" />
        <div class="title-counter">
          Open
          <span class="counter" class:selected={status === "open"}>
            {repo.payloads["xyz.radicle.project"].meta.issues.open}
          </span>
        </div>
      </Button>
    </Link>
    <Link
      route={{
        resource: "repo.issues",
        repo: repo.rid,
        node: baseUrl,
        status: "closed",
      }}>
      <Button variant={status === "closed" ? "gray" : "background"}>
        <Icon name="issue-closed" />
        <div class="title-counter">
          Closed
          <span class="counter" class:selected={status === "closed"}>
            {repo.payloads["xyz.radicle.project"].meta.issues.closed}
          </span>
        </div>
      </Button>
    </Link>
  </div>

  <List items={allIssues}>
    <IssueTeaser
      slot="item"
      let:item
      {baseUrl}
      repoId={repo.rid}
      issue={item} />
  </List>

  {#if error}
    <ErrorMessage
      title="Couldn't load issues"
      description="Please make sure you are able to connect to the seed <code>{baseUrlToString(
        api.baseUrl,
      )}</code>"
      {error} />
  {/if}

  {#if repo.payloads["xyz.radicle.project"].meta.issues[status] === 0}
    <div class="placeholder">
      <Placeholder iconName="no-issues" caption={`No ${status} issues`} />
    </div>
  {/if}

  {#if loading || showMoreButton}
    <div class="more">
      {#if loading}
        <Loading noDelay small={page !== 0} center />
      {/if}

      {#if showMoreButton}
        <Button
          size="large"
          variant="outline"
          on:click={() => loadIssues(status)}>
          More
        </Button>
      {/if}
    </div>
  {/if}
</Layout>