Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add breadcrumbs to header
Rūdolfs Ošiņš committed 2 years ago
commit ad84bc9f30d8693e78918ffa153c556dc5267185
parent eeba82017d151aea4a12debdf7192b5a99586950
13 files changed +175 -25
modified src/App/Footer.svelte
@@ -16,7 +16,7 @@
    color: var(--color-foreground-dim);
    height: 2.25rem;
    background-color: var(--color-background-dip);
-
    padding: 1rem 1.5rem;
+
    padding: 1rem;
  }

  .left {
modified src/App/Header.svelte
@@ -1,9 +1,11 @@
<script lang="ts">
+
  import { httpdStore } from "@app/lib/httpd";
+

+
  import Authenticate from "./Header/Authenticate.svelte";
+
  import Breadcrumbs from "./Header/Breadcrumbs.svelte";
  import Connect from "@app/App/Header/Connect.svelte";
  import Link from "@app/components/Link.svelte";
  import NodeInfo from "@app/App/Header/NodeInfo.svelte";
-
  import Authenticate from "./Header/Authenticate.svelte";
-
  import { httpdStore } from "@app/lib/httpd";
</script>

<style>
@@ -12,8 +14,7 @@
    justify-content: space-between;
    align-items: center;
    margin: 0;
-
    padding: 1.5rem;
-
    height: 5.5rem;
+
    padding: 1rem;
  }
  .left,
  .right {
@@ -45,6 +46,9 @@
        alt="Radicle logo"
        src="/radicle.svg" />
    </Link>
+
    <div class="layout-desktop">
+
      <Breadcrumbs />
+
    </div>
  </div>

  <div class="right layout-desktop-flex">
added src/App/Header/Breadcrumbs.svelte
@@ -0,0 +1,39 @@
+
<script lang="ts">
+
  import * as router from "@app/lib/router";
+
  import * as utils from "@app/lib/utils";
+

+
  import NodeSegment from "./Breadcrumbs/NodeSegment.svelte";
+
  import ProjectSegment from "./Breadcrumbs/ProjectSegment.svelte";
+
  import Separator from "./Breadcrumbs/Separator.svelte";
+

+
  const activeRouteStore = router.activeRouteStore;
+
</script>
+

+
<style>
+
  .breadcrumbs {
+
    display: flex;
+
    align-items: center;
+
    gap: 0.25rem;
+
    font-weight: var(--font-weight-semibold);
+
    font-size: var(--font-size-small);
+
    white-space: nowrap;
+
  }
+
</style>
+

+
{#if $activeRouteStore.resource === "booting" || $activeRouteStore.resource === "home" || $activeRouteStore.resource === "session" || $activeRouteStore.resource === "loadError" || $activeRouteStore.resource === "notFound"}
+
  <!-- Don't render breadcrumbs for these routes. -->
+
{:else if $activeRouteStore.resource === "nodes"}
+
  <div class="breadcrumbs">
+
    <NodeSegment baseUrl={$activeRouteStore.params.baseUrl} />
+
  </div>
+
{:else if $activeRouteStore.resource === "project.source" || $activeRouteStore.resource === "project.history" || $activeRouteStore.resource === "project.commit" || $activeRouteStore.resource === "project.issues" || $activeRouteStore.resource === "project.newIssue" || $activeRouteStore.resource === "project.issue" || $activeRouteStore.resource === "project.patches" || $activeRouteStore.resource === "project.patch"}
+
  <div class="breadcrumbs">
+
    <NodeSegment baseUrl={$activeRouteStore.params.baseUrl} />
+

+
    <Separator />
+

+
    <ProjectSegment activeRoute={$activeRouteStore} />
+
  </div>
+
{:else}
+
  {utils.unreachable($activeRouteStore)}
+
{/if}
added src/App/Header/Breadcrumbs/NodeSegment.svelte
@@ -0,0 +1,36 @@
+
<script lang="ts">
+
  import type { BaseUrl } from "@httpd-client";
+

+
  import { isLocal } from "@app/lib/utils";
+

+
  import IconSmall from "@app/components/IconSmall.svelte";
+
  import Link from "@app/components/Link.svelte";
+

+
  export let baseUrl: BaseUrl;
+
</script>
+

+
<style>
+
  .segment :global(a:hover) {
+
    color: var(--color-fill-primary);
+
  }
+
</style>
+

+
<span class="segment">
+
  <Link
+
    style="display: flex; align-items: center; gap: 0.25rem;"
+
    route={{
+
      resource: "nodes",
+
      params: {
+
        baseUrl,
+
        projectPageIndex: 0,
+
      },
+
    }}>
+
    {#if isLocal(baseUrl.hostname)}
+
      <IconSmall name="device" />
+
      Local Node
+
    {:else}
+
      <IconSmall name="globe" />
+
      {baseUrl.hostname}
+
    {/if}
+
  </Link>
+
</span>
added src/App/Header/Breadcrumbs/ProjectSegment.svelte
@@ -0,0 +1,67 @@
+
<script lang="ts">
+
  import type { ProjectLoadedRoute } from "@app/views/projects/router";
+

+
  import { capitalize } from "lodash";
+
  import * as utils from "@app/lib/utils";
+

+
  import CopyableId from "@app/components/CopyableId.svelte";
+
  import Link from "@app/components/Link.svelte";
+
  import Separator from "./Separator.svelte";
+

+
  export let activeRoute: ProjectLoadedRoute;
+
</script>
+

+
<style>
+
  .segment :global(a:hover) {
+
    color: var(--color-fill-primary);
+
  }
+
</style>
+

+
<span class="segment">
+
  <Link
+
    route={{
+
      resource: "project.source",
+
      project: activeRoute.params.project.id,
+
      node: activeRoute.params.baseUrl,
+
    }}>
+
    {activeRoute.params.project.name}
+
  </Link>
+
</span>
+

+
<Separator />
+

+
<span class="segment">
+
  <Link
+
    route={{
+
      resource: "project.source",
+
      project: activeRoute.params.project.id,
+
      node: activeRoute.params.baseUrl,
+
    }}>
+
    {#if activeRoute.resource === "project.history" || activeRoute.resource === "project.commit"}
+
      Commits
+
    {:else if activeRoute.resource === "project.newIssue" || activeRoute.resource === "project.issue"}
+
      Issues
+
    {:else if activeRoute.resource === "project.patch"}
+
      Patches
+
    {:else}
+
      {capitalize(activeRoute.resource.split(".")[1])}
+
    {/if}
+
  </Link>
+
</span>
+

+
{#if activeRoute.resource === "project.commit"}
+
  <Separator />
+
  <CopyableId id={activeRoute.params.commit.commit.id}>
+
    {utils.formatCommit(activeRoute.params.commit.commit.id)}
+
  </CopyableId>
+
{:else if activeRoute.resource === "project.issue"}
+
  <Separator />
+
  <CopyableId id={activeRoute.params.issue.id}>
+
    {utils.formatObjectId(activeRoute.params.issue.id)}
+
  </CopyableId>
+
{:else if activeRoute.resource === "project.patch"}
+
  <Separator />
+
  <CopyableId id={activeRoute.params.patch.id}>
+
    {utils.formatObjectId(activeRoute.params.patch.id)}
+
  </CopyableId>
+
{/if}
added src/App/Header/Breadcrumbs/Separator.svelte
@@ -0,0 +1,7 @@
+
<script lang="ts">
+
  import IconSmall from "@app/components/IconSmall.svelte";
+
</script>
+

+
<span style:color="var(--color-foreground-dim)">
+
  <IconSmall name="chevron-right" />
+
</span>
modified src/components/IconSmall.svelte
@@ -29,6 +29,7 @@
    | "eye-open"
    | "face"
    | "file"
+
    | "globe"
    | "issue"
    | "key"
    | "logo"
@@ -282,6 +283,11 @@
      fill-rule="evenodd"
      clip-rule="evenodd"
      d="M4.5 3.33329C4.5 3.24125 4.57462 3.16663 4.66667 3.16663H8.5898C8.79929 3.16663 9.0011 3.24553 9.15503 3.38762L11.2319 5.30473C11.4028 5.46249 11.5 5.68449 11.5 5.91707V12.6666C11.5 12.7587 11.4254 12.8333 11.3333 12.8333H4.66667C4.57462 12.8333 4.5 12.7587 4.5 12.6666V3.33329ZM4.66667 2.16663C4.02233 2.16663 3.5 2.68896 3.5 3.33329V12.6666C3.5 13.311 4.02233 13.8333 4.66667 13.8333H11.3333C11.9777 13.8333 12.5 13.311 12.5 12.6666V5.91707C12.5 5.40539 12.2862 4.91699 11.9102 4.56993L9.83331 2.65282C9.49466 2.34021 9.05068 2.16663 8.5898 2.16663H4.66667ZM6 4.16663C5.72386 4.16663 5.5 4.39048 5.5 4.66663C5.5 4.94277 5.72386 5.16663 6 5.16663H7.33333C7.60948 5.16663 7.83333 4.94277 7.83333 4.66663C7.83333 4.39048 7.60948 4.16663 7.33333 4.16663H6ZM6 6.83329C5.72386 6.83329 5.5 7.05715 5.5 7.33329C5.5 7.60944 5.72386 7.83329 6 7.83329H10C10.2761 7.83329 10.5 7.60944 10.5 7.33329C10.5 7.05715 10.2761 6.83329 10 6.83329H6ZM6 8.83329C5.72386 8.83329 5.5 9.05715 5.5 9.33329C5.5 9.60943 5.72386 9.83329 6 9.83329H8.66667C8.94281 9.83329 9.16667 9.60943 9.16667 9.33329C9.16667 9.05715 8.94281 8.83329 8.66667 8.83329H6ZM6 10.8333C5.72386 10.8333 5.5 11.0571 5.5 11.3333C5.5 11.6094 5.72386 11.8333 6 11.8333H9.33333C9.60948 11.8333 9.83333 11.6094 9.83333 11.3333C9.83333 11.0571 9.60948 10.8333 9.33333 10.8333H6Z" />
+
  {:else if name === "globe"}
+
    <path
+
      fill-rule="evenodd"
+
      clip-rule="evenodd"
+
      d="M3.4424 6.38689C4.03734 6.07787 4.81337 5.8403 5.68923 5.68926C5.73546 5.42115 5.78981 5.16239 5.85182 4.91533C5.99241 4.35527 6.17243 3.85525 6.38686 3.44243C5.01515 3.92794 3.92791 5.01518 3.4424 6.38689ZM7.99999 2.16669C4.77833 2.16669 2.16666 4.77836 2.16666 8.00002C2.16666 11.2217 4.77833 13.8334 7.99999 13.8334C11.2217 13.8334 13.8333 11.2217 13.8333 8.00002C13.8333 4.77836 11.2217 2.16669 7.99999 2.16669ZM7.99999 3.16669C7.86865 3.16669 7.59893 3.29241 7.29002 3.87344C7.10927 4.21341 6.94711 4.65475 6.8174 5.17614C6.78706 5.2981 6.75849 5.42444 6.73188 5.55489C7.13996 5.51895 7.56447 5.50002 7.99999 5.50002C8.43551 5.50002 8.86002 5.51895 9.2681 5.55489C9.24022 5.41825 9.2102 5.28611 9.17825 5.15879C9.04926 4.64495 8.8887 4.20963 8.70996 3.87344C8.40105 3.29241 8.13133 3.16669 7.99999 3.16669ZM10.1537 4.93762C10.0123 4.36853 9.83039 3.86072 9.61312 3.44243C10.9848 3.92794 12.0721 5.01518 12.5576 6.38689C11.9626 6.07787 11.1866 5.8403 10.3108 5.68926C10.2659 5.4292 10.2134 5.17795 10.1537 4.93762ZM9.42501 6.575C8.97388 6.52628 8.49569 6.50002 7.99999 6.50002C7.50429 6.50002 7.0261 6.52628 6.57497 6.575C6.52625 7.02613 6.49999 7.50432 6.49999 8.00002C6.49999 8.49572 6.52625 8.97391 6.57497 9.42504C7.02609 9.47376 7.50429 9.50002 7.99999 9.50002C8.49569 9.50002 8.97388 9.47376 9.42501 9.42504C9.47373 8.97391 9.49999 8.49572 9.49999 8.00002C9.49999 7.50432 9.47372 7.02612 9.42501 6.575ZM9.2681 10.4452C8.86002 10.4811 8.43551 10.5 7.99999 10.5C7.56447 10.5 7.13996 10.4811 6.73188 10.4452C6.87227 11.1333 7.06699 11.7071 7.29002 12.1266C7.59893 12.7076 7.86865 12.8334 7.99999 12.8334C8.13133 12.8334 8.40105 12.7076 8.70996 12.1266C8.93299 11.7071 9.12771 11.1333 9.2681 10.4452ZM9.61312 12.5576C9.92214 11.9627 10.1597 11.1866 10.3108 10.3108C11.1866 10.1597 11.9626 9.92217 12.5576 9.61315C12.0721 10.9849 10.9848 12.0721 9.61312 12.5576ZM12.8333 8.00002C12.8333 7.86868 12.7076 7.59896 12.1266 7.29005C11.7071 7.06702 11.1332 6.8723 10.4451 6.73191C10.4811 7.13999 10.5 7.5645 10.5 8.00002C10.5 8.43554 10.4811 8.86005 10.4451 9.26813C11.1333 9.12774 11.7071 8.93302 12.1266 8.70999C12.7076 8.40108 12.8333 8.13136 12.8333 8.00002ZM6.38686 12.5576C5.01515 12.0721 3.92791 10.9849 3.4424 9.61315C4.03734 9.92218 4.81337 10.1597 5.68923 10.3108C5.84027 11.1866 6.07784 11.9627 6.38686 12.5576ZM5.55486 9.26813C4.86673 9.12774 4.2929 8.93302 3.87341 8.70999C3.29238 8.40108 3.16666 8.13136 3.16666 8.00002C3.16666 7.86868 3.29238 7.59896 3.87341 7.29005C4.2929 7.06702 4.86673 6.8723 5.55486 6.73191C5.51892 7.13999 5.49999 7.5645 5.49999 8.00002C5.49999 8.43554 5.51892 8.86005 5.55486 9.26813Z" />
  {:else if name === "issue"}
    <path
      fill-rule="evenodd"
modified src/components/Link.svelte
@@ -7,6 +7,7 @@
  export let route: Route;
  export let disabled: boolean = false;
  export let title: string | undefined = undefined;
+
  export let style: string | undefined = undefined;

  const dispatch = createEventDispatcher<{
    afterNavigate: null;
@@ -28,6 +29,6 @@
  }
</script>

-
<a on:click={navigateToRoute} href={routeToPath(route)} {title}>
+
<a on:click={navigateToRoute} href={routeToPath(route)} {title} {style}>
  <slot />
</a>
modified src/views/projects/Layout.svelte
@@ -5,9 +5,8 @@
  import dompurify from "dompurify";

  import markdown from "@app/lib/markdown";
-
  import { twemoji, isLocal } from "@app/lib/utils";
+
  import { twemoji } from "@app/lib/utils";

-
  import Button from "@app/components/Button.svelte";
  import CloneButton from "@app/views/projects/Header/CloneButton.svelte";
  import Link from "@app/components/Link.svelte";

@@ -93,18 +92,6 @@
    <div
      class="layout-desktop-flex"
      style="margin-left: auto; display: flex; gap: 0.5rem;">
-
      <Link
-
        route={{
-
          resource: "nodes",
-
          params: {
-
            baseUrl,
-
            projectPageIndex: 0,
-
          },
-
        }}>
-
        <Button size="large" variant="outline">
-
          {isLocal(baseUrl.hostname) ? "radicle.local" : baseUrl.hostname}
-
        </Button>
-
      </Link>
      <TrackButton
        {tracking}
        trackings={project.trackings}
modified tests/e2e/hashRouter.spec.ts
@@ -34,7 +34,7 @@ test("navigation between node and project pages", async ({ page }) => {
  await expectBackAndForwardNavigationWorks("/#/nodes/radicle.local", page);
  await expectUrlPersistsReload(page);

-
  await page.getByRole("link", { name: "radicle.local" }).click();
+
  await page.getByRole("link", { name: "Local Node" }).click();
  await expect(page).toHaveURL("/#/nodes/127.0.0.1");
});

modified tests/e2e/historyRouter.spec.ts
@@ -34,7 +34,7 @@ test("navigation between node and project pages", async ({ page }) => {
  await expectBackAndForwardNavigationWorks("/nodes/radicle.local", page);
  await expectUrlPersistsReload(page);

-
  await page.getByRole("link", { name: "radicle.local" }).click();
+
  await page.getByRole("link", { name: "Local Node" }).click();
  await expect(page).toHaveURL("/nodes/127.0.0.1");
});

modified tests/e2e/project.spec.ts
@@ -36,7 +36,7 @@ test("navigate to project", async ({ page }) => {

  // Header.
  {
-
    const name = page.getByText("source-browsing");
+
    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",
@@ -331,7 +331,7 @@ test("peer and branch switching", async ({ page }) => {

  // Reset the source browser by clicking the project title.
  {
-
    await page.getByText("source-browsing").click();
+
    await page.getByRole("link", { name: "source-browsing" }).nth(1).click();

    await expect(page.getByTitle("Change peer")).not.toContainText("alice");
    await expect(page.getByTitle("Change peer")).not.toContainText("bob");
modified tests/e2e/project/commits.spec.ts
@@ -150,7 +150,10 @@ test("pushing changes while viewing history", async ({ page, peerManager }) => {
  await expect(page.getByLabel("canonical-branch")).toHaveText("main");
  await expect(page.getByTitle("Current HEAD")).toHaveText("516fa74");

-
  await page.getByText("alice-project").click();
+
  await page
+
    .getByRole("banner")
+
    .getByRole("link", { name: "alice-project" })
+
    .click();
  await expect(page).toHaveURL(`${alice.uiUrl()}/${rid}`);
  await page.getByRole("link", { name: "2 commits" }).click();