From b1411ae4a5f16aa176cae7ea3f79e2fcc7277ddc Mon Sep 17 00:00:00 2001 From: Georgios Jason Efstathiou Date: Mon, 29 Jan 2024 10:44:49 +0100 Subject: [PATCH] Implement new landing page --- package-lock.json | 18 ++ package.json | 1 + src/App/Header.svelte | 36 +-- src/App/Header/Authenticate.svelte | 15 +- src/components/Badge.svelte | 11 +- src/components/ConnectInstructions.svelte | 51 ++++ .../DropdownList/DropdownListItem.svelte | 1 - src/components/Icon.svelte | 7 +- src/components/IconSmall.svelte | 10 + src/components/Popover.svelte | 2 +- src/components/ProjectCard.svelte | 281 ++++++++++-------- src/components/TransitionedHeight.svelte | 114 +++++++ src/config.json | 70 +---- src/lib/commit.ts | 7 + src/lib/config.ts | 14 +- src/lib/projects.ts | 36 +++ src/lib/seeds.ts | 122 ++++++++ src/views/home/Index.svelte | 252 +++++++++++----- src/views/home/components/FilterButton.svelte | 104 +++++++ .../home/components/HomepageSection.svelte | 79 +++++ .../home/components/NewProjectButton.svelte | 38 +++ .../components/PreferredSeedDropdown.svelte | 97 ++++++ src/views/home/router.ts | 94 +++--- src/views/nodes/View.svelte | 58 ++-- src/views/nodes/router.ts | 34 +-- src/views/projects/Source/PeerSelector.svelte | 12 +- .../projects/Source/ProjectNameHeader.svelte | 7 +- tests/build/smoke.spec.ts | 6 +- tests/e2e/hashRouter.spec.ts | 2 +- tests/e2e/historyRouter.spec.ts | 2 +- tests/e2e/landingPage.spec.ts | 14 +- tests/e2e/node.spec.ts | 18 +- tests/e2e/project.spec.ts | 2 +- tests/e2e/project/commits.spec.ts | 2 +- tests/support/fixtures.ts | 40 +-- 35 files changed, 1169 insertions(+), 488 deletions(-) create mode 100644 src/components/ConnectInstructions.svelte create mode 100644 src/components/TransitionedHeight.svelte create mode 100644 src/lib/seeds.ts create mode 100644 src/views/home/components/FilterButton.svelte create mode 100644 src/views/home/components/HomepageSection.svelte create mode 100644 src/views/home/components/NewProjectButton.svelte create mode 100644 src/views/home/components/PreferredSeedDropdown.svelte diff --git a/package-lock.json b/package-lock.json index 2e5f862da..40468d673 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "version": "1.0.0", "hasInstallScript": true, "dependencies": { + "@efstajas/svelte-stored-writable": "^0.2.0", "@radicle/gray-matter": "4.1.0", "@wooorm/starry-night": "^3.2.0", "async-mutex": "^0.4.1", @@ -84,6 +85,23 @@ "node": ">=6.0.0" } }, + "node_modules/@efstajas/svelte-stored-writable": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@efstajas/svelte-stored-writable/-/svelte-stored-writable-0.2.0.tgz", + "integrity": "sha512-z04mzHHe8HNgteQRJqbfq/omTEFCl+BHMLt1P0Mu1PcnNvnT5fhGTRG0oBd1D8HkRxu4Do+pQO5YrrdIwKluRg==", + "dependencies": { + "svelte": "^3.57.0", + "zod": "^3.21.4" + } + }, + "node_modules/@efstajas/svelte-stored-writable/node_modules/svelte": { + "version": "3.59.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz", + "integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", diff --git a/package.json b/package.json index 375d7d59c..3b02c8051 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "wait-on": "^7.2.0" }, "dependencies": { + "@efstajas/svelte-stored-writable": "^0.2.0", "@radicle/gray-matter": "4.1.0", "@wooorm/starry-night": "^3.2.0", "async-mutex": "^0.4.1", diff --git a/src/App/Header.svelte b/src/App/Header.svelte index cf9ea1648..8b58f5567 100644 --- a/src/App/Header.svelte +++ b/src/App/Header.svelte @@ -6,11 +6,11 @@ import Authenticate from "./Header/Authenticate.svelte"; import Breadcrumbs from "./Header/Breadcrumbs.svelte"; import Button from "@app/components/Button.svelte"; - import Command from "@app/components/Command.svelte"; import IconSmall from "@app/components/IconSmall.svelte"; import Link from "@app/components/Link.svelte"; import NodeInfo from "@app/App/Header/NodeInfo.svelte"; import Popover from "@app/components/Popover.svelte"; + import ConnectInstructions from "@app/components/ConnectInstructions.svelte"; const buttonTitle: Record = { stopped: "radicle-httpd is stopped", @@ -39,26 +39,9 @@ height: var(--button-regular-height); margin: 0 0.5rem; } - .label { - display: block; - font-size: var(--font-size-small); - font-weight: var(--font-weight-regular); - margin-bottom: 0.75rem; - } .connect-popover { max-width: 20rem; } - .divider { - height: 1px; - width: 100%; - background-color: var(--color-fill-separator); - margin: 1rem 0; - } - .heading { - font-size: var(--font-size-small); - font-weight: var(--font-weight-bold); - margin-bottom: 0.5rem; - }
@@ -89,22 +72,7 @@ Connect
-
Connect & Authenticate
-
- Start the local node to browse projecs on your local machine, create - issues, and participate in discussions. -
- - -
-
New to Radicle?
-
- Run the following command and follow the instructions to install - Radicle and get started. -
- +
{:else} diff --git a/src/App/Header/Authenticate.svelte b/src/App/Header/Authenticate.svelte index 0a33d2470..d71c6af0b 100644 --- a/src/App/Header/Authenticate.svelte +++ b/src/App/Header/Authenticate.svelte @@ -5,11 +5,11 @@ import Avatar from "@app/components/Avatar.svelte"; import Button from "@app/components/Button.svelte"; - import Command from "@app/components/Command.svelte"; import IconButton from "@app/components/IconButton.svelte"; import IconSmall from "@app/components/IconSmall.svelte"; import NodeId from "@app/components/NodeId.svelte"; import Popover from "@app/components/Popover.svelte"; + import ConnectInstructions from "@app/components/ConnectInstructions.svelte"; {#if $httpdStore.state === "authenticated"} @@ -87,12 +81,7 @@ Authenticate
-
- Authenticate with your local node to make changes. -
- +
{/if} diff --git a/src/components/Badge.svelte b/src/components/Badge.svelte index 8b85f0dd7..83ec74a16 100644 --- a/src/components/Badge.svelte +++ b/src/components/Badge.svelte @@ -4,7 +4,7 @@ | "foreground" | "background" | "outline" - | "yellowOutline" + | "yellow" | "neutral" | "negative" | "positive" @@ -50,10 +50,9 @@ color: var(--color-foreground-match-background); background-color: var(--color-fill-secondary); } - .yellow-outline { - border: 1px solid var(--color-foreground-yellow); - color: var(--color-foreground-yellow); - background-color: transparent; + .yellow { + color: var(--color-foreground-black); + background-color: var(--color-fill-yellow); } .outline { border: 1px solid var(--color-border-hint); @@ -112,7 +111,7 @@ class:small={size === "small"} class:medium={size === "medium"} class:caution={variant === "caution"} - class:yellow-outline={variant === "yellowOutline"} + class:yellow={variant === "yellow"} class:outline={variant === "outline"} class:background={variant === "background"} class:foreground={variant === "foreground"} diff --git a/src/components/ConnectInstructions.svelte b/src/components/ConnectInstructions.svelte new file mode 100644 index 000000000..638fba31c --- /dev/null +++ b/src/components/ConnectInstructions.svelte @@ -0,0 +1,51 @@ + + + + +
+ {#if $httpdStore.state === "running"} +
Authenticate with your local node to make changes.
+ + {:else} +
Connect & Authenticate
+
+ Connect to your local node to browse projects on your local machine, + create issues, and participate in discussions. +
+ + +
+
New to Radicle?
+
+ Run the following command and follow the instructions to install Radicle + and get started. +
+ + {/if} +
diff --git a/src/components/DropdownList/DropdownListItem.svelte b/src/components/DropdownList/DropdownListItem.svelte index 6cc37309e..a670d74b5 100644 --- a/src/components/DropdownList/DropdownListItem.svelte +++ b/src/components/DropdownList/DropdownListItem.svelte @@ -17,7 +17,6 @@ font-size: var(--font-size-small); font-weight: var(--font-weight-regular); color: var(--color-foreground-contrast); - height: 2rem; } .item.disabled { color: var(--color-foreground-disabled); diff --git a/src/components/Icon.svelte b/src/components/Icon.svelte index 5d77f0b0f..9ac40b5ae 100644 --- a/src/components/Icon.svelte +++ b/src/components/Icon.svelte @@ -42,6 +42,7 @@ | "no-issues" | "no-patches" | "patch" + | "plus" | "review" | "sun"; export let size: "24" | "48" = "24"; @@ -384,7 +385,6 @@ d="M12 18C12.5523 18 13 17.5523 13 17C13 16.4477 12.5523 16 12 16C11.4477 16 11 16.4477 11 17C11 17.5523 11.4477 18 12 18Z" /> {:else if name === "face"} - .only + {:else if name === "plus"} + {:else if name === "review"} + {:else if name === "badge"} + + {:else if name === "branch"} - import type { WeeklyActivity } from "@app/lib/commit"; + import type { BaseUrl } from "@httpd-client"; - import capitalize from "lodash/capitalize"; - import { formatCommit, twemoji } from "@app/lib/utils"; + import type { WeeklyActivity } from "@app/lib/commit"; + import { formatTimestamp, twemoji } from "@app/lib/utils"; import ActivityDiagram from "@app/components/ActivityDiagram.svelte"; - import Badge from "@app/components/Badge.svelte"; import IconSmall from "@app/components/IconSmall.svelte"; + import Link from "@app/components/Link.svelte"; + + export let compact = false; export let activity: WeeklyActivity[]; - export let compact = false; export let description: string; - export let head: string; - export let visibility: "public" | "private" = "public"; + export let baseUrl: BaseUrl; + + export let numberOfIssues: number; + export let numberOfPatches: number; + + export let isDelegate: boolean; + export let isSeeding: boolean; + export let isPrivate: boolean; + + export let lastUpdatedTimestamp: number; + + $: lastUpdated = formatTimestamp(lastUpdatedTimestamp); + export let id: string; export let name: string; -
-
-
-
- - {name} - - {#if visibility === "private"} - {#if compact} -
+ +
+
+
+ +
+
+
+

{name}

+
+ {#if isPrivate} +
+
- {:else} - - {capitalize(visibility)} - {/if} - {/if} -
-
{description}
-
- {#if compact} - {formatCommit(head)} - {:else} - {head} - {/if} + {#if isDelegate} +
+
+ +
+ {/if} + {#if isSeeding} +
+
+ +
+ {/if} +
+

{description}

+
+
+ + {numberOfIssues} Β· + + + {numberOfPatches} Β· Updated {lastUpdated} +
- - {#if compact} -
- -
- {/if}
- - {#if !compact} -
-
- {id} -
-
- -
-
- {/if} -
+ diff --git a/src/components/TransitionedHeight.svelte b/src/components/TransitionedHeight.svelte new file mode 100644 index 000000000..aa1078e8b --- /dev/null +++ b/src/components/TransitionedHeight.svelte @@ -0,0 +1,114 @@ + + + + +
+
+ +
+
diff --git a/src/config.json b/src/config.json index fc909a668..3e2b6b83f 100644 --- a/src/config.json +++ b/src/config.json @@ -17,71 +17,9 @@ ] }, "reactions": ["πŸ‘", "πŸ‘Ž", "πŸ˜„", "πŸŽ‰", "πŸ™", "πŸš€", "πŸ‘€"], - "projects": { - "pinned": [ - { - "name": "radicle-interface", - "id": "rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5", - "baseUrl": { - "hostname": "seed.radicle.xyz", - "port": 443, - "scheme": "https" - } - }, - { - "name": "heartwood", - "id": "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5", - "baseUrl": { - "hostname": "seed.radicle.xyz", - "port": 443, - "scheme": "https" - } - }, - { - "name": "rips", - "id": "rad:z3trNYnLWS11cJWC6BbxDs5niGo82", - "baseUrl": { - "hostname": "seed.radicle.xyz", - "port": 443, - "scheme": "https" - } - }, - { - "name": "radicle-team", - "id": "rad:z3TajuiHXifEDEX4qbJxe8nXr9ufi", - "baseUrl": { - "hostname": "seed.radicle.xyz", - "port": 443, - "scheme": "https" - } - }, - { - "name": "awesome-radicle", - "id": "rad:z3yQUb9HDAC7TQrUDGkQsXDsYFj9G", - "baseUrl": { - "hostname": "seed.rhizoma.dev", - "port": 443, - "scheme": "https" - } - }, - { - "name": "radicle-tui", - "id": "rad:z39mP9rQAaGmERfUMPULfPUi473tY", - "baseUrl": { - "hostname": "seed.radicle.xyz", - "port": 443, - "scheme": "https" - } - }, - { - "name": "radicle-homepage", - "id": "rad:z2ATVRDqFu2Yq2MG5ZZCWecwSDYqj", - "baseUrl": { - "hostname": "seed.radicle.xyz", - "port": 443, - "scheme": "https" - } - } - ] + "fallbackPreferredSeed": { + "hostname": "seed.radicle.garden", + "port": 443, + "scheme": "https" } } diff --git a/src/lib/commit.ts b/src/lib/commit.ts index e95b9a5ef..526320d44 100644 --- a/src/lib/commit.ts +++ b/src/lib/commit.ts @@ -116,3 +116,10 @@ export async function loadProjectActivity(id: string, baseUrl: BaseUrl) { return groupCommitsByWeek(commits.activity); } + +export async function fetchLastCommit(id: string, baseUrl: BaseUrl) { + const api = new HttpdClient(baseUrl); + const res = await api.project.getAllCommits(id, { perPage: 1 }); + + return res.commits[0]; +} diff --git a/src/lib/config.ts b/src/lib/config.ts index ad7633fc6..71c7757f6 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -12,13 +12,7 @@ export interface Config { defaultHttpdScheme: string; pinned: { baseUrl: BaseUrl }[]; }; - projects: { - pinned: { - name: string; - id: string; - baseUrl: BaseUrl; - }[]; - }; + fallbackPreferredSeed: BaseUrl; } function getConfig(): Config { @@ -33,7 +27,11 @@ function getConfig(): Config { defaultNodePort: 8776, pinned: [], }, - projects: { pinned: [] }, + fallbackPreferredSeed: { + hostname: "seed.radicle.garden", + port: 443, + scheme: "https", + }, }; } else if (window.PLAYWRIGHT) { return window.APP_CONFIG; diff --git a/src/lib/projects.ts b/src/lib/projects.ts index 4e700786a..00bde6e42 100644 --- a/src/lib/projects.ts +++ b/src/lib/projects.ts @@ -3,12 +3,22 @@ import type { BaseUrl, Project } from "@httpd-client"; import { HttpdClient } from "@httpd-client"; import { isFulfilled } from "@app/lib/utils"; import { cached } from "./cache"; +import { + fetchLastCommit, + loadProjectActivity, + type WeeklyActivity, +} from "./commit"; export interface ProjectBaseUrl { project: Project; baseUrl: BaseUrl; } +export interface ProjectWithListingData extends ProjectBaseUrl { + activity: WeeklyActivity[]; + lastCommit: Awaited>; +} + export async function getProjectsFromNodes( params: { id: string; baseUrl: BaseUrl }[], ): Promise { @@ -25,6 +35,32 @@ export async function getProjectsFromNodes( return results.filter(isFulfilled).map(r => r.value); } +export async function getProjectListingData(id: string, baseUrl: BaseUrl) { + const activity = await loadProjectActivity(id, baseUrl); + const lastCommit = await fetchLastCommit(id, baseUrl); + + return { activity, lastCommit }; +} + +export async function getProjectsListingData(projects: ProjectBaseUrl[]) { + const result = await Promise.all( + projects.map(async ({ project, baseUrl }) => { + const { activity, lastCommit } = await getProjectListingData( + project.id, + baseUrl, + ); + return { project, activity, lastCommit, baseUrl }; + }), + ); + + return result.sort((a, b) => { + const aLastCommit = a.lastCommit?.commit.committer.time ?? 0; + const bLastCommit = b.lastCommit?.commit.committer.time ?? 0; + + return bLastCommit - aLastCommit; + }); +} + export const cacheQueryProject = cached( queryProject, (baseUrl: BaseUrl, projectId: string) => diff --git a/src/lib/seeds.ts b/src/lib/seeds.ts new file mode 100644 index 000000000..700d07c26 --- /dev/null +++ b/src/lib/seeds.ts @@ -0,0 +1,122 @@ +import storedWritable from "@efstajas/svelte-stored-writable"; +import type { BaseUrl } from "@httpd-client"; +import { z } from "zod"; +import { api, httpdStore, type HttpdState } from "./httpd"; +import { writable, derived, get } from "svelte/store"; +import { config } from "./config"; + +const preferredSeedSchema = z + .object({ + hostname: z.string(), + port: z.number(), + scheme: z.string(), + }) + .optional(); + +const configuredPreferredSeeds = writable(undefined); +const storedPreferredSeed = storedWritable( + "preferredSeed", + preferredSeedSchema, + undefined, +); + +async function loadConfiguredPreferredSeeds() { + if (get(httpdStore).state === "stopped") { + configuredPreferredSeeds.set([]); + return; + } + + const profile = await api.profile.getProfile(); + + let newValue = profile.config.preferredSeeds.map(seed => { + const preferredSeedValue = seed?.split("@")[1]; + const preferredSeedOrigin = preferredSeedValue?.split(":")[0]; + + return { + hostname: preferredSeedOrigin, + port: 443, + scheme: "https", + }; + }); + + if (newValue.length === 0) { + newValue = [config.fallbackPreferredSeed]; + } + + configuredPreferredSeeds.set(newValue); +} + +export function initialize() { + let previousHttpdState: HttpdState["state"] | undefined; + + httpdStore.subscribe(async v => { + if (previousHttpdState === v.state) return; + + await loadConfiguredPreferredSeeds(); + + previousHttpdState = v.state; + }); +} + +export function selectPreferredSeed(seed: BaseUrl) { + storedPreferredSeed.set(seed); +} + +export const prefferedSeeds = derived( + [configuredPreferredSeeds, storedPreferredSeed], + ([configuredPreferredSeeds, storedPreferredSeed]) => { + // Not loaded yet + if (!configuredPreferredSeeds) return undefined; + + // No configured preferred seeds + if (configuredPreferredSeeds.length === 0) + return { + selected: config.fallbackPreferredSeed, + seeds: [config.fallbackPreferredSeed], + }; + + // No stored preferred seed + if (!storedPreferredSeed) + return { + selected: configuredPreferredSeeds[0], + seeds: configuredPreferredSeeds, + }; + + // Stored preferred seed not configured + if ( + !configuredPreferredSeeds.some( + seed => seed.hostname === storedPreferredSeed.hostname, + ) + ) + return { + selected: configuredPreferredSeeds[0], + seeds: configuredPreferredSeeds, + }; + + return { + selected: storedPreferredSeed, + seeds: configuredPreferredSeeds, + }; + }, +); + +export async function waitForLoad(): Promise<{ + selected: BaseUrl; + seeds: BaseUrl[]; +}> { + if (!get(configuredPreferredSeeds)) { + await new Promise(resolve => { + const unsubscribe = prefferedSeeds.subscribe(v => { + if (v) { + unsubscribe(); + resolve(); + } + }); + }); + } + + const seeds = get(prefferedSeeds); + if (!seeds) throw new Error("Preferred seed undefined after loading"); + + return seeds; +} diff --git a/src/views/home/Index.svelte b/src/views/home/Index.svelte index c079078af..1626c5c20 100644 --- a/src/views/home/Index.svelte +++ b/src/views/home/Index.svelte @@ -1,101 +1,213 @@
-
-

- Radicle 🌱 enables developers πŸ§™ to securely collaborate πŸ” on software -
- over a peer-to-peer network 🌐 built on Git. -

-
- - {#if projects.length > 0} -
- {#if isLocal(projects[0].baseUrl.hostname)} - - Explore projects on your local node. - {:else} - - Explore projects on the Radicle network. - {/if} -
- -
- {#each projects as { project, baseUrl, activity }} -
- +
+ + + + + + +
+ {#if !nodeId} +
+ +
+ {:else if localProjects === "error"} +
Error loading projects
+
+ There was an error loading projects from your local node. +
+
+ {:else if !localProjects.length} +
No local projects
+
+ Seed or check out a project to work with it on your local node. +
+ {:else} +
Nothing to see here
+
+ No local projects matched your filter settings. +
+ {/if} +
+
+
+ {#if filteredLocalProjects !== "error"} + {#each filteredLocalProjects as { project, baseUrl, activity, lastCommit }} - -
- {/each} + description={project.description} + numberOfIssues={project.issues.open} + numberOfPatches={project.patches.open} + isPrivate={project.visibility?.type === "private"} + isSeeding={true} + isDelegate={isDelegate(nodeId, project.delegates) ?? false} + lastUpdatedTimestamp={lastCommit.commit.committer.time} + {activity} + {baseUrl} /> + {/each} + {/if} +
+ +
+ + + +
+ +
+
+ +
+ {#if preferredSeedProjects === "error"} +
Something went wrong
+
+ There was an error loading projects from your preferred seed node. +
+ {:else} +
Nothing to see here
+
+ Your preferred seed node doesn't have any pinned projects. +
+ {/if} +
+
+
+ {#if preferredSeedProjects !== "error"} + {#each preferredSeedProjects as { project, baseUrl, activity, lastCommit }} + + {/each} + {/if}
- {/if} +
diff --git a/src/views/home/components/FilterButton.svelte b/src/views/home/components/FilterButton.svelte new file mode 100644 index 000000000..728c18572 --- /dev/null +++ b/src/views/home/components/FilterButton.svelte @@ -0,0 +1,104 @@ + + + + + + + +
+ + { + value = item.value; + closeFocused(); + }} + slot="item" + let:item + selected={item.value === value}> +
+
+ + {item.title} + + {item.description} +
+
+
+
+
+
diff --git a/src/views/home/components/HomepageSection.svelte b/src/views/home/components/HomepageSection.svelte new file mode 100644 index 000000000..f2ac49354 --- /dev/null +++ b/src/views/home/components/HomepageSection.svelte @@ -0,0 +1,79 @@ + + + + +
+
+
+

{title}

+

{subtitle}

+
+
+ +
+
+ + + {#if empty} +
+
+ +
+
+ {:else} +
+ +
+ {/if} +
+
diff --git a/src/views/home/components/NewProjectButton.svelte b/src/views/home/components/NewProjectButton.svelte new file mode 100644 index 000000000..ba649c5f0 --- /dev/null +++ b/src/views/home/components/NewProjectButton.svelte @@ -0,0 +1,38 @@ + + + + + + + +
+ Run the following command within an already-existing Git repository to + create a new Radicle project. + +
+
diff --git a/src/views/home/components/PreferredSeedDropdown.svelte b/src/views/home/components/PreferredSeedDropdown.svelte new file mode 100644 index 000000000..a070705e7 --- /dev/null +++ b/src/views/home/components/PreferredSeedDropdown.svelte @@ -0,0 +1,97 @@ + + + + + + + + +
+ {#if stateOptions} + + { + selectPreferredSeed(item); + window.location.reload(); + }} + slot="item" + selected={item.hostname === preferredSeed.hostname}> +
+ {item.hostname} +
+
+
+ {/if} +
+
+
+
Add a different seed node
+
Update your preferred seeds in your radicle config.
+ +
+ + diff --git a/src/views/home/router.ts b/src/views/home/router.ts index 0a084db66..1233b7a9a 100644 --- a/src/views/home/router.ts +++ b/src/views/home/router.ts @@ -1,17 +1,13 @@ -import type { LoadErrorRoute } from "@app/lib/router/definitions"; -import type { ProjectBaseUrl } from "@app/lib/projects"; -import type { WeeklyActivity } from "@app/lib/commit"; +import type { BaseUrl } from "@httpd-client"; +import type { ProjectWithListingData } from "@app/lib/projects"; import { get } from "svelte/store"; +import type { LoadErrorRoute } from "@app/lib/router/definitions"; +import { getProjectsListingData } from "@app/lib/projects"; +import * as seeds from "@app/lib/seeds"; import { api, httpdStore } from "@app/lib/httpd"; -import { config } from "@app/lib/config"; -import { getProjectsFromNodes } from "@app/lib/projects"; -import { loadProjectActivity } from "@app/lib/commit"; - -export interface ProjectBaseUrlActivity extends ProjectBaseUrl { - activity: WeeklyActivity[]; -} +import { HttpdClient } from "@httpd-client"; export interface HomeRoute { resource: "home"; @@ -19,35 +15,63 @@ export interface HomeRoute { export interface HomeLoadedRoute { resource: "home"; - params: { projects: ProjectBaseUrlActivity[] }; + params: { + nodeId: string | undefined; + localProjects: ProjectWithListingData[] | "error"; + preferredSeedProjects: ProjectWithListingData[] | "error"; + preferredSeed: BaseUrl; + }; +} + +const fetchProjects = async (baseUrl: BaseUrl, show: "all" | "pinned") => { + const api = new HttpdClient(baseUrl); + + return (await api.project.getAll({ perPage: 30, show })).map(project => ({ + project, + baseUrl, + })); +}; + +function handleProjectLoadError(error: unknown): "error" { + console.error(error); + return "error"; } export async function loadHomeRoute(): Promise< HomeLoadedRoute | LoadErrorRoute > { - let projects: ProjectBaseUrl[] = []; - if (get(httpdStore).state !== "stopped") { - projects = (await api.project.getAll({ perPage: 30, show: "all" })).map( - project => ({ - project, - baseUrl: api.baseUrl, - }), - ); - } else { - projects = await getProjectsFromNodes(config.projects.pinned); - } - const results = await Promise.all( - projects.map(async projectNode => { - const activity = await loadProjectActivity( - projectNode.project.id, - projectNode.baseUrl, - ); - return { - ...projectNode, - activity, - }; - }), - ); + seeds.initialize(); + const preferredSeeds = await seeds.waitForLoad(); - return { resource: "home", params: { projects: results } }; + const connectedToLocalNode = get(httpdStore).state !== "stopped"; + + const [localProjects, seedProjects] = await Promise.all([ + connectedToLocalNode + ? fetchProjects(api.baseUrl, "all").catch(handleProjectLoadError) + : undefined, + fetchProjects(preferredSeeds.selected, "pinned").catch( + handleProjectLoadError, + ), + ]); + + const projectsWithListingData = await Promise.all([ + localProjects !== "error" + ? await getProjectsListingData(localProjects ?? []) + : ("error" as const), + seedProjects !== "error" + ? await getProjectsListingData(seedProjects) + : ("error" as const), + ]); + + const nodeId = connectedToLocalNode ? (await api.getNode()).id : undefined; + + return { + resource: "home", + params: { + localProjects: projectsWithListingData[0], + preferredSeedProjects: projectsWithListingData[1], + preferredSeed: preferredSeeds.selected, + nodeId, + }, + }; } diff --git a/src/views/nodes/View.svelte b/src/views/nodes/View.svelte index 6fb70baf4..0d41a9055 100644 --- a/src/views/nodes/View.svelte +++ b/src/views/nodes/View.svelte @@ -1,24 +1,23 @@