Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Only have projects on home page
Alexis Sellier committed 3 years ago
commit dbfa9862ddeb0e36b72a71a8dabce611662441a2
parent c4769c8127ee3fcd307bf7ea522a880ea915dcc6
6 files changed +163 -77
modified cypress/e2e/home.spec.ts
@@ -2,28 +2,22 @@
import { MockExtensionProvider } from "../support/e2e";

describe("Landing page", () => {
-
  it("Loads user, orgs and seeds", () => {
+
  it("Loads projects", () => {
    cy.intercept("https://willow.radicle.garden:8777/", { fixture: "projectHome.json" });
    cy.intercept("https://willow.radicle.garden:8777/v1/peer", { fixture: "projectPeer.json" });
    cy.intercept("https://pine.radicle.garden:8777/", { fixture: "projectHome.json" });
    cy.intercept("https://pine.radicle.garden:8777/v1/peer", { fixture: "projectPeer.json" });
    cy.intercept("https://maple.radicle.garden:8777/", { fixture: "projectHome.json" });
    cy.intercept("https://maple.radicle.garden:8777/v1/peer", { fixture: "projectPeer.json" });
-
    cy.intercept("https://gateway.thegraph.com/api/1758a78ae257ad4906f9c638e4a68c19/subgraphs/id/0x2f0963e77ca6ac0c2dad1bf4147b6b40e0dd8728-0", {
-
      "data": { "safe": null }
-
    });
-
    cy.intercept("https://maple.radicle.garden:8777/v1/projects", { fixture: "projectList.json" });
-
    cy.intercept("https://pine.radicle.garden:8777/v1/projects", { fixture: "projectList.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects", { fixture: "projectList.json" });
+
    cy.intercept("https://maple.radicle.garden:8777/v1/projects/*", { fixture: "projectInfo.json" });
+
    cy.intercept({ pathname: "/v1/projects/*" }, { fixture: "projectInfo.json" });
    cy.visit("/", {
      onBeforeLoad(win) {
        win.ethereum = new MockExtensionProvider("homestead", "0x3256a804085C24f3451cAb2C98a37e16DEEc5721");
      },
    });
-
    cy.get("div.card-label")
+
    cy.get(".project .name")
      .first()
-
      .should("have.text", "willow.radicle.garden")
-
      .next()
-
      .should("have.text", "1 project(s)");
+
      .should("have.text", "bright-forest-protocol");
  });
});
modified src/base/home/Index.svelte
@@ -1,12 +1,13 @@
<script lang="ts">
+
  import { navigate } from 'svelte-routing';
  import type { Config } from '@app/config';
-
  import { Profile } from '@app/profile';
-
  import { Org } from '@app/base/orgs/Org';
  import Loading from '@app/Loading.svelte';
+
  import Widget from '@app/base/projects/Widget.svelte';
+
  import { Project, ProjectInfo } from '@app/project';
+
  import type { Host } from '@app/api';
+
  import * as proj from "@app/project";
  import Message from '@app/Message.svelte';
-
  import Cards from '@app/Cards.svelte';
  import { setOpenGraphMetaTag } from '@app/utils';
-
  import { Seed } from '@app/base/seeds/Seed';

  export let config: Config;

@@ -16,21 +17,18 @@
    { prop: "og:url", content: window.location.href }
  ]);

-
  const getOrgs = config.orgs.pinned.length > 0
-
    ? Org.getMulti(config.orgs.pinned, config)
-
    : Org.getAll(config);
-

-
  const getUsers = config.users.pinned.length > 0
-
    ? Profile.getMulti(config.users.pinned, config)
-
    : Promise.resolve([]);
-

-
  const getSeeds = Object.keys(config.seeds.pinned).length > 0
-
    ? Seed.lookupMulti(Object.keys(config.seeds.pinned), config)
+
  const getProjects = config.projects.pinned.length > 0
+
    ? Project.getMulti(config.projects.pinned)
    : Promise.resolve([]);

-
  const getEntities = Promise.all([getUsers, getOrgs, getSeeds]).then(([users, orgs, seeds]) => {
-
    return { users, orgs, seeds };
-
  });
+
  const onClick = (project: ProjectInfo, seed: Host) => {
+
    navigate(proj.path({
+
      urn: project.urn,
+
      seed: seed.host,
+
      profile: null,
+
      revision: project.head,
+
    }));
+
  };
</script>

<style>
@@ -48,10 +46,21 @@
    border-radius: var(--border-radius);
    margin-bottom: 1.5rem;
  }
+
  .projects {
+
    display: flex;
+
    flex-direction: row;
+
    flex-wrap: wrap;
+
    gap: 1rem;
+
    width: 100%;
+
  }
+
  .project {
+
    width: 16rem;
+
  }
  .heading {
    color: var(--color-secondary);
    padding: 1rem 0rem;
    font-size: 1.25rem;
+
    margin-bottom: 1rem;
  }
  .loading {
    padding-top: 2rem;
@@ -77,31 +86,29 @@
    peer-to-peer network 🌐 built on Git.</p>
  </div>

-
  {#await getEntities}
+
  {#await getProjects}
    <div class="loading">
      <Loading center />
    </div>
-
  {:then entities}
-
    {#if entities.seeds.length}
+
  {:then results}
+
    {#if results.length}
      <div class="heading">
-
        Explore <strong>seed nodes</strong> on the Radicle network.
+
        Explore <strong>projects</strong> on the Radicle network.
      </div>
-
      <Cards {config} seeds={entities.seeds}>
-
        <div class="empty">There are no seed nodes.</div>
-
      </Cards>
-
    {/if}
-
    {#if entities.orgs.length || entities.users.length}
-
      <div class="heading">
-
        Explore <strong>orgs</strong> and <strong>users</strong> on the Radicle network.
+

+
      <div class="projects">
+
        {#each results as result}
+
          <div class="project">
+
            <Widget compact project={result.info} seed={{ api: result.seed }}
+
              on:click={() => onClick(result.info, result.seed)} />
+
          </div>
+
        {/each}
      </div>
-
      <Cards {config} profiles={entities.users} orgs={entities.orgs}>
-
        <div class="empty">There are no orgs or users.</div>
-
      </Cards>
    {/if}
  {:catch}
    <div class="padding">
      <Message error>
-
        <strong>Error: </strong> failed to load orgs and users.
+
        <strong>Error: </strong> failed to load projects.
      </Message>
    </div>
  {/await}
modified src/base/projects/Widget.svelte
@@ -3,13 +3,15 @@
  import AnchorBadge from '@app/base/profiles/AnchorBadge.svelte';
  import Diagram from '@app/Diagram.svelte';
  import { groupCommitsByWeek } from '@app/commit';
+
  import type { Host } from '@app/api';
  import { Project } from '@app/project';
-
  import type { Seed } from '@app/base/seeds/Seed';
+
  import { formatCommit } from '@app/utils';

  export let project: proj.ProjectInfo;
-
  export let seed: Seed;
+
  export let seed: { api: Host };
  export let faded = false;
  export let anchor: proj.Anchor | null = null;
+
  export let compact = false;

  const getTimestampOneYearAgo = () => {
    const now = new Date();
@@ -48,8 +50,22 @@
  }
  div .description {
    overflow-x: hidden;
+
    overflow-y: hidden;
    text-overflow: ellipsis;
  }
+
  article.compact {
+
    min-width: 16rem;
+
    height: 8rem;
+
  }
+
  article.compact .left {
+
    width: 100%;
+
  }
+
  article.compact .right {
+
    display: none;
+
  }
+
  article.compact .description {
+
    white-space: nowrap;
+
  }
  article.project-faded {
    border: 1px dashed var(--color-foreground-subtle);
    cursor: not-allowed;
@@ -126,7 +142,7 @@
  }
</style>

-
<article on:click class:project-faded={faded}>
+
<article on:click class:project-faded={faded} class:compact>
  <div class="left">
    <div class="id">
      <span class="name">{project.name}</span>
@@ -136,7 +152,11 @@
      <span class="commit">
        <slot name="stateHash">
          {#if project.head}
-
            {project.head}
+
            {#if compact}
+
              {formatCommit(project.head)}
+
            {:else}
+
              {project.head}
+
            {/if}
          {:else}
            <span class="subtle">✗ No head</span>
          {/if}
@@ -144,36 +164,39 @@
      </span>
    </div>
  </div>
-
  <div class="right">
-
    <div class="id">
-
      <span class="urn desktop">{project.urn}</span>
-
    </div>
-
    <div class="anchor">
-
      <span class="anchor-info">
-
        <span class="actions">
-
          <slot name="actions">
-
          </slot>
-
        </span>
-
        <span class="anchor-badge">
-
          <slot name="anchor">
-
            {#if anchor && project.head}
-
              <AnchorBadge
-
                commit={project.head}
-
                head={project.head} noText noBg
-
                anchors={[anchor.anchor.stateHash]} />
-
            {/if}
-
          </slot>
+

+
  {#if !compact}
+
    <div class="right">
+
      <div class="id">
+
        <span class="urn desktop">{project.urn}</span>
+
      </div>
+
      <div class="anchor">
+
        <span class="anchor-info">
+
          <span class="actions">
+
            <slot name="actions">
+
            </slot>
+
          </span>
+
          <span class="anchor-badge">
+
            <slot name="anchor">
+
              {#if anchor && project.head}
+
                <AnchorBadge
+
                  commit={project.head}
+
                  head={project.head} noText noBg
+
                  anchors={[anchor.anchor.stateHash]} />
+
              {/if}
+
            </slot>
+
          </span>
        </span>
-
      </span>
-
    </div>
-
    {#await loadCommits() then points}
-
      <div class="desktop activity">
-
        <Diagram {points}
-
          strokeWidth={3}
-
          viewBoxHeight={100}
-
          viewBoxWidth={600}
-
        />
      </div>
-
    {/await}
-
  </div>
+
      {#await loadCommits() then points}
+
        <div class="desktop activity">
+
          <Diagram {points}
+
            strokeWidth={3}
+
            viewBoxHeight={100}
+
            viewBoxWidth={600}
+
          />
+
        </div>
+
      {/await}
+
    </div>
+
  {/if}
</article>
modified src/config.json
@@ -99,6 +99,50 @@
      "git": { "port": 443 }
    }
  },
+
  "projects": {
+
    "pinned": [
+
      {
+
        "name": "radicle-cli",
+
        "urn": "rad:git:hnrkmg77m8tfzj4gi4pa4mbhgysfgzwntjpao",
+
        "seed": "seed.alt-clients.radicle.xyz"
+
      },
+
      {
+
        "name": "radicle-interface",
+
        "urn": "rad:git:hnrkjajuucc6zp5eknt3s9xykqsrus44cjimy",
+
        "seed": "seed.alt-clients.radicle.xyz"
+
      },
+
      {
+
        "name": "radicle-client-services",
+
        "urn": "rad:git:hnrkk9c4zt9thuxhwi1ukxqcrs5tmhbtcsony",
+
        "seed": "seed.alt-clients.radicle.xyz"
+
      },
+
      {
+
        "name": "solmate",
+
        "urn": "rad:git:hnrkbgczcfh9ycjtdfynmqyg478bqrso1hnty",
+
        "seed": "willow.radicle.garden"
+
      },
+
      {
+
        "name": "openzeppelin-contracts",
+
        "urn": "rad:git:hnrkbx9xzjg8tf9hj9uh5qdxwuyjddsxyaw6o",
+
        "seed": "willow.radicle.garden"
+
      },
+
      {
+
        "name": "haskell-language-server",
+
        "urn": "rad:git:hnrkf1jaje1e93rua64zgphg7zb14qo9zexgy",
+
        "seed": "pine.radicle.garden"
+
      },
+
      {
+
        "name": "rx",
+
        "urn": "rad:git:hnrk8aoks6w3tdbcwt9dqnor9aqy8tjha3k5o",
+
        "seed": "maple.radicle.garden"
+
      },
+
      {
+
        "name": "py_ecc",
+
        "urn": "rad:git:hnrkba67x8poqt4d9qgoc4au39ro9dx7pdhfo",
+
        "seed": "maple.radicle.garden"
+
      }
+
    ]
+
  },
  "ipfs": { "gateway": "https://ipfs.io/ipfs/" },
  "abi": {
    "registrar": [
modified src/config.ts
@@ -35,6 +35,7 @@ export class Config {
  reverseRegistrar: { address: string };
  orgs: { subgraph: string; contractHash: string; pinned: string[] };
  users: { pinned: string[] };
+
  projects: { pinned: { urn: string; name: string; seed: string }[] };
  seeds: { pinned: Record<string, { emoji: string }> };
  gasLimits: { createOrg: number };
  provider: ethers.providers.JsonRpcProvider;
@@ -120,6 +121,7 @@ export class Config {
    this.provider = provider;
    this.signer = null;
    this.gasLimits = gasLimits;
+
    this.projects = config.projects;
    this.abi = config.abi;
    this.ceramic = {
      client: ceramic,
modified src/project.ts
@@ -432,4 +432,20 @@ export class Project implements ProjectInfo {

    return new Project(urn, info, seed, peers, remote.heads, profile, anchors);
  }
+

+
  static async getMulti(projs: { urn: Urn; seed: string }[]): Promise<{ info: ProjectInfo; seed: Host }[]> {
+
    const promises = [];
+

+
    for (const proj of projs) {
+
      const seed = { host: proj.seed, port: null };
+
      promises.push(Project.getInfo(proj.urn, seed).then(info => {
+
        return { info, seed };
+
      }));
+
    }
+
    const results = await Promise.allSettled(promises);
+
    const isFulfilled = <T>(input: PromiseSettledResult<T>):
+
      input is PromiseFulfilledResult<T> => input.status === 'fulfilled';
+

+
    return results.filter(isFulfilled).map(r => r.value);
+
  }
}