Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
Add repo issues and patches routes
Merged rudolfs opened 1 year ago

check

๐Ÿ‘‰ Workflow runs ๐Ÿ‘‰ Branch on GitHub

9 files changed +243 -44 6cfd87fc โ†’ f09253f6
modified src/App.svelte
@@ -9,6 +9,8 @@

  import AuthenticationError from "@app/views/AuthenticationError.svelte";
  import Home from "@app/views/Home.svelte";
+
  import Issues from "@app/views/repo/Issues.svelte";
+
  import Patches from "@app/views/repo/Patches.svelte";

  const activeRouteStore = router.activeRouteStore;

@@ -40,6 +42,10 @@
  <!-- Don't show anything -->
{:else if $activeRouteStore.resource === "home"}
  <Home {...$activeRouteStore.params} />
+
{:else if $activeRouteStore.resource === "repo.issues"}
+
  <Issues {...$activeRouteStore.params} />
+
{:else if $activeRouteStore.resource === "repo.patches"}
+
  <Patches {...$activeRouteStore.params} />
{:else if $activeRouteStore.resource === "authenticationError"}
  <AuthenticationError {...$activeRouteStore.params} />
{:else}
modified src/components/RepoCard.svelte
@@ -31,7 +31,12 @@
  }
</style>

-
<Border variant="ghost" styleWidth="100%" stylePadding="8px 12px" hoverable>
+
<Border
+
  variant="ghost"
+
  styleWidth="100%"
+
  stylePadding="8px 12px"
+
  hoverable
+
  on:click>
  <div class="container txt-small">
    <div class="global-flex header">
      <div class="global-flex">
modified src/lib/router.ts
@@ -5,6 +5,7 @@ import { get, writable } from "svelte/store";
import * as mutexExecutor from "@app/lib/mutexExecutor";
import * as utils from "@app/lib/utils";
import { loadRoute } from "@app/lib/router/definitions";
+
import { repoRouteToPath, repoUrlToRoute } from "@app/views/repo/router";

export { type Route };

@@ -45,10 +46,8 @@ async function navigateToUrl(
  if (route) {
    await navigate(action, route);
  } else {
-
    await navigate(action, {
-
      // On 404 we redirect to the Home page, shouldn't happen.
-
      resource: "home",
-
    });
+
    console.error("Could not resolve route for URL: ", url);
+
    await navigate(action, { resource: "home" });
  }
}

@@ -80,30 +79,11 @@ async function navigate(
    return;
  }

-
  setTitle(loadedRoute);
  activeRouteStore.set(loadedRoute);
  activeUnloadedRouteStore.set(newRoute);
  isLoading.set(false);
}

-
function setTitle(loadedRoute: LoadedRoute) {
-
  const title: string[] = [];
-

-
  if (loadedRoute.resource === "booting") {
-
    title.push("Radicle");
-
  } else if (loadedRoute.resource === "home") {
-
    title.push("Home");
-
    title.push("Radicle");
-
  } else if (loadedRoute.resource === "authenticationError") {
-
    title.push("Authentication Error");
-
    title.push("Radicle");
-
  } else {
-
    utils.unreachable(loadedRoute);
-
  }
-

-
  document.title = title.join(" ยท ");
-
}
-

export async function push(newRoute: Route): Promise<void> {
  await navigate("push", newRoute);
}
@@ -114,12 +94,15 @@ export async function replace(newRoute: Route): Promise<void> {

function urlToRoute(url: URL): Route | null {
  const segments = url.pathname.substring(1).split("/");
-

  const resource = segments.shift();
+

  switch (resource) {
    case "": {
      return { resource: "home" };
    }
+
    case "repos": {
+
      return repoUrlToRoute(segments, url.searchParams);
+
    }
    case "authenticationError": {
      return { resource: "authenticationError", params: { error: "" } };
    }
@@ -134,6 +117,11 @@ export function routeToPath(route: Route): string {
    return "/";
  } else if (route.resource === "authenticationError") {
    return "/authenticationError";
+
  } else if (
+
    route.resource === "repo.issues" ||
+
    route.resource === "repo.patches"
+
  ) {
+
    return repoRouteToPath(route);
  } else if (route.resource === "booting") {
    return "";
  } else {
modified src/lib/router/definitions.ts
@@ -1,8 +1,11 @@
-
import type { RepoInfo } from "@bindings/RepoInfo";
import type { Config } from "@bindings/Config";
+
import type { RepoInfo } from "@bindings/RepoInfo";
+
import type { LoadedRepoRoute, RepoRoute } from "@app/views/repo/router";

import { invoke } from "@tauri-apps/api/core";

+
import { loadIssues, loadPatches } from "@app/views/repo/router";
+

interface BootingRoute {
  resource: "booting";
}
@@ -24,12 +27,17 @@ interface LoadedHomeRoute {
  params: { repos: RepoInfo[]; config: Config };
}

-
export type Route = BootingRoute | HomeRoute | AuthenticationErrorRoute;
+
export type Route =
+
  | AuthenticationErrorRoute
+
  | BootingRoute
+
  | HomeRoute
+
  | RepoRoute;

export type LoadedRoute =
+
  | AuthenticationErrorRoute
  | BootingRoute
  | LoadedHomeRoute
-
  | AuthenticationErrorRoute;
+
  | LoadedRepoRoute;

export async function loadRoute(
  route: Route,
@@ -39,6 +47,10 @@ export async function loadRoute(
    const repos: RepoInfo[] = await invoke("list_repos");
    const config: Config = await invoke("config");
    return { resource: "home", params: { repos, config } };
+
  } else if (route.resource === "repo.issues") {
+
    return loadIssues(route);
+
  } else if (route.resource === "repo.patches") {
+
    return loadPatches(route);
  }
  return route;
}
modified src/views/Home.svelte
@@ -2,26 +2,13 @@
  import type { Config } from "@bindings/Config";
  import type { RepoInfo } from "@bindings/RepoInfo";

+
  import * as router from "@app/lib/router";
+

  import Header from "@app/components/Header.svelte";
  import RepoCard from "@app/components/RepoCard.svelte";
-
  import { onMount } from "svelte";
-
  import { invoke } from "@tauri-apps/api/core";

  export let repos: RepoInfo[];
  export let config: Config;
-

-
  onMount(async () => {
-
    const patches = await invoke("list_patches", {
-
      rid: "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
-
      status: "open",
-
    });
-
    const issues = await invoke("list_issues", {
-
      rid: "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
-
      status: "open",
-
    });
-

-
    console.log(patches, issues);
-
  });
</script>

<style>
@@ -40,7 +27,16 @@
  <div class="repo-grid">
    {#each repos as repo}
      {#if repo.payloads["xyz.radicle.project"]}
-
        <RepoCard {repo} selfDid={`did:key:${config.publicKey}`} />
+
        <RepoCard
+
          {repo}
+
          selfDid={`did:key:${config.publicKey}`}
+
          on:click={() => {
+
            void router.push({
+
              resource: "repo.issues",
+
              rid: repo.rid,
+
              status: "open",
+
            });
+
          }} />
      {/if}
    {/each}
  </div>
added src/views/repo/Issues.svelte
@@ -0,0 +1,20 @@
+
<script lang="ts">
+
  import type { Issue } from "@bindings/Issue";
+
  import type { RepoInfo } from "@bindings/RepoInfo";
+

+
  import Layout from "./Layout.svelte";
+

+
  export let repo: RepoInfo;
+
  export let issues: Issue[];
+
</script>
+

+
<Layout {repo}>
+
  <pre>
+
    <!-- prettier-ignore -->
+
    {#each issues as issue}
+
      - {issue.title}
+
    {:else}
+
      No issues.
+
    {/each}
+
  </pre>
+
</Layout>
added src/views/repo/Layout.svelte
@@ -0,0 +1,38 @@
+
<script lang="ts">
+
  import type { RepoInfo } from "@bindings/RepoInfo";
+

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

+
  export let repo: RepoInfo;
+

+
  $: project = repo.payloads["xyz.radicle.project"]!;
+
</script>
+

+
<Header currentPage="Repositories" />
+
<div>{project.data.name}</div>
+
<div>{repo.rid}</div>
+

+
Issues
+
<Link route={{ resource: "repo.issues", rid: repo.rid, status: "open" }}>
+
  Open
+
</Link>
+
<Link route={{ resource: "repo.issues", rid: repo.rid, status: "closed" }}>
+
  Closed
+
</Link>
+

+
<br />
+
Patches
+
<Link route={{ resource: "repo.patches", rid: repo.rid, status: "draft" }}>
+
  Draft
+
</Link>
+
<Link route={{ resource: "repo.patches", rid: repo.rid, status: "open" }}>
+
  Open
+
</Link>
+
<Link route={{ resource: "repo.patches", rid: repo.rid, status: "archived" }}>
+
  Archived
+
</Link>
+
<Link route={{ resource: "repo.patches", rid: repo.rid, status: "merged" }}>
+
  Merged
+
</Link>
+
<slot />
added src/views/repo/Patches.svelte
@@ -0,0 +1,20 @@
+
<script lang="ts">
+
  import type { Patch } from "@bindings/Patch";
+
  import type { RepoInfo } from "@bindings/RepoInfo";
+

+
  import Layout from "./Layout.svelte";
+

+
  export let repo: RepoInfo;
+
  export let patches: Patch[];
+
</script>
+

+
<Layout {repo}>
+
  <pre>
+
    <!-- prettier-ignore -->
+
    {#each patches as patch}
+
      - {patch.title}
+
    {:else}
+
      No patches.
+
    {/each}
+
  </pre>
+
</Layout>
added src/views/repo/router.ts
@@ -0,0 +1,114 @@
+
import type { RepoInfo } from "@bindings/RepoInfo";
+
import type { Patch } from "@bindings/Patch";
+
import type { Issue } from "@bindings/Issue";
+

+
import { invoke } from "@tauri-apps/api/core";
+
import { unreachable } from "@app/lib/utils";
+

+
export interface RepoIssuesRoute {
+
  resource: "repo.issues";
+
  rid: string;
+
  status?: "open" | "closed";
+
}
+

+
export interface LoadedRepoIssuesRoute {
+
  resource: "repo.issues";
+
  params: { repo: RepoInfo; issues: Issue[] };
+
}
+

+
export interface RepoPatchesRoute {
+
  resource: "repo.patches";
+
  rid: string;
+
  status?: "draft" | "open" | "archived" | "merged";
+
}
+

+
export interface LoadedRepoPatchesRoute {
+
  resource: "repo.patches";
+
  params: { repo: RepoInfo; patches: Patch[] };
+
}
+

+
export type RepoRoute = RepoIssuesRoute | RepoPatchesRoute;
+
export type LoadedRepoRoute = LoadedRepoIssuesRoute | LoadedRepoPatchesRoute;
+

+
export async function loadPatches(route: RepoRoute): Promise<LoadedRepoRoute> {
+
  const repo: RepoInfo = await invoke("repo_by_id", {
+
    rid: route.rid,
+
  });
+
  const patches: Patch[] = await invoke("list_patches", {
+
    rid: route.rid,
+
    status: route.status,
+
  });
+

+
  return { resource: "repo.patches", params: { repo, patches } };
+
}
+

+
export async function loadIssues(route: RepoRoute): Promise<LoadedRepoRoute> {
+
  const repo: RepoInfo = await invoke("repo_by_id", {
+
    rid: route.rid,
+
  });
+
  const issues: Issue[] = await invoke("list_issues", {
+
    rid: route.rid,
+
    status: route.status,
+
  });
+

+
  return { resource: "repo.issues", params: { repo, issues } };
+
}
+

+
export function repoRouteToPath(route: RepoRoute): string {
+
  const pathSegments = ["/repos", route.rid];
+

+
  if (route.resource === "repo.issues") {
+
    let url = [...pathSegments, "issues"].join("/");
+
    const searchParams = new URLSearchParams();
+
    if (route.status) {
+
      searchParams.set("status", route.status);
+
      url += `?${searchParams}`;
+
    }
+
    return url;
+
  } else if (route.resource === "repo.patches") {
+
    let url = [...pathSegments, "patches"].join("/");
+
    const searchParams = new URLSearchParams();
+
    if (route.status) {
+
      searchParams.set("status", route.status);
+
      url += `?${searchParams}`;
+
    }
+
    return url;
+
  } else {
+
    return unreachable(route);
+
  }
+
}
+

+
export function repoUrlToRoute(
+
  segments: string[],
+
  searchParams: URLSearchParams,
+
): RepoRoute | null {
+
  const rid = segments.shift();
+
  const resource = segments.shift();
+

+
  if (rid) {
+
    if (resource === "issues") {
+
      const status = searchParams.get("status");
+
      if (status === "open" || status === "closed") {
+
        return { resource: "repo.issues", rid, status };
+
      } else {
+
        return { resource: "repo.issues", rid };
+
      }
+
    } else if (resource === "patches") {
+
      const status = searchParams.get("status");
+
      if (
+
        status === "draft" ||
+
        status === "open" ||
+
        status === "archived" ||
+
        status === "merged"
+
      ) {
+
        return { resource: "repo.patches", rid, status };
+
      } else {
+
        return { resource: "repo.patches", rid };
+
      }
+
    } else {
+
      return null;
+
    }
+
  } else {
+
    return null;
+
  }
+
}