mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-18 19:30:27 +08:00
Replace repo sidebar with horizontal tab bar
Convert the vertical sidebar navigation (Source, Issues, Patches) into a horizontal tab bar above the content area. The ContextRepo info moves into a popover behind an info button in the tab bar. Mobile footer navigation is preserved for small screens, with the tab bar hidden on mobile.
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
<Link
|
||||
style="width: 100%; display: flex; align-items: center; justify-content: center;"
|
||||
route={{ resource: "nodes", params: undefined }}>
|
||||
<Icon name="logo" />
|
||||
<Icon name="seed" />
|
||||
</Link>
|
||||
|
||||
<slot />
|
||||
|
||||
+10
-22
@@ -16,24 +16,20 @@
|
||||
display: flex;
|
||||
height: 3rem;
|
||||
align-items: center;
|
||||
padding: 0 0.5rem 0 1rem;
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-top-left-radius: var(--border-radius-md);
|
||||
border-top-right-radius: var(--border-radius-md);
|
||||
padding: 0 1rem;
|
||||
background-color: var(--color-surface-canvas);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.header-border {
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.sticky {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
border-radius: var(--border-radius-md);
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
@@ -51,27 +47,19 @@
|
||||
.container {
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-top: 0;
|
||||
border-bottom-left-radius: var(--border-radius-md);
|
||||
border-bottom-right-radius: var(--border-radius-md);
|
||||
}
|
||||
@media (max-width: 719.98px) {
|
||||
.header {
|
||||
border-radius: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
padding: 0 1rem 0 1rem;
|
||||
}
|
||||
.container {
|
||||
border-radius: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div bind:this={header} class="header" class:collapsed={!expanded} class:sticky>
|
||||
<div
|
||||
bind:this={header}
|
||||
class="header"
|
||||
class:sticky
|
||||
class:header-border={expanded}>
|
||||
<div class="left">
|
||||
{#if collapsable}
|
||||
<ExpandButton
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
<style>
|
||||
.list {
|
||||
border-top: 1px solid var(--color-border-subtle);
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
.list-item:not(:last-child) {
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
color: var(--color-text-on-brand);
|
||||
}
|
||||
|
||||
@container (max-width: 20rem) {
|
||||
@container (max-width: 30rem) {
|
||||
.activity {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,11 @@
|
||||
background-color: var(--color-surface-base);
|
||||
padding: 1rem;
|
||||
}
|
||||
.file {
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: var(--border-radius-md);
|
||||
overflow: clip;
|
||||
}
|
||||
.summary {
|
||||
font: var(--txt-body-m-regular);
|
||||
}
|
||||
@@ -99,7 +104,7 @@
|
||||
<Observer let:filesVisibility let:observer>
|
||||
{#each diff.files as file}
|
||||
{@const path = "path" in file ? file.path : file.newPath}
|
||||
<div use:intersection={observer} id={"observer:" + path}>
|
||||
<div class="file" use:intersection={observer} id={"observer:" + path}>
|
||||
{#if "diff" in file}
|
||||
<FileDiff
|
||||
{repoId}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { BaseUrl, Commit, Repo, SeedingPolicy } from "@http-client";
|
||||
import type { BaseUrl, Commit, Repo } from "@http-client";
|
||||
|
||||
import dompurify from "dompurify";
|
||||
import escape from "lodash/escape";
|
||||
@@ -14,10 +14,8 @@
|
||||
import Layout from "./Layout.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import Separator from "./Separator.svelte";
|
||||
import Share from "./Share.svelte";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let seedingPolicy: SeedingPolicy;
|
||||
export let commit: Commit;
|
||||
export let repo: Repo;
|
||||
export let nodeAvatarUrl: string | undefined;
|
||||
@@ -70,7 +68,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {nodeAvatarUrl} {seedingPolicy} {baseUrl} {repo}>
|
||||
<Layout {nodeAvatarUrl} {baseUrl} {repo}>
|
||||
<svelte:fragment slot="breadcrumb">
|
||||
<Separator />
|
||||
<Link
|
||||
@@ -117,7 +115,6 @@
|
||||
</Button>
|
||||
</a>
|
||||
{/if}
|
||||
<Share />
|
||||
</div>
|
||||
</span>
|
||||
<CommitAuthorship {header}>
|
||||
|
||||
@@ -5,20 +5,65 @@
|
||||
<script lang="ts">
|
||||
import type { BaseUrl, Repo } from "@http-client";
|
||||
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import config from "@app/lib/config";
|
||||
import debounce from "lodash/debounce";
|
||||
import { routeToPath } from "@app/lib/router";
|
||||
import { toClipboard } from "@app/lib/utils";
|
||||
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import SeedButton from "@app/views/repos/Header/SeedButton.svelte";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let activeTab: ActiveTab = undefined;
|
||||
export let repo: Repo;
|
||||
|
||||
let shareIcon: "link" | "checkmark" = "link";
|
||||
|
||||
const restoreIcon = debounce(() => {
|
||||
shareIcon = "link";
|
||||
}, 1000);
|
||||
|
||||
function tabRoute(): string {
|
||||
if (activeTab === "issues") {
|
||||
return routeToPath({
|
||||
resource: "repo.issues",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
});
|
||||
} else if (activeTab === "patches") {
|
||||
return routeToPath({
|
||||
resource: "repo.patches",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
});
|
||||
} else {
|
||||
return routeToPath({
|
||||
resource: "repo.source",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
path: "/",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function copyLink() {
|
||||
const origin = new URL(config.nodes.fallbackPublicExplorer).origin;
|
||||
await toClipboard(origin.concat(tabRoute()));
|
||||
shareIcon = "checkmark";
|
||||
restoreIcon();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.counter {
|
||||
@@ -41,8 +86,16 @@
|
||||
.title-counter {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -54,11 +107,7 @@
|
||||
node: baseUrl,
|
||||
path: "/",
|
||||
}}>
|
||||
<Button
|
||||
size="large"
|
||||
styleWidth="100%"
|
||||
styleJustifyContent="flex-start"
|
||||
variant={activeTab === "source" ? "gray" : "background"}>
|
||||
<Button variant={activeTab === "source" ? "gray" : "background"}>
|
||||
<Icon name="chevron-left-right" />
|
||||
Source
|
||||
</Button>
|
||||
@@ -69,12 +118,7 @@
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
}}>
|
||||
<Button
|
||||
let:hover
|
||||
size="large"
|
||||
styleJustifyContent="flex-start"
|
||||
styleWidth="100%"
|
||||
variant={activeTab === "issues" ? "gray" : "background"}>
|
||||
<Button let:hover variant={activeTab === "issues" ? "gray" : "background"}>
|
||||
<Icon name="issue" />
|
||||
<div class="title-counter">
|
||||
Issues
|
||||
@@ -94,12 +138,7 @@
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
}}>
|
||||
<Button
|
||||
let:hover
|
||||
size="large"
|
||||
styleWidth="100%"
|
||||
styleJustifyContent="flex-start"
|
||||
variant={activeTab === "patches" ? "gray" : "background"}>
|
||||
<Button let:hover variant={activeTab === "patches" ? "gray" : "background"}>
|
||||
<Icon name="patch" />
|
||||
<div class="title-counter">
|
||||
Patches
|
||||
@@ -112,4 +151,17 @@
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<div class="actions">
|
||||
{#if activeTab !== "issues" && activeTab !== "patches"}
|
||||
<Button variant="outline" size="regular" on:click={copyLink}>
|
||||
<Icon name={shareIcon} />
|
||||
<span class="global-hide-on-small-desktop-down">Copy link</span>
|
||||
</Button>
|
||||
{/if}
|
||||
<slot name="actions" />
|
||||
<SeedButton seedCount={repo.seeding} repoId={repo.rid} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,11 +11,23 @@
|
||||
import Popover from "@app/components/Popover.svelte";
|
||||
import Radio from "@app/components/Radio.svelte";
|
||||
|
||||
import { baseUrlToString } from "@app/lib/utils";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let id: string;
|
||||
export let name: string;
|
||||
export let currentRefname: string;
|
||||
export let enabledArchiveDownload: boolean;
|
||||
|
||||
let enabledArchiveDownload = false;
|
||||
|
||||
void fetch(
|
||||
`${baseUrlToString(baseUrl)}/raw/${id}/archive/${currentRefname}`,
|
||||
{
|
||||
method: "HEAD",
|
||||
},
|
||||
).then(response => {
|
||||
enabledArchiveDownload = response.ok;
|
||||
});
|
||||
|
||||
let activeTab: "radicle" | "git" | "archive" = "radicle";
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
.not-seeding {
|
||||
background-color: var(--color-surface-brand-secondary);
|
||||
color: var(--color-text-on-brand);
|
||||
background-color: var(--color-surface-mid);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.disabled {
|
||||
background-color: var(--color-surface-mid);
|
||||
@@ -44,7 +44,7 @@
|
||||
on:click={() => {
|
||||
toggle();
|
||||
}}
|
||||
variant="secondary-toggle-off">
|
||||
variant="gray">
|
||||
<Icon name="seed" />
|
||||
<span class="title-counter">
|
||||
<span class="global-hide-on-mobile-down">Seed</span>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import { groupCommits } from "@app/lib/commit";
|
||||
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import CloneButton from "@app/views/repos/Header/CloneButton.svelte";
|
||||
import CommitTeaser from "./Commit/CommitTeaser.svelte";
|
||||
import ErrorMessage from "@app/components/ErrorMessage.svelte";
|
||||
import Header from "./Source/Header.svelte";
|
||||
@@ -36,7 +37,21 @@
|
||||
export let tree: Tree;
|
||||
export let nodeAvatarUrl: string | undefined;
|
||||
|
||||
$: currentRefname = formatQualifiedRefname(
|
||||
revision || repo.payloads["xyz.radicle.project"].data.defaultBranch,
|
||||
peer,
|
||||
);
|
||||
|
||||
const api = new HttpdClient(baseUrl);
|
||||
let totalCommits: number | undefined = undefined;
|
||||
|
||||
function fetchTotalCommits(rid: string, sha: string) {
|
||||
void api.repo.getTreeStatsBySha(rid, sha).then(stats => {
|
||||
totalCommits = stats.commits;
|
||||
});
|
||||
}
|
||||
|
||||
$: fetchTotalCommits(repo.rid, commit);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let error: any;
|
||||
@@ -80,18 +95,13 @@
|
||||
justify-content: center;
|
||||
}
|
||||
.group-header {
|
||||
margin-left: 1rem;
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
margin: 1rem 0 0.5rem 1rem;
|
||||
font: var(--txt-body-m-regular);
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
.group-header:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {nodeAvatarUrl} {seedingPolicy} {baseUrl} {repo} activeTab="source">
|
||||
<Layout {nodeAvatarUrl} {baseUrl} {repo} activeTab="source">
|
||||
<svelte:fragment slot="breadcrumb">
|
||||
<Separator />
|
||||
<Link
|
||||
@@ -103,16 +113,19 @@
|
||||
Commits
|
||||
</Link>
|
||||
</svelte:fragment>
|
||||
<RepoNameHeader
|
||||
{repo}
|
||||
currentRefname={formatQualifiedRefname(
|
||||
revision || repo.payloads["xyz.radicle.project"].data.defaultBranch,
|
||||
peer,
|
||||
)}
|
||||
{baseUrl}
|
||||
slot="header" />
|
||||
<svelte:fragment slot="actions">
|
||||
<CloneButton
|
||||
{baseUrl}
|
||||
{currentRefname}
|
||||
id={repo.rid}
|
||||
name={repo.payloads["xyz.radicle.project"].data.name} />
|
||||
</svelte:fragment>
|
||||
<RepoNameHeader {repo} {baseUrl} {seedingPolicy} slot="header" />
|
||||
|
||||
<div style:margin="1rem" slot="subheader">
|
||||
<div
|
||||
style:padding="1rem"
|
||||
style:border-bottom="1px solid var(--color-border-subtle)"
|
||||
slot="subheader">
|
||||
<Header
|
||||
{baseRoute}
|
||||
{commit}
|
||||
@@ -140,41 +153,24 @@
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#await api.repo.getTreeStatsBySha(repo.rid, commit)}
|
||||
{#if totalCommits === undefined || loading || allCommitHeaders.length < totalCommits}
|
||||
<div class="more">
|
||||
<Loading small center />
|
||||
{#if totalCommits === undefined || loading}
|
||||
<Loading small={page !== 0} center />
|
||||
{:else if allCommitHeaders.length < totalCommits}
|
||||
<Button size="large" variant="outline" on:click={loadMore}>More</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{:then stats}
|
||||
{#if loading || allCommitHeaders.length < stats.commits}
|
||||
<div class="more">
|
||||
{#if loading}
|
||||
<Loading small={page !== 0} center />
|
||||
{:else if allCommitHeaders.length < stats.commits}
|
||||
<Button size="large" variant="outline" on:click={loadMore}>
|
||||
More
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if error}
|
||||
<div class="message">
|
||||
<ErrorMessage
|
||||
title="Couldn't load commits"
|
||||
description="Make sure you are able to connect to the seed <code>{baseUrlToString(
|
||||
api.baseUrl,
|
||||
)}</code>"
|
||||
{error} />
|
||||
</div>
|
||||
{/if}
|
||||
{:catch error}
|
||||
{#if error}
|
||||
<div class="message">
|
||||
<ErrorMessage
|
||||
title="Couldn't load repo stats"
|
||||
title="Couldn't load commits"
|
||||
description="Make sure you are able to connect to the seed <code>{baseUrlToString(
|
||||
api.baseUrl,
|
||||
)}</code>"
|
||||
{error} />
|
||||
</div>
|
||||
{/await}
|
||||
{/if}
|
||||
</Layout>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { BaseUrl, Issue, Repo, SeedingPolicy } from "@http-client";
|
||||
import type { BaseUrl, Issue, Repo } from "@http-client";
|
||||
|
||||
import capitalize from "lodash/capitalize";
|
||||
import uniqBy from "lodash/uniqBy";
|
||||
@@ -24,7 +24,6 @@
|
||||
import ThreadComponent from "@app/components/Thread.svelte";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let seedingPolicy: SeedingPolicy;
|
||||
export let issue: Issue;
|
||||
export let repo: Repo;
|
||||
export let rawPath: (commit?: string) => string;
|
||||
@@ -122,7 +121,6 @@
|
||||
{baseUrl}
|
||||
{nodeAvatarUrl}
|
||||
{repo}
|
||||
{seedingPolicy}
|
||||
activeTab="issues"
|
||||
stylePaddingBottom="0">
|
||||
<svelte:fragment slot="breadcrumb">
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type {
|
||||
BaseUrl,
|
||||
Issue,
|
||||
IssueState,
|
||||
Repo,
|
||||
SeedingPolicy,
|
||||
} from "@http-client";
|
||||
import type { BaseUrl, Issue, IssueState, Repo } 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 Icon from "@app/components/Icon.svelte";
|
||||
import IssueTeaser from "@app/views/repos/Issue/IssueTeaser.svelte";
|
||||
@@ -24,12 +14,9 @@
|
||||
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 Separator from "./Separator.svelte";
|
||||
import Share from "./Share.svelte";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let seedingPolicy: SeedingPolicy;
|
||||
export let issues: Issue[];
|
||||
export let repo: Repo;
|
||||
export let status: IssueState["status"];
|
||||
@@ -65,17 +52,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
const stateOptions: IssueState["status"][] = ["open", "closed"];
|
||||
const stateColor: Record<IssueState["status"], string> = {
|
||||
open: "var(--color-text-open)",
|
||||
closed: "var(--color-text-merged)",
|
||||
};
|
||||
|
||||
const stateBackground: Record<IssueState["status"], string> = {
|
||||
open: "var(--color-surface-open)",
|
||||
closed: "var(--color-surface-merged)",
|
||||
};
|
||||
|
||||
$: showMoreButton =
|
||||
!loading &&
|
||||
!error &&
|
||||
@@ -85,8 +61,9 @@
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.25rem;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
.more {
|
||||
margin-top: 2rem;
|
||||
@@ -95,21 +72,22 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.dropdown-button-counter {
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-surface-alpha-subtle);
|
||||
color: var(--color-text-primary);
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
.dropdown-list-counter {
|
||||
.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-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.title-counter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.placeholder {
|
||||
height: calc(100% - 4rem);
|
||||
@@ -124,7 +102,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {nodeAvatarUrl} {seedingPolicy} {baseUrl} {repo} activeTab="issues">
|
||||
<Layout {nodeAvatarUrl} {baseUrl} {repo} activeTab="issues">
|
||||
<svelte:fragment slot="breadcrumb">
|
||||
<Separator />
|
||||
<Link
|
||||
@@ -137,65 +115,40 @@
|
||||
</Link>
|
||||
</svelte:fragment>
|
||||
<div slot="header" class="header">
|
||||
<Popover
|
||||
popoverPadding="0"
|
||||
popoverPositionTop="2.5rem"
|
||||
popoverBorderRadius="var(--border-radius-md)">
|
||||
<Button
|
||||
let:expanded
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}
|
||||
ariaLabel="filter-dropdown"
|
||||
title="Filter issues by state">
|
||||
<div
|
||||
style:color={stateColor[status]}
|
||||
style:background={stateBackground[status]}
|
||||
style:padding="0.25rem 0.25rem"
|
||||
style:border-radius="var(--border-radius-sm)">
|
||||
<Icon name={status === "closed" ? "issue-closed" : "issue"} />
|
||||
<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>
|
||||
{capitalize(status)}
|
||||
<div class="dropdown-button-counter">
|
||||
{repo.payloads["xyz.radicle.project"].meta.issues[status]}
|
||||
</div>
|
||||
<Icon name={expanded ? "chevron-up" : "chevron-down"} />
|
||||
</Button>
|
||||
|
||||
<DropdownList slot="popover" items={stateOptions}>
|
||||
<Link
|
||||
on:afterNavigate={() => closeFocused()}
|
||||
slot="item"
|
||||
let:item
|
||||
route={{
|
||||
resource: "repo.issues",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
status: item,
|
||||
}}>
|
||||
<DropdownListItem selected={item === status}>
|
||||
<div
|
||||
style:color={stateColor[item]}
|
||||
style:background={stateBackground[item]}
|
||||
style:padding="0.25rem 0.25rem"
|
||||
style:border-radius="var(--border-radius-sm)">
|
||||
<Icon name={item === "closed" ? "issue-closed" : "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}>
|
||||
{repo.payloads["xyz.radicle.project"].meta.issues[item]}
|
||||
</div>
|
||||
</div>
|
||||
</DropdownListItem>
|
||||
</Link>
|
||||
</DropdownList>
|
||||
</Popover>
|
||||
|
||||
<Share />
|
||||
</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}>
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
<script lang="ts">
|
||||
import type { ActiveTab } from "./Header.svelte";
|
||||
import type { BaseUrl, Repo, SeedingPolicy } from "@http-client";
|
||||
import type { BaseUrl, Repo } from "@http-client";
|
||||
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import Header from "@app/components/Header.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import MobileFooter from "@app/App/MobileFooter.svelte";
|
||||
import RepoHeader from "./Header.svelte";
|
||||
import Separator from "./Separator.svelte";
|
||||
import Sidebar from "@app/views/repos/Sidebar.svelte";
|
||||
import UserAvatar from "@app/components/UserAvatar.svelte";
|
||||
|
||||
export let activeTab: ActiveTab | undefined = undefined;
|
||||
export let seedingPolicy: SeedingPolicy;
|
||||
export let baseUrl: BaseUrl;
|
||||
export let repo: Repo;
|
||||
export let stylePaddingBottom: string = "2.5rem";
|
||||
@@ -21,23 +20,18 @@
|
||||
|
||||
<style>
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template: auto 1fr auto / auto 1fr auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.desktop-header {
|
||||
grid-column: 1 / 4;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
grid-column: 1 / 2;
|
||||
border-right: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-column: 2 / 3;
|
||||
overflow: scroll;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobile-footer {
|
||||
@@ -68,6 +62,9 @@
|
||||
.desktop-header {
|
||||
display: none;
|
||||
}
|
||||
.tab-bar {
|
||||
display: none;
|
||||
}
|
||||
.content {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
@@ -75,7 +72,6 @@
|
||||
.mobile-footer {
|
||||
margin-top: auto;
|
||||
display: grid;
|
||||
grid-column: 1 / 4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -133,12 +129,12 @@
|
||||
</Header>
|
||||
</div>
|
||||
|
||||
<div class="sidebar global-hide-on-medium-desktop-down">
|
||||
<Sidebar {seedingPolicy} {activeTab} {baseUrl} {repo} />
|
||||
</div>
|
||||
|
||||
<div class="sidebar global-hide-on-mobile-down global-hide-on-desktop-up">
|
||||
<Sidebar {seedingPolicy} {activeTab} {baseUrl} {repo} collapsedOnly />
|
||||
<div class="tab-bar">
|
||||
<RepoHeader {activeTab} {baseUrl} {repo}>
|
||||
<svelte:fragment slot="actions">
|
||||
<slot name="actions" />
|
||||
</svelte:fragment>
|
||||
</RepoHeader>
|
||||
</div>
|
||||
|
||||
<div class="content" style:padding-bottom={stylePaddingBottom}>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
Repo,
|
||||
Revision,
|
||||
Diff,
|
||||
SeedingPolicy,
|
||||
} from "@http-client";
|
||||
|
||||
interface Thread {
|
||||
@@ -71,7 +70,7 @@
|
||||
import Markdown from "@app/components/Markdown.svelte";
|
||||
import NodeId from "@app/components/NodeId.svelte";
|
||||
import Placeholder from "@app/components/Placeholder.svelte";
|
||||
import Radio from "@app/components/Radio.svelte";
|
||||
|
||||
import Reactions from "@app/components/Reactions.svelte";
|
||||
import Reviews from "@app/views/repos/Cob/Reviews.svelte";
|
||||
import RevisionComponent from "@app/views/repos/Cob/Revision.svelte";
|
||||
@@ -80,7 +79,6 @@
|
||||
import Share from "@app/views/repos/Share.svelte";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let seedingPolicy: SeedingPolicy;
|
||||
export let patch: Patch;
|
||||
export let stats: Diff["stats"];
|
||||
export let rawPath: (commit?: string) => string;
|
||||
@@ -267,18 +265,14 @@
|
||||
height: 100%;
|
||||
}
|
||||
.tabs {
|
||||
font: var(--txt-body-s-regular);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
gap: 0.25rem;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
box-shadow: inset 0 -1px 0 var(--color-border-subtle);
|
||||
}
|
||||
.tabs-spacer {
|
||||
width: 1rem;
|
||||
height: 100%;
|
||||
padding: 1rem;
|
||||
border-top: 1px solid var(--color-border-subtle);
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
background-color: var(--color-surface-base);
|
||||
}
|
||||
.author-metadata {
|
||||
color: var(--color-text-tertiary);
|
||||
@@ -304,7 +298,6 @@
|
||||
{baseUrl}
|
||||
{repo}
|
||||
{nodeAvatarUrl}
|
||||
{seedingPolicy}
|
||||
activeTab="patches"
|
||||
stylePaddingBottom="0">
|
||||
<svelte:fragment slot="breadcrumb">
|
||||
@@ -419,42 +412,31 @@
|
||||
</CobHeader>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tabs-spacer"></div>
|
||||
<Radio styleGap="0.375rem">
|
||||
{#each Object.entries(tabs) as [name, { route, icon }]}
|
||||
<Link {route}>
|
||||
<Button
|
||||
size="large"
|
||||
variant={name === view.name ||
|
||||
(view.name === "diff" && name === "changes")
|
||||
? "tab-active"
|
||||
: "tab"}>
|
||||
<Icon name={icon} />
|
||||
{capitalize(name)}
|
||||
</Button>
|
||||
</Link>
|
||||
{/each}
|
||||
</Radio>
|
||||
{#each Object.entries(tabs) as [name, { route, icon }]}
|
||||
<Link {route}>
|
||||
<Button
|
||||
variant={name === view.name ||
|
||||
(view.name === "diff" && name === "changes")
|
||||
? "gray"
|
||||
: "background"}>
|
||||
<Icon name={icon} />
|
||||
{capitalize(name)}
|
||||
</Button>
|
||||
</Link>
|
||||
{/each}
|
||||
|
||||
{#if view.name === "changes"}
|
||||
<div
|
||||
class="global-hide-on-mobile-down"
|
||||
style="margin-left: auto; margin-top: -0.5rem;">
|
||||
<div class="global-hide-on-mobile-down" style="margin-left: auto;">
|
||||
<RevisionSelector {view} {baseUrl} {patch} {repo} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if view.name === "diff"}
|
||||
<div
|
||||
class="global-hide-on-mobile-down"
|
||||
style="margin-left: auto; margin-top: -0.5rem;">
|
||||
<div style:margin-left="auto">
|
||||
<CompareButton
|
||||
fromCommit={view.fromCommit}
|
||||
toCommit={view.toCommit} />
|
||||
</div>
|
||||
<div class="global-hide-on-mobile-down" style="margin-left: auto;">
|
||||
<CompareButton
|
||||
fromCommit={view.fromCommit}
|
||||
toCommit={view.toCommit} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="tabs-spacer"></div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
{#if view.name === "changes"}
|
||||
|
||||
+82
-114
@@ -1,21 +1,12 @@
|
||||
<script lang="ts">
|
||||
import type {
|
||||
BaseUrl,
|
||||
Patch,
|
||||
PatchState,
|
||||
Repo,
|
||||
SeedingPolicy,
|
||||
} from "@http-client";
|
||||
import type { BaseUrl, Patch, PatchState, Repo } from "@http-client";
|
||||
|
||||
import { HttpdClient } from "@http-client";
|
||||
import capitalize from "lodash/capitalize";
|
||||
|
||||
import { PATCHES_PER_PAGE } from "./router";
|
||||
import { baseUrlToString } from "@app/lib/utils";
|
||||
|
||||
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 Icon from "@app/components/Icon.svelte";
|
||||
import Layout from "./Layout.svelte";
|
||||
@@ -24,12 +15,9 @@
|
||||
import Loading from "@app/components/Loading.svelte";
|
||||
import PatchTeaser from "./Patch/PatchTeaser.svelte";
|
||||
import Placeholder from "@app/components/Placeholder.svelte";
|
||||
import Popover, { closeFocused } from "@app/components/Popover.svelte";
|
||||
import Separator from "./Separator.svelte";
|
||||
import Share from "./Share.svelte";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let seedingPolicy: SeedingPolicy;
|
||||
export let patches: Patch[];
|
||||
export let repo: Repo;
|
||||
export let status: PatchState["status"];
|
||||
@@ -65,27 +53,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
const stateOptions: PatchState["status"][] = [
|
||||
"draft",
|
||||
"open",
|
||||
"archived",
|
||||
"merged",
|
||||
];
|
||||
|
||||
const stateColor: Record<PatchState["status"], string> = {
|
||||
draft: "var(--color-text-draft)",
|
||||
open: "var(--color-text-open)",
|
||||
archived: "var(--color-text-archived)",
|
||||
merged: "var(--color-text-merged)",
|
||||
};
|
||||
|
||||
const stateBackground: Record<PatchState["status"], string> = {
|
||||
draft: "var(--color-surface-draft)",
|
||||
open: "var(--color-surface-open)",
|
||||
archived: "var(--color-surface-archived)",
|
||||
merged: "var(--color-surface-merged)",
|
||||
};
|
||||
|
||||
$: showMoreButton =
|
||||
!loading &&
|
||||
!error &&
|
||||
@@ -96,8 +63,9 @@
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.25rem;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
.more {
|
||||
margin-top: 2rem;
|
||||
@@ -106,21 +74,22 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.dropdown-button-counter {
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-surface-alpha-subtle);
|
||||
color: var(--color-text-primary);
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
.dropdown-list-counter {
|
||||
.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-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.title-counter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.placeholder {
|
||||
height: calc(100% - 4rem);
|
||||
@@ -132,10 +101,13 @@
|
||||
.placeholder {
|
||||
height: calc(100vh - 10rem);
|
||||
}
|
||||
.title-counter:not(.active) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {nodeAvatarUrl} {seedingPolicy} {baseUrl} {repo} activeTab="patches">
|
||||
<Layout {nodeAvatarUrl} {baseUrl} {repo} activeTab="patches">
|
||||
<svelte:fragment slot="breadcrumb">
|
||||
<Separator />
|
||||
<Link
|
||||
@@ -148,78 +120,74 @@
|
||||
</Link>
|
||||
</svelte:fragment>
|
||||
<div slot="header" class="header">
|
||||
<Popover
|
||||
popoverPadding="0"
|
||||
popoverPositionTop="2.5rem"
|
||||
popoverBorderRadius="var(--border-radius-md)">
|
||||
<Button
|
||||
let:expanded
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}
|
||||
ariaLabel="filter-dropdown"
|
||||
title="Filter patches by state">
|
||||
<div
|
||||
style:color={stateColor[status]}
|
||||
style:background={stateBackground[status]}
|
||||
style:padding="0.25rem 0.25rem"
|
||||
style:border-radius="var(--border-radius-sm)">
|
||||
<Icon
|
||||
name={status === "draft"
|
||||
? "patch-draft"
|
||||
: status === "merged"
|
||||
? "patch-merged"
|
||||
: status === "archived"
|
||||
? "patch-archived"
|
||||
: "patch"} />
|
||||
<Link
|
||||
route={{
|
||||
resource: "repo.patches",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
search: "status=open",
|
||||
}}>
|
||||
<Button variant={status === "open" ? "gray" : "background"}>
|
||||
<Icon name="patch" />
|
||||
<div class="title-counter" class:active={status === "open"}>
|
||||
Open
|
||||
<span class="counter" class:selected={status === "open"}>
|
||||
{repo.payloads["xyz.radicle.project"].meta.patches.open}
|
||||
</span>
|
||||
</div>
|
||||
{capitalize(status)}
|
||||
<div class="dropdown-button-counter">
|
||||
{repo.payloads["xyz.radicle.project"].meta.patches[status]}
|
||||
</div>
|
||||
<Icon name={expanded ? "chevron-up" : "chevron-down"} />
|
||||
</Button>
|
||||
<DropdownList slot="popover" items={stateOptions}>
|
||||
<Link
|
||||
slot="item"
|
||||
let:item
|
||||
on:afterNavigate={() => closeFocused()}
|
||||
route={{
|
||||
resource: "repo.patches",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
search: `status=${item}`,
|
||||
}}>
|
||||
<DropdownListItem selected={item === status}>
|
||||
<div
|
||||
style:color={stateColor[item]}
|
||||
style:background={stateBackground[item]}
|
||||
style:padding="0.25rem 0.25rem"
|
||||
style:border-radius="var(--border-radius-sm)">
|
||||
<Icon
|
||||
name={item === "draft"
|
||||
? "patch-draft"
|
||||
: item === "merged"
|
||||
? "patch-merged"
|
||||
: item === "archived"
|
||||
? "patch-archived"
|
||||
: "patch"} />
|
||||
</div>
|
||||
<div
|
||||
style="display: flex; gap: 1rem;justify-content: space-between; width: 100%;">
|
||||
{capitalize(item)}
|
||||
<div
|
||||
class="dropdown-list-counter"
|
||||
class:selected={item === status}>
|
||||
{repo.payloads["xyz.radicle.project"].meta.patches[item]}
|
||||
</div>
|
||||
</div>
|
||||
</DropdownListItem>
|
||||
</Link>
|
||||
</DropdownList>
|
||||
</Popover>
|
||||
|
||||
<Share />
|
||||
</Link>
|
||||
<Link
|
||||
route={{
|
||||
resource: "repo.patches",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
search: "status=draft",
|
||||
}}>
|
||||
<Button variant={status === "draft" ? "gray" : "background"}>
|
||||
<Icon name="patch-draft" />
|
||||
<div class="title-counter" class:active={status === "draft"}>
|
||||
Draft
|
||||
<span class="counter" class:selected={status === "draft"}>
|
||||
{repo.payloads["xyz.radicle.project"].meta.patches.draft}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link
|
||||
route={{
|
||||
resource: "repo.patches",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
search: "status=archived",
|
||||
}}>
|
||||
<Button variant={status === "archived" ? "gray" : "background"}>
|
||||
<Icon name="patch-archived" />
|
||||
<div class="title-counter" class:active={status === "archived"}>
|
||||
Archived
|
||||
<span class="counter" class:selected={status === "archived"}>
|
||||
{repo.payloads["xyz.radicle.project"].meta.patches.archived}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link
|
||||
route={{
|
||||
resource: "repo.patches",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
search: "status=merged",
|
||||
}}>
|
||||
<Button variant={status === "merged" ? "gray" : "background"}>
|
||||
<Icon name="patch-merged" />
|
||||
<div class="title-counter" class:active={status === "merged"}>
|
||||
Merged
|
||||
<span class="counter" class:selected={status === "merged"}>
|
||||
{repo.payloads["xyz.radicle.project"].meta.patches.merged}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<List items={allPatches}>
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { ActiveTab } from "./Header.svelte";
|
||||
import type { BaseUrl, Repo, SeedingPolicy } from "@http-client";
|
||||
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import ContextRepo from "@app/views/repos/Sidebar/ContextRepo.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import Popover from "@app/components/Popover.svelte";
|
||||
|
||||
const SIDEBAR_STATE_KEY = "sidebarState";
|
||||
|
||||
export let activeTab: ActiveTab | undefined = undefined;
|
||||
export let seedingPolicy: SeedingPolicy;
|
||||
export let baseUrl: BaseUrl;
|
||||
export let repo: Repo;
|
||||
export let collapsedOnly = false;
|
||||
|
||||
let expanded = collapsedOnly ? false : loadSidebarState();
|
||||
|
||||
export function storeSidebarState(expanded: boolean): void {
|
||||
if (localStorage) {
|
||||
localStorage.setItem(
|
||||
SIDEBAR_STATE_KEY,
|
||||
expanded ? "expanded" : "collapsed",
|
||||
);
|
||||
} else {
|
||||
console.warn(
|
||||
"localStorage isn't available, not able to persist the sidebar state without it.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function loadSidebarState(): boolean {
|
||||
const storedSidebarState = localStorage
|
||||
? localStorage.getItem(SIDEBAR_STATE_KEY)
|
||||
: null;
|
||||
|
||||
if (storedSidebarState === null) {
|
||||
return true;
|
||||
} else {
|
||||
return storedSidebarState === "expanded" ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
expanded = !expanded;
|
||||
storeSidebarState(expanded);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.sidebar {
|
||||
padding: 1rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
transition: width 150ms ease-in-out;
|
||||
width: 4.5rem;
|
||||
}
|
||||
.sidebar.expanded {
|
||||
width: 22.5rem;
|
||||
}
|
||||
.repo-navigation {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.counter {
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-surface-mid);
|
||||
color: var(--color-text-tertiary);
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
.selected {
|
||||
background-color: var(--color-surface-alpha-subtle);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.hover {
|
||||
background-color: var(--color-surface-strong);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.title-counter {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
gap: 0.5rem;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity 150ms ease-in-out;
|
||||
}
|
||||
.title-counter.expanded {
|
||||
opacity: 1;
|
||||
}
|
||||
.sidebar-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
.repo {
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.box {
|
||||
padding: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
background-color: var(--color-surface-subtle);
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
font: var(--txt-body-m-regular);
|
||||
border-radius: var(--border-radius-md);
|
||||
}
|
||||
.repo.expanded {
|
||||
opacity: 1;
|
||||
height: initial;
|
||||
overflow: initial;
|
||||
transition: opacity 150ms;
|
||||
transition-delay: 150ms;
|
||||
}
|
||||
.vertical-buttons {
|
||||
opacity: 1;
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
transition: opacity 150ms ease-in-out;
|
||||
transition-delay: 60ms;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.vertical-buttons.expanded {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.icon {
|
||||
transform: rotate(180deg);
|
||||
transition: transform 150ms ease-in-out;
|
||||
}
|
||||
.icon.expanded {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
.bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="sidebar" class:expanded>
|
||||
<!-- Top Navigation Items -->
|
||||
<div class="repo-navigation">
|
||||
<Link
|
||||
title="Source"
|
||||
route={{
|
||||
resource: "repo.source",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
path: "/",
|
||||
}}>
|
||||
<Button
|
||||
stylePadding="0.5rem 0.75rem"
|
||||
size="large"
|
||||
styleWidth="100%"
|
||||
styleJustifyContent="flex-start"
|
||||
variant={activeTab === "source" ? "gray" : "background"}>
|
||||
<Icon name="chevron-left-right" />
|
||||
<span class="title-counter" class:expanded>Source</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link
|
||||
title={`${repo.payloads["xyz.radicle.project"].meta.issues.open} Issues`}
|
||||
route={{
|
||||
resource: "repo.issues",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
}}>
|
||||
<Button
|
||||
stylePadding="0.5rem 0.75rem"
|
||||
let:hover
|
||||
size="large"
|
||||
styleJustifyContent="flex-start"
|
||||
styleWidth="100%"
|
||||
variant={activeTab === "issues" ? "gray" : "background"}>
|
||||
<Icon name="issue" />
|
||||
<div class="title-counter" class:expanded>
|
||||
Issues
|
||||
<span
|
||||
class="counter"
|
||||
class:selected={activeTab === "issues"}
|
||||
class:hover={hover && activeTab !== "issues"}>
|
||||
{repo.payloads["xyz.radicle.project"].meta.issues.open}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
title={`${repo.payloads["xyz.radicle.project"].meta.patches.open} Patches`}
|
||||
route={{
|
||||
resource: "repo.patches",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
}}>
|
||||
<Button
|
||||
stylePadding="0.5rem 0.75rem"
|
||||
let:hover
|
||||
size="large"
|
||||
styleWidth="100%"
|
||||
styleJustifyContent="flex-start"
|
||||
variant={activeTab === "patches" ? "gray" : "background"}>
|
||||
<Icon name="patch" />
|
||||
<div class="title-counter" class:expanded>
|
||||
Patches
|
||||
<span
|
||||
class="counter"
|
||||
class:hover={hover && activeTab !== "patches"}
|
||||
class:selected={activeTab === "patches"}>
|
||||
{repo.payloads["xyz.radicle.project"].meta.patches.open}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<!-- Context and other information section -->
|
||||
<div class="bottom">
|
||||
<div class="repo box" class:expanded>
|
||||
<ContextRepo
|
||||
{baseUrl}
|
||||
repoThreshold={repo.threshold}
|
||||
repoDelegates={repo.delegates}
|
||||
{seedingPolicy} />
|
||||
</div>
|
||||
<div class="vertical-buttons" class:expanded style:gap="0.5rem">
|
||||
<Popover popoverPositionBottom="0" popoverPositionLeft="3rem">
|
||||
<Button
|
||||
stylePadding="0 0.75rem"
|
||||
variant="background"
|
||||
title="Info"
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}>
|
||||
<Icon name="guide" />
|
||||
</Button>
|
||||
|
||||
<div slot="popover" class="txt-body-m-regular" style:width="18rem">
|
||||
<ContextRepo
|
||||
{baseUrl}
|
||||
repoThreshold={repo.threshold}
|
||||
repoDelegates={repo.delegates}
|
||||
{seedingPolicy} />
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
{#if !collapsedOnly}
|
||||
<div class="sidebar-footer" style:flex-direction="row">
|
||||
<Button title="Collapse" on:click={toggleSidebar} variant="background">
|
||||
<div class="icon" class:expanded>
|
||||
<Icon name="chevron-left" />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,53 +2,78 @@
|
||||
import type { BaseUrl, Repo, SeedingPolicy } from "@http-client";
|
||||
|
||||
import capitalize from "lodash/capitalize";
|
||||
|
||||
import IconButton from "@app/components/IconButton.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import HoverPopover from "@app/components/HoverPopover.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import NodeId from "@app/components/NodeId.svelte";
|
||||
import UserAvatar from "@app/components/UserAvatar.svelte";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
|
||||
export let repoThreshold: number;
|
||||
export let repoDelegates: Repo["delegates"];
|
||||
export let seedingPolicy: SeedingPolicy;
|
||||
|
||||
let delegateExpanded = false;
|
||||
let policyExpanded = false;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.item-header {
|
||||
gap: 2rem;
|
||||
.context-repo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 0.2rem 0;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.item-header:first-child {
|
||||
margin-top: 0;
|
||||
.label {
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
.item-header:last-child {
|
||||
margin-bottom: 0;
|
||||
.value {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.nid {
|
||||
height: 21.5px;
|
||||
margin: 0.5rem 0;
|
||||
.avatars {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.avatars :global(.popover) {
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
.avatar-popover {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.description {
|
||||
font: var(--txt-body-s-medium);
|
||||
color: var(--color-text-quaternary);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="item-header">
|
||||
<span>Delegates</span>
|
||||
<div class="global-flex-item">
|
||||
<span class="txt-body-m-semibold">
|
||||
<div class="context-repo">
|
||||
<div class="row">
|
||||
<span class="label txt-body-m-medium">Delegates</span>
|
||||
<span class="value txt-body-m-medium">
|
||||
{repoThreshold}/{repoDelegates.length}
|
||||
</span>
|
||||
<IconButton on:click={() => (delegateExpanded = !delegateExpanded)}>
|
||||
<Icon name={delegateExpanded ? "chevron-up" : "chevron-down"} />
|
||||
</IconButton>
|
||||
<div class="avatars">
|
||||
{#each repoDelegates as delegate}
|
||||
<HoverPopover>
|
||||
<Link
|
||||
slot="toggle"
|
||||
style="display: flex"
|
||||
route={{ resource: "users", did: delegate.id, baseUrl }}>
|
||||
<UserAvatar nodeId={delegate.id} styleWidth="1rem" />
|
||||
</Link>
|
||||
<div slot="popover" class="avatar-popover">
|
||||
<NodeId {baseUrl} nodeId={delegate.id} alias={delegate.alias} />
|
||||
</div>
|
||||
</HoverPopover>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if delegateExpanded}
|
||||
<div style:color="var(--color-text-tertiary)" style:margin-bottom="1rem">
|
||||
<div class="description">
|
||||
{#if repoDelegates.length === 1}
|
||||
Any changes accepted by the sole delegate will be included in the
|
||||
canonical branch.
|
||||
@@ -57,29 +82,15 @@
|
||||
to be included in the canonical branch.
|
||||
{/if}
|
||||
</div>
|
||||
<div class="delegates">
|
||||
{#each repoDelegates as delegate}
|
||||
<div class="nid">
|
||||
<NodeId {baseUrl} nodeId={delegate.id} alias={delegate.alias} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="item-header">
|
||||
<span style:text-wrap="nowrap">Seeding Scope</span>
|
||||
<div class="global-flex-item">
|
||||
<span class="txt-body-m-semibold">
|
||||
<div class="row">
|
||||
<span class="label txt-body-m-medium">Seeding Scope</span>
|
||||
<span class="value txt-body-m-medium">
|
||||
{capitalize(
|
||||
"scope" in seedingPolicy ? seedingPolicy.scope : "not defined",
|
||||
)}
|
||||
</span>
|
||||
<IconButton on:click={() => (policyExpanded = !policyExpanded)}>
|
||||
<Icon name={policyExpanded ? "chevron-up" : "chevron-down"} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
{#if policyExpanded}
|
||||
<div style:color="var(--color-text-tertiary)">
|
||||
<div class="description">
|
||||
{#if seedingPolicy.policy === "block"}
|
||||
Seeding scope only has an effect when a repository is seeded. This repo
|
||||
isn't seeded by the seed node.
|
||||
@@ -89,4 +100,4 @@
|
||||
This repository tracks only peers followed by the seed node.
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -10,17 +10,18 @@
|
||||
|
||||
import { HttpdClient } from "@http-client";
|
||||
|
||||
import { formatQualifiedRefname } from "@app/lib/utils";
|
||||
|
||||
import BlobComponent from "./Source/Blob.svelte";
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import CloneButton from "@app/views/repos/Header/CloneButton.svelte";
|
||||
import FilePath from "@app/components/FilePath.svelte";
|
||||
import Header from "./Source/Header.svelte";
|
||||
import Layout from "./Layout.svelte";
|
||||
import Placeholder from "@app/components/Placeholder.svelte";
|
||||
|
||||
import BlobComponent from "./Source/Blob.svelte";
|
||||
import FilePath from "@app/components/FilePath.svelte";
|
||||
import RepoNameHeader from "./Source/RepoNameHeader.svelte";
|
||||
import Separator from "./Separator.svelte";
|
||||
import TreeComponent from "./Source/Tree.svelte";
|
||||
import { formatQualifiedRefname } from "@app/lib/utils";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let blobResult: BlobResult;
|
||||
@@ -52,6 +53,11 @@
|
||||
});
|
||||
};
|
||||
|
||||
$: currentRefname = formatQualifiedRefname(
|
||||
revision || repo.payloads["xyz.radicle.project"].data.defaultBranch,
|
||||
peer,
|
||||
);
|
||||
|
||||
$: baseRoute = {
|
||||
resource: "repo.source",
|
||||
node: baseUrl,
|
||||
@@ -68,13 +74,14 @@
|
||||
.container {
|
||||
display: flex;
|
||||
width: inherit;
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.column-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-right: 0.5rem;
|
||||
border-right: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.column-right {
|
||||
@@ -82,8 +89,6 @@
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding-bottom: 2.5rem;
|
||||
max-width: 75rem;
|
||||
margin: 0 auto;
|
||||
/* To allow pre elements to shrink when overflowing */
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -122,7 +127,6 @@
|
||||
{baseUrl}
|
||||
{nodeAvatarUrl}
|
||||
{repo}
|
||||
{seedingPolicy}
|
||||
activeTab="source"
|
||||
stylePaddingBottom="0">
|
||||
<svelte:fragment slot="breadcrumb">
|
||||
@@ -131,16 +135,19 @@
|
||||
<FilePath filenameWithPath={path} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<RepoNameHeader
|
||||
{repo}
|
||||
currentRefname={formatQualifiedRefname(
|
||||
revision || repo.payloads["xyz.radicle.project"].data.defaultBranch,
|
||||
peer,
|
||||
)}
|
||||
{baseUrl}
|
||||
slot="header" />
|
||||
<svelte:fragment slot="actions">
|
||||
<CloneButton
|
||||
{baseUrl}
|
||||
{currentRefname}
|
||||
id={repo.rid}
|
||||
name={repo.payloads["xyz.radicle.project"].data.name} />
|
||||
</svelte:fragment>
|
||||
<RepoNameHeader {repo} {baseUrl} {seedingPolicy} slot="header" />
|
||||
|
||||
<div style:margin="1rem" slot="subheader">
|
||||
<div
|
||||
style:padding="1rem"
|
||||
style:border-bottom="1px solid var(--color-border-subtle)"
|
||||
slot="subheader">
|
||||
<Header
|
||||
filesLinkActive={true}
|
||||
historyLinkActive={false}
|
||||
|
||||
@@ -142,6 +142,8 @@
|
||||
.markdown-wrapper {
|
||||
padding: 2rem;
|
||||
background-color: var(--color-surface-canvas);
|
||||
max-width: 75rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@media (max-width: 719.98px) {
|
||||
.markdown-wrapper {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
<script lang="ts" context="module">
|
||||
// Cache commit counts across component remounts (tab navigation).
|
||||
const commitCountCache: Record<string, number> = {};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { RepoRoute } from "../router";
|
||||
import type { BaseUrl, Repo, Remote, Tree } from "@http-client";
|
||||
@@ -9,7 +14,7 @@
|
||||
import CommitButton from "../components/CommitButton.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import Loading from "@app/components/Loading.svelte";
|
||||
|
||||
import PeerBranchSelector from "./PeerBranchSelector.svelte";
|
||||
|
||||
export let commit: string;
|
||||
@@ -27,6 +32,22 @@
|
||||
export let tree: Tree;
|
||||
|
||||
const api = new HttpdClient(node);
|
||||
let commitCount: number | undefined = commitCountCache[commit];
|
||||
|
||||
function fetchCommitCount(rid: string, sha: string) {
|
||||
const cached = commitCountCache[sha];
|
||||
if (cached !== undefined) {
|
||||
commitCount = cached;
|
||||
} else {
|
||||
void api.repo.getTreeStatsBySha(rid, sha).then(stats => {
|
||||
commitCountCache[sha] = stats.commits;
|
||||
commitCount = stats.commits;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: fetchCommitCount(repo.rid, commit);
|
||||
|
||||
let selectedBranch: string | undefined;
|
||||
let commitButtonVariant: ComponentProps<CommitButton>["variant"] | undefined =
|
||||
undefined;
|
||||
@@ -56,16 +77,6 @@
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.top-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
row-gap: 0.5rem;
|
||||
gap: 1px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
font: var(--txt-body-s-regular);
|
||||
display: flex;
|
||||
@@ -73,16 +84,17 @@
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}
|
||||
.header::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -1rem;
|
||||
bottom: 0;
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
width: calc(100% + 1rem);
|
||||
z-index: -1;
|
||||
.branch-commit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.mobile-branch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.counter {
|
||||
@@ -90,6 +102,8 @@
|
||||
background-color: var(--color-surface-mid);
|
||||
color: var(--color-text-tertiary);
|
||||
padding: 0 0.25rem;
|
||||
min-width: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title-counter {
|
||||
@@ -104,7 +118,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="top-header">
|
||||
<div class="mobile-branch global-hide-on-small-desktop-up" style:gap="1px">
|
||||
{#if selectedBranch}
|
||||
<PeerBranchSelector
|
||||
{peers}
|
||||
@@ -114,12 +128,75 @@
|
||||
{repo}
|
||||
{selectedBranch} />
|
||||
{/if}
|
||||
<div class="global-flex-item txt-overflow" style:gap="1px">
|
||||
<CommitButton
|
||||
variant={commitButtonVariant}
|
||||
styleMinWidth="0"
|
||||
hideSummaryOnMobile
|
||||
repoId={repo.rid}
|
||||
commit={lastCommit}
|
||||
baseUrl={node} />
|
||||
{#if !onCanonical}
|
||||
<Link route={baseRoute}>
|
||||
<Button
|
||||
variant="not-selected"
|
||||
styleBorderRadius="0 var(--border-radius-sm) var(--border-radius-sm) 0">
|
||||
<Icon name="close" />
|
||||
</Button>
|
||||
</Link>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<div style="display: flex; gap: 0.375rem;">
|
||||
<Link
|
||||
route={{
|
||||
resource: "repo.source",
|
||||
repo: repo.rid,
|
||||
node: node,
|
||||
peer,
|
||||
revision,
|
||||
}}>
|
||||
<Button variant={filesLinkActive ? "gray" : "background"}>
|
||||
<Icon name="document" />Files
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
route={{
|
||||
resource: "repo.history",
|
||||
repo: repo.rid,
|
||||
node: node,
|
||||
peer,
|
||||
revision,
|
||||
}}>
|
||||
<Button variant={historyLinkActive ? "gray" : "background"}>
|
||||
<Icon name="commit" />
|
||||
<div class="title-counter">
|
||||
Commits
|
||||
{#if commitCount !== undefined}
|
||||
<div class="counter" class:selected={historyLinkActive}>
|
||||
{commitCount}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div class="branch-commit global-hide-on-mobile-down" style:gap="1px">
|
||||
{#if selectedBranch}
|
||||
<PeerBranchSelector
|
||||
{peers}
|
||||
{peer}
|
||||
{baseRoute}
|
||||
{onCanonical}
|
||||
{repo}
|
||||
{selectedBranch} />
|
||||
{/if}
|
||||
<CommitButton
|
||||
variant={commitButtonVariant}
|
||||
styleMinWidth="0"
|
||||
styleWidth="100%"
|
||||
hideSummaryOnMobile={false}
|
||||
hideSummaryOnMobile
|
||||
repoId={repo.rid}
|
||||
commit={lastCommit}
|
||||
baseUrl={node} />
|
||||
@@ -134,43 +211,3 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<div style="display: flex; gap: 0.375rem;">
|
||||
<Link
|
||||
route={{
|
||||
resource: "repo.source",
|
||||
repo: repo.rid,
|
||||
node: node,
|
||||
peer,
|
||||
revision,
|
||||
}}>
|
||||
<Button size="large" variant={filesLinkActive ? "tab-active" : "tab"}>
|
||||
<Icon name="document" />Files
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
route={{
|
||||
resource: "repo.history",
|
||||
repo: repo.rid,
|
||||
node: node,
|
||||
peer,
|
||||
revision,
|
||||
}}>
|
||||
<Button size="large" variant={historyLinkActive ? "tab-active" : "tab"}>
|
||||
<Icon name="commit" />
|
||||
<div class="title-counter">
|
||||
Commits
|
||||
{#await api.repo.getTreeStatsBySha(repo.rid, commit)}
|
||||
<Loading small center noDelay grayscale />
|
||||
{:then stats}
|
||||
<div class="counter" class:selected={historyLinkActive}>
|
||||
{stats.commits}
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
<DropdownListItem
|
||||
selected={selectedPeer?.id === peer?.id &&
|
||||
selectedBranch === revision}
|
||||
style={`${subgridStyle} gap: inherit;`}>
|
||||
style={subgridStyle}>
|
||||
<div class="global-flex-item">
|
||||
<Icon name="branch" />
|
||||
<span class="txt-overflow">
|
||||
@@ -231,7 +231,7 @@
|
||||
</Link>
|
||||
{:else}
|
||||
<div
|
||||
style="gap: inherit; padding: 0.5rem 0.375rem;"
|
||||
style="padding: 0.5rem 0.375rem;"
|
||||
class="subgrid-item txt-body-m-regular"
|
||||
style:color="var(--color-text-tertiary)">
|
||||
No entries found
|
||||
@@ -245,9 +245,7 @@
|
||||
searchInput = "";
|
||||
toggle();
|
||||
}}>
|
||||
<DropdownListItem
|
||||
selected={onCanonical}
|
||||
style={`${subgridStyle} gap: inherit;`}>
|
||||
<DropdownListItem selected={onCanonical} style={subgridStyle}>
|
||||
<div class="global-flex-item">
|
||||
<Icon name="branch" />
|
||||
{repo.payloads["xyz.radicle.project"].data.defaultBranch}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
peer: peer.remote.id,
|
||||
revision: name,
|
||||
})}
|
||||
style={`${subgridStyle} padding-left: 2.3rem; gap: inherit;`}>
|
||||
style={`${subgridStyle} padding-left: 2.3rem;`}>
|
||||
<div class="global-flex-item">
|
||||
<Icon name="branch" />
|
||||
<span class="txt-overflow">
|
||||
|
||||
@@ -1,32 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { BaseUrl, Repo } from "@http-client";
|
||||
import type { BaseUrl, Repo, SeedingPolicy } from "@http-client";
|
||||
|
||||
import dompurify from "dompurify";
|
||||
import { markdown } from "@app/lib/markdown";
|
||||
import { baseUrlToString, twemoji } from "@app/lib/utils";
|
||||
import { formatRepositoryId, twemoji } from "@app/lib/utils";
|
||||
|
||||
import Badge from "@app/components/Badge.svelte";
|
||||
import CloneButton from "@app/views/repos/Header/CloneButton.svelte";
|
||||
import ContextRepo from "@app/views/repos/Sidebar/ContextRepo.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import Id from "@app/components/Id.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import SeedButton from "@app/views/repos/Header/SeedButton.svelte";
|
||||
import Share from "@app/views/repos/Share.svelte";
|
||||
import RepoAvatar from "@app/components/RepoAvatar.svelte";
|
||||
|
||||
export let repo: Repo;
|
||||
export let baseUrl: BaseUrl;
|
||||
export let currentRefname: string;
|
||||
|
||||
let enabledArchiveDownload = false;
|
||||
|
||||
void fetch(
|
||||
`${baseUrlToString(baseUrl)}/raw/${repo.rid}/archive/${currentRefname}`,
|
||||
{
|
||||
method: "HEAD",
|
||||
},
|
||||
).then(response => {
|
||||
enabledArchiveDownload = response.ok;
|
||||
});
|
||||
export let seedingPolicy: SeedingPolicy;
|
||||
|
||||
function render(content: string): string {
|
||||
return dompurify.sanitize(
|
||||
@@ -38,6 +26,28 @@
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.header-layout {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
.avatar {
|
||||
flex-shrink: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
.meta {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
.info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 1rem;
|
||||
border-left: 1px solid var(--color-border-subtle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.title {
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
@@ -47,77 +57,100 @@
|
||||
justify-content: left;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
padding: 1rem 1rem 0 1rem;
|
||||
}
|
||||
.description {
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
}
|
||||
.repo-name:hover {
|
||||
color: inherit;
|
||||
}
|
||||
.description {
|
||||
margin-top: 0.5rem;
|
||||
font: var(--txt-body-m-regular);
|
||||
color: var(--color-text-tertiary);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.description :global(a) {
|
||||
border-bottom: 1px solid var(--color-text-tertiary);
|
||||
}
|
||||
.description :global(a:hover) {
|
||||
border-bottom: 1px solid var(--color-text-primary);
|
||||
}
|
||||
.id {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.title-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@media (max-width: 719.98px) {
|
||||
.header-layout {
|
||||
flex-wrap: wrap;
|
||||
padding: 1rem;
|
||||
}
|
||||
.avatar :global(img) {
|
||||
width: 4rem !important;
|
||||
}
|
||||
.meta {
|
||||
flex-basis: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.info {
|
||||
flex-basis: 100%;
|
||||
padding: 0;
|
||||
border-left: none;
|
||||
border-top: 1px solid var(--color-border-subtle);
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
.mobile-description {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="title-container">
|
||||
<div class="title">
|
||||
<span class="txt-overflow">
|
||||
<Link
|
||||
route={{
|
||||
resource: "repo.source",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
}}>
|
||||
<span class="repo-name">
|
||||
{project.data.name}
|
||||
</span>
|
||||
</Link>
|
||||
</span>
|
||||
{#if repo.visibility.type === "private"}
|
||||
<Badge variant="private" size="tiny">
|
||||
<Icon name="lock" />
|
||||
Private
|
||||
</Badge>
|
||||
{/if}
|
||||
<div style="margin-left: auto; display: flex; gap: 0.5rem;">
|
||||
<Share />
|
||||
<div
|
||||
style:display="flex"
|
||||
style:gap="0.5rem"
|
||||
class="global-hide-on-mobile-down">
|
||||
<CloneButton
|
||||
{enabledArchiveDownload}
|
||||
{baseUrl}
|
||||
{currentRefname}
|
||||
id={repo.rid}
|
||||
name={project.data.name} />
|
||||
<SeedButton seedCount={repo.seeding} repoId={repo.rid} />
|
||||
</div>
|
||||
<div
|
||||
style:display="flex"
|
||||
style:gap="0.5rem"
|
||||
class="global-hide-on-small-desktop-up">
|
||||
<SeedButton disabled seedCount={repo.seeding} repoId={repo.rid} />
|
||||
</div>
|
||||
<div class="header-layout">
|
||||
<div class="avatar">
|
||||
<RepoAvatar name={project.data.name} rid={repo.rid} styleWidth="10rem" />
|
||||
</div>
|
||||
<div class="meta">
|
||||
<div class="title">
|
||||
<span class="txt-overflow">
|
||||
<Link
|
||||
route={{
|
||||
resource: "repo.source",
|
||||
repo: repo.rid,
|
||||
node: baseUrl,
|
||||
}}>
|
||||
<span class="repo-name">
|
||||
{project.data.name}
|
||||
</span>
|
||||
</Link>
|
||||
</span>
|
||||
{#if repo.visibility.type === "private"}
|
||||
<Badge variant="private" size="tiny">
|
||||
<Icon name="lock" />
|
||||
Private
|
||||
</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<Id shorten={false} id={repo.rid} ariaLabel="repo-id">
|
||||
{formatRepositoryId(repo.rid)}
|
||||
</Id>
|
||||
</div>
|
||||
<div
|
||||
class="description global-hide-on-mobile-down"
|
||||
title={project.data.description}
|
||||
use:twemoji>
|
||||
{@html render(project.data.description)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="id">
|
||||
<Id shorten={false} id={repo.rid} ariaLabel="repo-id" />
|
||||
<div
|
||||
class="description mobile-description global-hide-on-small-desktop-up"
|
||||
title={project.data.description}
|
||||
use:twemoji>
|
||||
{@html render(project.data.description)}
|
||||
</div>
|
||||
<div class="info">
|
||||
<ContextRepo
|
||||
{baseUrl}
|
||||
repoThreshold={repo.threshold}
|
||||
repoDelegates={repo.delegates}
|
||||
{seedingPolicy} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="description" use:twemoji>
|
||||
{@html render(project.data.description)}
|
||||
</div>
|
||||
|
||||
@@ -145,7 +145,6 @@ export type RepoLoadedRoute =
|
||||
resource: "repo.commit";
|
||||
params: {
|
||||
baseUrl: BaseUrl;
|
||||
seedingPolicy: SeedingPolicy;
|
||||
repo: Repo;
|
||||
commit: Commit;
|
||||
nodeAvatarUrl: string | undefined;
|
||||
@@ -155,7 +154,6 @@ export type RepoLoadedRoute =
|
||||
resource: "repo.issue";
|
||||
params: {
|
||||
baseUrl: BaseUrl;
|
||||
seedingPolicy: SeedingPolicy;
|
||||
repo: Repo;
|
||||
rawPath: (commit?: string) => string;
|
||||
issue: Issue;
|
||||
@@ -166,7 +164,6 @@ export type RepoLoadedRoute =
|
||||
resource: "repo.issues";
|
||||
params: {
|
||||
baseUrl: BaseUrl;
|
||||
seedingPolicy: SeedingPolicy;
|
||||
repo: Repo;
|
||||
issues: Issue[];
|
||||
status: IssueState["status"];
|
||||
@@ -177,7 +174,6 @@ export type RepoLoadedRoute =
|
||||
resource: "repo.patches";
|
||||
params: {
|
||||
baseUrl: BaseUrl;
|
||||
seedingPolicy: SeedingPolicy;
|
||||
repo: Repo;
|
||||
patches: Patch[];
|
||||
status: PatchState["status"];
|
||||
@@ -188,7 +184,6 @@ export type RepoLoadedRoute =
|
||||
resource: "repo.patch";
|
||||
params: {
|
||||
baseUrl: BaseUrl;
|
||||
seedingPolicy: SeedingPolicy;
|
||||
repo: Repo;
|
||||
rawPath: (commit?: string) => string;
|
||||
patch: Patch;
|
||||
@@ -270,10 +265,9 @@ export async function loadRepoRoute(
|
||||
} else if (route.resource === "repo.history") {
|
||||
return await loadHistoryView(route, previousLoaded);
|
||||
} else if (route.resource === "repo.commit") {
|
||||
const [repo, commit, seedingPolicy, node] = await Promise.all([
|
||||
const [repo, commit, node] = await Promise.all([
|
||||
api.repo.getByRid(route.repo),
|
||||
api.repo.getCommitBySha(route.repo, route.commit),
|
||||
api.getPolicyByRid(route.repo),
|
||||
api.getNode(),
|
||||
]);
|
||||
|
||||
@@ -281,7 +275,6 @@ export async function loadRepoRoute(
|
||||
resource: "repo.commit",
|
||||
params: {
|
||||
baseUrl: route.node,
|
||||
seedingPolicy,
|
||||
repo,
|
||||
commit,
|
||||
nodeAvatarUrl: node.avatarUrl,
|
||||
@@ -318,14 +311,13 @@ async function loadPatchesView(
|
||||
const searchParams = new URLSearchParams(route.search || "");
|
||||
const status = (searchParams.get("status") as PatchState["status"]) || "open";
|
||||
|
||||
const [repo, patches, seedingPolicy, node] = await Promise.all([
|
||||
const [repo, patches, node] = await Promise.all([
|
||||
api.repo.getByRid(route.repo),
|
||||
api.repo.getAllPatches(route.repo, {
|
||||
status,
|
||||
page: 0,
|
||||
perPage: PATCHES_PER_PAGE,
|
||||
}),
|
||||
api.getPolicyByRid(route.repo),
|
||||
api.getNode(),
|
||||
]);
|
||||
|
||||
@@ -333,7 +325,6 @@ async function loadPatchesView(
|
||||
resource: "repo.patches",
|
||||
params: {
|
||||
baseUrl: route.node,
|
||||
seedingPolicy,
|
||||
patches,
|
||||
status,
|
||||
repo,
|
||||
@@ -348,14 +339,13 @@ async function loadIssuesView(
|
||||
const api = new HttpdClient(route.node);
|
||||
const status = route.status || "open";
|
||||
|
||||
const [repo, issues, seedingPolicy, node] = await Promise.all([
|
||||
const [repo, issues, node] = await Promise.all([
|
||||
api.repo.getByRid(route.repo),
|
||||
api.repo.getAllIssues(route.repo, {
|
||||
status,
|
||||
page: 0,
|
||||
perPage: ISSUES_PER_PAGE,
|
||||
}),
|
||||
api.getPolicyByRid(route.repo),
|
||||
api.getNode(),
|
||||
]);
|
||||
|
||||
@@ -363,7 +353,6 @@ async function loadIssuesView(
|
||||
resource: "repo.issues",
|
||||
params: {
|
||||
baseUrl: route.node,
|
||||
seedingPolicy,
|
||||
issues,
|
||||
status,
|
||||
repo,
|
||||
@@ -623,17 +612,15 @@ async function loadIssueView(route: RepoIssueRoute): Promise<RepoLoadedRoute> {
|
||||
route.repo
|
||||
}${commit ? `/${commit}` : ""}`;
|
||||
|
||||
const [repo, issue, seedingPolicy, node] = await Promise.all([
|
||||
const [repo, issue, node] = await Promise.all([
|
||||
api.repo.getByRid(route.repo),
|
||||
api.repo.getIssueById(route.repo, route.issue),
|
||||
api.getPolicyByRid(route.repo),
|
||||
api.getNode(),
|
||||
]);
|
||||
return {
|
||||
resource: "repo.issue",
|
||||
params: {
|
||||
baseUrl: route.node,
|
||||
seedingPolicy,
|
||||
repo,
|
||||
rawPath,
|
||||
issue,
|
||||
@@ -655,7 +642,6 @@ async function loadPatchView(
|
||||
let repoPromise: Promise<Repo>;
|
||||
let patchPromise: Promise<Patch>;
|
||||
let nodePromise: Promise<Partial<Node>>;
|
||||
let seedingPolicyPromise: Promise<SeedingPolicy>;
|
||||
|
||||
if (
|
||||
previousLoaded.resource === "repo.patch" &&
|
||||
@@ -664,20 +650,17 @@ async function loadPatchView(
|
||||
) {
|
||||
repoPromise = Promise.resolve(previousLoaded.params.repo);
|
||||
patchPromise = Promise.resolve(previousLoaded.params.patch);
|
||||
seedingPolicyPromise = Promise.resolve(previousLoaded.params.seedingPolicy);
|
||||
nodePromise = Promise.resolve({
|
||||
avatarUrl: previousLoaded.params.nodeAvatarUrl,
|
||||
});
|
||||
} else {
|
||||
repoPromise = api.repo.getByRid(route.repo);
|
||||
patchPromise = api.repo.getPatchById(route.repo, route.patch);
|
||||
seedingPolicyPromise = api.getPolicyByRid(route.repo);
|
||||
nodePromise = api.getNode();
|
||||
}
|
||||
const [repo, patch, seedingPolicy, { avatarUrl }] = await Promise.all([
|
||||
const [repo, patch, { avatarUrl }] = await Promise.all([
|
||||
repoPromise,
|
||||
patchPromise,
|
||||
seedingPolicyPromise,
|
||||
nodePromise,
|
||||
]);
|
||||
|
||||
@@ -740,7 +723,6 @@ async function loadPatchView(
|
||||
resource: "repo.patch",
|
||||
params: {
|
||||
baseUrl: route.node,
|
||||
seedingPolicy,
|
||||
repo,
|
||||
rawPath,
|
||||
patch,
|
||||
|
||||
@@ -69,8 +69,7 @@ test("copy to clipboard", async ({ page, browserName, context }) => {
|
||||
await page.getByRole("button", { name: "59a0821", exact: true }).click();
|
||||
await expectClipboard("59a0821edc73630bce540596cffc7854da557365");
|
||||
|
||||
await page.getByLabel("filter-dropdown").click();
|
||||
await page.getByRole("button", { name: "Draft" }).click();
|
||||
await page.getByRole("link", { name: "Draft" }).click();
|
||||
await page.getByRole("button", { name: "783d33c", exact: true }).click();
|
||||
await expectClipboard("783d33c5b14e13234d4d7affa98bd0b52d1b1ea3");
|
||||
}
|
||||
|
||||
+32
-15
@@ -21,10 +21,10 @@ test("navigate to repo", async ({ page }) => {
|
||||
// Header.
|
||||
{
|
||||
const name = page.getByRole("link", { name: "source-browsing" }).nth(1);
|
||||
const id = page.getByText(sourceBrowsingRid);
|
||||
const description = page.getByText(
|
||||
"Git repository for source browsing tests",
|
||||
);
|
||||
const id = page.getByLabel("repo-id");
|
||||
const description = page
|
||||
.getByText("Git repository for source browsing tests")
|
||||
.first();
|
||||
|
||||
await expect(name).toBeVisible();
|
||||
await expect(id).toBeVisible();
|
||||
@@ -33,7 +33,9 @@ test("navigate to repo", async ({ page }) => {
|
||||
|
||||
// Repo menu shows default selected branch and commit and contributor counts.
|
||||
{
|
||||
await expect(page.getByTitle("Change branch")).toBeVisible();
|
||||
await expect(
|
||||
page.locator('[title="Change branch"]:visible').first(),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page
|
||||
.getByRole("button", {
|
||||
@@ -66,7 +68,7 @@ test("repo description", async ({ page, peer }) => {
|
||||
await page.goto(peer.ridUrl(rid));
|
||||
await page.waitForLoadState("networkidle");
|
||||
await expect(
|
||||
page.getByText("Radicle Heartwood Protocol & Stack"),
|
||||
page.getByText("Radicle Heartwood Protocol & Stack").first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -83,7 +85,9 @@ test("show source tree at specific revision", async ({ page }) => {
|
||||
})
|
||||
.click();
|
||||
|
||||
await expect(page.getByTitle("Current HEAD")).toContainText("335dd6d");
|
||||
await expect(page.getByTitle("Current HEAD").first()).toContainText(
|
||||
"335dd6d",
|
||||
);
|
||||
await expect(page.locator(".source-tree")).toHaveText("bin src");
|
||||
await expect(
|
||||
page.getByRole("link", {
|
||||
@@ -317,11 +321,15 @@ test("peer and branch switching", async ({ page }) => {
|
||||
// Alice's peer.
|
||||
{
|
||||
await changeBranch("alice", `main ${shortAliceHead}`, page);
|
||||
await expect(page.getByTitle("Change branch")).toHaveText(/alice/);
|
||||
await expect(
|
||||
page.locator('[title="Change branch"]:visible').first(),
|
||||
).toHaveText(/alice/);
|
||||
|
||||
// Default `main` branch.
|
||||
{
|
||||
await expect(page.getByTitle("Change branch")).toHaveText(/main/);
|
||||
await expect(
|
||||
page.locator('[title="Change branch"]:visible').first(),
|
||||
).toHaveText(/main/);
|
||||
await expect(
|
||||
page
|
||||
.getByRole("button", {
|
||||
@@ -339,8 +347,11 @@ test("peer and branch switching", async ({ page }) => {
|
||||
// Feature branch with a slash in the name.
|
||||
{
|
||||
await changeBranch("alice", "feature/branch", page);
|
||||
await page.getByTitle("Change branch").click();
|
||||
await page.getByText("feature/branch").click();
|
||||
await page.locator('[title="Change branch"]:visible').first().click();
|
||||
await page
|
||||
.getByRole("button", { name: "feature/branch" })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.getByRole("button", { name: "feature/branch" }),
|
||||
@@ -379,10 +390,16 @@ test("peer and branch switching", async ({ page }) => {
|
||||
{
|
||||
await page.getByRole("link", { name: "source-browsing" }).nth(1).click();
|
||||
|
||||
await expect(page.getByTitle("Change branch")).not.toContainText("alice");
|
||||
await expect(page.getByTitle("Change branch")).not.toContainText("bob");
|
||||
await expect(
|
||||
page.locator('[title="Change branch"]:visible').first(),
|
||||
).not.toContainText("alice");
|
||||
await expect(
|
||||
page.locator('[title="Change branch"]:visible').first(),
|
||||
).not.toContainText("bob");
|
||||
|
||||
await expect(page.getByTitle("Change branch")).toBeVisible();
|
||||
await expect(
|
||||
page.locator('[title="Change branch"]:visible').first(),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page
|
||||
.getByRole("button", {
|
||||
@@ -437,7 +454,7 @@ test("only one modal can be open at a time", async ({ page }) => {
|
||||
await expect(page.getByText("Use the Radicle CLI")).not.toBeVisible();
|
||||
await expect(page.getByText("bob")).not.toBeVisible();
|
||||
|
||||
await page.getByTitle("Change branch").click();
|
||||
await page.locator('[title="Change branch"]:visible').first().click();
|
||||
await expect(page.getByText("Code font")).not.toBeVisible();
|
||||
await expect(page.getByText("Use the Radicle CLI")).not.toBeVisible();
|
||||
await expect(page.getByText("bob")).toBeVisible();
|
||||
|
||||
@@ -14,7 +14,7 @@ test("navigation from commit list", async ({ page }) => {
|
||||
await page.goto(sourceBrowsingUrl);
|
||||
await changeBranch("bob", `main ${shortBobHead}`, page);
|
||||
|
||||
await page.getByText("Update readme").first().click();
|
||||
await page.getByRole("link", { name: "Update readme" }).first().click();
|
||||
await expect(page).toHaveURL(commitUrl);
|
||||
});
|
||||
|
||||
@@ -103,13 +103,10 @@ test("navigation to source tree at specific revision", async ({ page }) => {
|
||||
// Go to source tree at this revision.
|
||||
await page.getByTitle("View file at this commit").click();
|
||||
const branchSelectorCommitButton = page.getByTitle("Current HEAD").first();
|
||||
await expect(
|
||||
branchSelectorCommitButton.getByText("Add a deeply nested directory tree"),
|
||||
).toBeVisible();
|
||||
await expect(branchSelectorCommitButton).toContainText("0801ace");
|
||||
await expect(page).toHaveURL(
|
||||
`${sourceBrowsingUrl}/tree/0801aceeab500033f8d608778218657bd626ef73/deep/directory/hierarchy/is/entirely/possible/in/git/repositories/.gitkeep`,
|
||||
);
|
||||
await expect(branchSelectorCommitButton).toContainText("0801ace");
|
||||
await expect(page.locator(".source-tree >> text=.gitkeep")).toBeVisible();
|
||||
await expect(
|
||||
page
|
||||
|
||||
@@ -23,9 +23,9 @@ test("peer and branch switching", async ({ page }) => {
|
||||
{
|
||||
await changeBranch("alice", `main ${shortAliceHead}`, page);
|
||||
|
||||
await expect(page.getByTitle("Change branch")).toHaveText(
|
||||
"alice Delegate / main",
|
||||
);
|
||||
await expect(
|
||||
page.locator('[title="Change branch"]:visible').first(),
|
||||
).toHaveText("alice Delegate / main");
|
||||
|
||||
await expect(page.getByText("Thursday, November 17, 2022")).toBeVisible();
|
||||
await expect(page.locator(".list .teaser")).toHaveCount(
|
||||
@@ -63,7 +63,9 @@ test("peer and branch switching", async ({ page }) => {
|
||||
{
|
||||
await changeBranch("bob", `main ${shortBobHead}`, page);
|
||||
|
||||
await expect(page.getByTitle("Change branch")).toContainText("bob");
|
||||
await expect(
|
||||
page.locator('[title="Change branch"]:visible').first(),
|
||||
).toContainText("bob");
|
||||
|
||||
await expect(page.getByText("Wednesday, December 21, 2022")).toBeVisible();
|
||||
await expect(page.locator(".list").first().locator(".teaser")).toHaveCount(
|
||||
@@ -158,7 +160,9 @@ test("relative timestamps", async ({ page }) => {
|
||||
await expect(page.getByText("Thursday, December 15,")).toBeVisible();
|
||||
|
||||
await changeBranch("bob", `main ${shortBobHead}`, page);
|
||||
await expect(page.getByTitle("Change branch")).toHaveText(/bob/);
|
||||
await expect(
|
||||
page.locator('[title="Change branch"]:visible').first(),
|
||||
).toHaveText(/bob/);
|
||||
const latestCommit = page.locator(".teaser").first();
|
||||
await expect(latestCommit).toContainText(
|
||||
`Bob Belcher committed ${shortBobHead} now`,
|
||||
@@ -194,7 +198,9 @@ test("pushing changes while viewing history", async ({ page, peerManager }) => {
|
||||
await expect(page).toHaveURL(`${alice.uiUrl()}/${rid}/history`);
|
||||
await expect(page.getByRole("link", { name: "Commits 2" })).toBeVisible();
|
||||
|
||||
await expect(page.getByTitle("Change branch")).toHaveText("main Canonical");
|
||||
await expect(
|
||||
page.locator('[title="Change branch"]:visible').first(),
|
||||
).toHaveText("main Canonical");
|
||||
const branchSelectorCommitButton = page.getByTitle("Current HEAD").first();
|
||||
await expect(branchSelectorCommitButton).toHaveText("516fa74 first change");
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@ test("navigate issue listing", async ({ page }) => {
|
||||
await page.getByRole("link", { name: "Issues 1" }).click();
|
||||
await expect(page).toHaveURL(`${cobUrl}/issues`);
|
||||
|
||||
await page.getByRole("button", { name: "filter-dropdown" }).first().click();
|
||||
await page.getByRole("link", { name: "Closed 2" }).click();
|
||||
await page.getByRole("link", { name: "Closed" }).click();
|
||||
await expect(page).toHaveURL(`${cobUrl}/issues?status=closed`);
|
||||
});
|
||||
|
||||
@@ -39,12 +38,8 @@ test("issue counters", async ({ page, peer }) => {
|
||||
],
|
||||
{ cwd: repoFolder },
|
||||
);
|
||||
await page.getByRole("button", { name: "filter-dropdown" }).first().click();
|
||||
await page.locator(".dropdown-item").getByText("Open 1").click();
|
||||
await page.getByRole("link", { name: "Open" }).first().click();
|
||||
await expect(page.getByRole("button", { name: "Issues 2" })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("button", { name: "filter-dropdown" }).first(),
|
||||
).toHaveText("Open 2");
|
||||
await expect(page.locator(".issue-teaser")).toHaveCount(2);
|
||||
|
||||
await page
|
||||
|
||||
@@ -6,8 +6,7 @@ test("navigate patch listing", async ({ page }) => {
|
||||
await page.getByRole("link", { name: "Patches 2" }).click();
|
||||
await expect(page).toHaveURL(`${cobUrl}/patches`);
|
||||
|
||||
await page.getByRole("button", { name: "filter-dropdown" }).first().click();
|
||||
await page.getByRole("link", { name: "Merged 1" }).click();
|
||||
await page.getByRole("link", { name: "Merged" }).click();
|
||||
await expect(page).toHaveURL(`${cobUrl}/patches?status=merged`);
|
||||
await expect(
|
||||
page.locator(".comments").filter({ hasText: "5" }),
|
||||
@@ -40,11 +39,7 @@ test("patches counters", async ({ page, peer }) => {
|
||||
await peer.git(["push", "rad", "HEAD:refs/patches"], {
|
||||
cwd: repoFolder,
|
||||
});
|
||||
await page.getByRole("button", { name: "filter-dropdown" }).first().click();
|
||||
await page.locator(".dropdown-item").getByText("Open 1").click();
|
||||
await page.getByRole("link", { name: "Open" }).first().click();
|
||||
await expect(page.getByRole("button", { name: "Patches 2" })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("button", { name: "filter-dropdown" }).first(),
|
||||
).toHaveText("Open 2");
|
||||
await expect(page.locator(".patch-teaser")).toHaveCount(2);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { RadiclePeer } from "@tests/support/peerManager";
|
||||
import * as Path from "node:path";
|
||||
|
||||
export async function changeBranch(peer: string, branch: string, page: Page) {
|
||||
await page.getByTitle("Change branch").click();
|
||||
await page.locator('[title="Change branch"]:visible').first().click();
|
||||
const peerLocator = page.getByLabel("peer-item").filter({ hasText: peer });
|
||||
await peerLocator.getByTitle("Expand peer").click();
|
||||
await page.getByRole("button", { name: branch }).click();
|
||||
|
||||
Reference in New Issue
Block a user