mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-15 19:30:38 +08:00
We just use the radicleExplorer that is configured in the app config and don't query the node for it. Besides simplifying things this also makes the button work on iOS again, because we don't have the async http request anymore.
214 lines
5.7 KiB
Svelte
214 lines
5.7 KiB
Svelte
<script lang="ts">
|
|
import type {
|
|
BaseUrl,
|
|
Issue,
|
|
IssueState,
|
|
Project,
|
|
SeedingPolicy,
|
|
} from "@http-client";
|
|
|
|
import capitalize from "lodash/capitalize";
|
|
import { HttpdClient } from "@http-client";
|
|
import { ISSUES_PER_PAGE } from "./router";
|
|
import { baseUrlToString } from "@app/lib/utils";
|
|
import { closeFocused } from "@app/components/Popover.svelte";
|
|
|
|
import Button from "@app/components/Button.svelte";
|
|
import DropdownList from "@app/components/DropdownList.svelte";
|
|
import DropdownListItem from "@app/components/DropdownList/DropdownListItem.svelte";
|
|
import ErrorMessage from "@app/components/ErrorMessage.svelte";
|
|
import IconSmall from "@app/components/IconSmall.svelte";
|
|
import IssueTeaser from "@app/views/projects/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 Popover from "@app/components/Popover.svelte";
|
|
import Share from "./Share.svelte";
|
|
|
|
export let baseUrl: BaseUrl;
|
|
export let seedingPolicy: SeedingPolicy;
|
|
export let issues: Issue[];
|
|
export let project: Project;
|
|
export let status: IssueState["status"];
|
|
|
|
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.project.getAllIssues(project.id, {
|
|
status,
|
|
page,
|
|
perPage: ISSUES_PER_PAGE,
|
|
});
|
|
allIssues = [...allIssues, ...response];
|
|
} catch (e) {
|
|
error = e;
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
}
|
|
|
|
const stateOptions: IssueState["status"][] = ["open", "closed"];
|
|
const stateColor: Record<IssueState["status"], string> = {
|
|
open: "var(--color-fill-success)",
|
|
closed: "var(--color-foreground-red)",
|
|
};
|
|
|
|
$: showMoreButton =
|
|
!loading && !error && allIssues.length < project.issues[status];
|
|
</script>
|
|
|
|
<style>
|
|
.header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 1rem;
|
|
}
|
|
.more {
|
|
margin-top: 2rem;
|
|
min-height: 3rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.dropdown-button-counter {
|
|
border-radius: var(--border-radius-tiny);
|
|
background-color: var(--color-fill-counter);
|
|
color: var(--color-foreground-contrast);
|
|
padding: 0 0.25rem;
|
|
}
|
|
.dropdown-list-counter {
|
|
border-radius: var(--border-radius-tiny);
|
|
background-color: var(--color-fill-ghost);
|
|
color: var(--color-foreground-dim);
|
|
padding: 0 0.25rem;
|
|
}
|
|
.selected {
|
|
background-color: var(--color-fill-counter);
|
|
color: var(--color-foreground-dim);
|
|
}
|
|
.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 {seedingPolicy} {baseUrl} {project} activeTab="issues">
|
|
<div slot="header" class="header">
|
|
<Popover
|
|
popoverPadding="0"
|
|
popoverPositionTop="2.5rem"
|
|
popoverBorderRadius="var(--border-radius-small)">
|
|
<Button
|
|
let:expanded
|
|
slot="toggle"
|
|
let:toggle
|
|
on:click={toggle}
|
|
ariaLabel="filter-dropdown"
|
|
title="Filter issues by state">
|
|
<div style:color={stateColor[status]}>
|
|
<IconSmall name="issue" />
|
|
</div>
|
|
{capitalize(status)}
|
|
<div class="dropdown-button-counter">
|
|
{project.issues[status]}
|
|
</div>
|
|
<IconSmall name={expanded ? "chevron-up" : "chevron-down"} />
|
|
</Button>
|
|
|
|
<DropdownList slot="popover" items={stateOptions}>
|
|
<Link
|
|
on:afterNavigate={() => closeFocused()}
|
|
slot="item"
|
|
let:item
|
|
route={{
|
|
resource: "project.issues",
|
|
project: project.id,
|
|
node: baseUrl,
|
|
status: item,
|
|
}}>
|
|
<DropdownListItem selected={item === status}>
|
|
<div style:color={stateColor[item]}>
|
|
<IconSmall name="issue" />
|
|
</div>
|
|
<div
|
|
style="display: flex; gap: 1rem;justify-content: space-between; width: 100%;">
|
|
{capitalize(item)}
|
|
<div
|
|
class="dropdown-list-counter"
|
|
class:selected={item === status}>
|
|
{project.issues[item]}
|
|
</div>
|
|
</div>
|
|
</DropdownListItem>
|
|
</Link>
|
|
</DropdownList>
|
|
</Popover>
|
|
|
|
<Share />
|
|
</div>
|
|
|
|
<List items={allIssues}>
|
|
<IssueTeaser
|
|
slot="item"
|
|
let:item
|
|
{baseUrl}
|
|
projectId={project.id}
|
|
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 project.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>
|