mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-31 01:00:47 +08:00
Add rudimentary "Browse all repos on node" view
Seemed to me there was no way to browse all the repositories being seeded on a node. On the massive public nodes this is not that useful a feature since there are too many and too jumbled an assortment, but for a smaller personal node, this feature might be valuable to make the explorer's contents a bit easier to explore. Went with the most unintrusive implementation of this idea for now; just enough that it "does the job" (you can access this view, and paginate the results) but does not have any of the bells and whistles it should have (search/filtering, sorting, page query parameters support). As part of this work, since the repos section gets much more complex, I extracted the repos part of the page into its own component. Otherwise, it is just very simply allowing the parameters to the `fetchRepoInfos` call to be manipulated from the page.
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
<script lang="ts">
|
||||
import type { BaseUrl, NodeStats } from "@http-client";
|
||||
|
||||
import * as router from "@app/lib/router";
|
||||
import { baseUrlToString } from "@app/lib/utils";
|
||||
import { fetchRepoInfos } from "@app/components/RepoCard";
|
||||
import { handleError } from "@app/views/nodes/error";
|
||||
|
||||
import Loading from "@app/components/Loading.svelte";
|
||||
import Placeholder from "@app/components/Placeholder.svelte";
|
||||
import RepoCard from "@app/components/RepoCard.svelte";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let stats: NodeStats;
|
||||
|
||||
let listState: "pinned" | "all" = "pinned";
|
||||
|
||||
$: page = 0;
|
||||
$: perPage = listState === "pinned" ? stats.repos.total : 24;
|
||||
$: totalPages = Math.ceil(stats.repos.total / perPage);
|
||||
|
||||
function showPinned() {
|
||||
listState = "pinned";
|
||||
page = 0;
|
||||
}
|
||||
function showAll() {
|
||||
listState = "all";
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.subtitle,
|
||||
.pagination {
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--color-foreground-dim);
|
||||
}
|
||||
.pagination {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
.repos {
|
||||
margin-top: 0;
|
||||
}
|
||||
.repo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(21rem, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
height: 35vh;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.text-button {
|
||||
background: none;
|
||||
border: none;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.text-button:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
.text-button:hover:not(:disabled) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.current-page {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
gap: 0.5rem 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1010.98px) {
|
||||
.repos {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
.footer {
|
||||
flex-direction: column;
|
||||
}
|
||||
.pagination {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="repos">
|
||||
{#await fetchRepoInfos(baseUrl, { show: listState, perPage, page })}
|
||||
<div style:height="35vh">
|
||||
<Loading small center />
|
||||
</div>
|
||||
{:then repoInfos}
|
||||
{#if repoInfos.length > 0}
|
||||
<div class="repo-grid">
|
||||
{#each repoInfos as repoInfo}
|
||||
<RepoCard {baseUrl} {repoInfo} />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="footer">
|
||||
{#if listState === "pinned"}
|
||||
<div class="subtitle">
|
||||
{repoInfos.length}
|
||||
pinned {repoInfos.length === 1 ? "repository" : "repositories"} ·
|
||||
<button class="text-button" on:click={showAll}>Browse all</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="subtitle">
|
||||
{stats.repos.total.toLocaleString()}
|
||||
seeded {stats.repos.total === 1 ? "repository" : "repositories"} ·
|
||||
<button class="text-button" on:click={showPinned}>
|
||||
See pinned
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="pagination">
|
||||
{#if page !== 0}
|
||||
<button class="text-button" on:click={() => (page = page - 1)}>
|
||||
Previous
|
||||
</button>
|
||||
·
|
||||
{/if}
|
||||
|
||||
{#each Array.from({ length: Math.min(totalPages, 7) }) as _, i}
|
||||
{@const startPage = Math.max(page - 3, 0)}
|
||||
{@const pageNumber = startPage + i}
|
||||
<button
|
||||
class="text-button"
|
||||
class:current-page={page === pageNumber}
|
||||
on:click={() => (page = pageNumber)}
|
||||
disabled={page === pageNumber}>
|
||||
{pageNumber + 1}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
{#if page !== totalPages - 1}
|
||||
·
|
||||
<button class="text-button" on:click={() => (page = page + 1)}>
|
||||
Next
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="empty-state">
|
||||
{#if listState === "pinned"}
|
||||
<Placeholder
|
||||
iconName="desert"
|
||||
caption="This node doesn't have any pinned repositories." />
|
||||
{:else}
|
||||
<Placeholder
|
||||
iconName="desert"
|
||||
caption="This node doesn't seed any repositories." />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{:catch error}
|
||||
{router.push(handleError(error, baseUrlToString(baseUrl)))}
|
||||
{/await}
|
||||
</div>
|
||||
@@ -1,12 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { BaseUrl, Node, NodeStats } from "@http-client";
|
||||
|
||||
import * as router from "@app/lib/router";
|
||||
import dompurify from "dompurify";
|
||||
import { markdown } from "@app/lib/markdown";
|
||||
import { baseUrlToString } from "@app/lib/utils";
|
||||
import { fetchRepoInfos } from "@app/components/RepoCard";
|
||||
import { handleError } from "@app/views/nodes/error";
|
||||
|
||||
import Settings from "@app/App/Settings.svelte";
|
||||
|
||||
@@ -16,11 +12,9 @@
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import IconButton from "@app/components/IconButton.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import Loading from "@app/components/Loading.svelte";
|
||||
import MobileFooter from "@app/App/MobileFooter.svelte";
|
||||
import Placeholder from "@app/components/Placeholder.svelte";
|
||||
import Popover from "@app/components/Popover.svelte";
|
||||
import RepoCard from "@app/components/RepoCard.svelte";
|
||||
import ReposView from "./ReposView.svelte";
|
||||
|
||||
import PolicyExplainer from "./PolicyExplainer.svelte";
|
||||
import SeedSelector from "./SeedSelector.svelte";
|
||||
@@ -138,32 +132,6 @@
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--color-foreground-dim);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.repos {
|
||||
margin-top: 0;
|
||||
}
|
||||
.repo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(21rem, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
height: 35vh;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.box {
|
||||
font-size: var(--font-size-small);
|
||||
line-height: 1.625rem;
|
||||
@@ -220,9 +188,6 @@
|
||||
.container {
|
||||
padding: 0;
|
||||
}
|
||||
.repos {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
.mobile-footer {
|
||||
margin-top: auto;
|
||||
display: grid;
|
||||
@@ -418,35 +383,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="repos">
|
||||
{#await fetchRepoInfos( baseUrl, { show: "pinned", perPage: stats.repos.total }, )}
|
||||
<div style:height="35vh">
|
||||
<Loading small center />
|
||||
</div>
|
||||
{:then repoInfos}
|
||||
{#if repoInfos.length > 0}
|
||||
<div class="repo-grid">
|
||||
{#each repoInfos as repoInfo}
|
||||
<RepoCard {baseUrl} {repoInfo} />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="subtitle">
|
||||
{repoInfos.length}
|
||||
pinned {repoInfos.length === 1
|
||||
? "repository"
|
||||
: "repositories"}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="empty-state">
|
||||
<Placeholder
|
||||
iconName="desert"
|
||||
caption="This node doesn't have any pinned repositories." />
|
||||
</div>
|
||||
{/if}
|
||||
{:catch error}
|
||||
{router.push(handleError(error, baseUrlToString(baseUrl)))}
|
||||
{/await}
|
||||
</div>
|
||||
<ReposView {baseUrl} {stats} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user