Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add seeds page
Sebastian Martinez committed 4 years ago
commit 72d5b3b34cecf501cc90fd5c3db6b2fd46436576
parent 68395bceb242a5b1085955f198cd539d274463d5
13 files changed +275 -40
modified src/App.svelte
@@ -8,6 +8,7 @@
  import Registrations from '@app/base/registrations/Routes.svelte';
  import Orgs from '@app/base/orgs/Routes.svelte';
  import Users from '@app/base/users/Routes.svelte';
+
  import Seeds from '@app/base/seeds/Routes.svelte';
  import Faucet from '@app/base/faucet/Routes.svelte';
  import Projects from '@app/base/projects/Routes.svelte';
  import Resolver from '@app/base/resolver/Routes.svelte';
@@ -83,6 +84,7 @@
        </Route>
        <Registrations {config} session={$session} />
        <Orgs {config} />
+
        <Seeds {config} />
        <Faucet {config} />
        <Users {config} />
        <Projects {config} />
modified src/api.ts
@@ -17,7 +17,13 @@ export async function get(
  const base = config.seed.api.host;
  const port = config.seed.api.port;
  const search = new URLSearchParams(query).toString();
-
  const baseUrl = `https://${base}:${port}/v1/${path}`;
+
  // Allow using the functionality with local runned http-api
+
  const isLocalhost = /^0.0.0.0$/.test(base);
+
  const protocol = isLocalhost ? "http://" : "https://";
+

+
  const baseUrl = path
+
    ? `${protocol}${base}:${port}/v1/${path}`
+
    : `${protocol}${base}:${port}`;
  const url = search ? `${baseUrl}?${search}` : baseUrl;

  let response = null;
modified src/base/orgs/View.svelte
@@ -275,9 +275,11 @@
              </button>
            </div>
            <div class="seed-address desktop">
-
              <span class="seed-icon">🌱</span>{
-
                utils.formatSeedId(profile.seedId)}@{profile.seedHost
-
              }<span class="faded">:{config.seed.link.port}</span>
+
              <span class="seed-icon">🌱</span>
+
                <a href="/seeds/{profile.seedHost}" class="link">
+
                  {utils.formatSeedId(profile.seedId)}@{profile.seedHost}
+
                </a>
+
              <span class="faded">:{config.seed.link.port}</span>
            </div>
            <div class="desktop">
              <button class="tiny faded" disabled={seedCopied} on:click={copySeed(profile.seedId, profile.seedHost)}>
modified src/base/orgs/View/Projects.svelte
@@ -7,6 +7,7 @@
  import Message from "@app/Message.svelte";
  import Widget from '@app/base/projects/Widget.svelte';
  import Anchor from './Anchor.svelte';
+
  import { formatCommit } from "@app/utils";

  export let org: Org;
  export let config: Config;
@@ -47,6 +48,10 @@
      <div class="project">
        {#if "safeTxHash" in project} <!-- Pending project -->
          <Widget {project} org={org.address} {config} faded>
+
            <span slot="stateHash">
+
              <span class="mobile">commit {formatCommit(project.anchor.stateHash)}</span>
+
              <span class="desktop">commit {project.anchor.stateHash}</span>
+
            </span>
            <span class="anchor" slot="actions">
              {#if org.safe && $session}
                <Anchor {project} safe={org.safe} on:success={() => updateRecords()} account={$session.address} {config} />
@@ -55,7 +60,12 @@
            </span>
          </Widget>
        {:else} <!-- Anchored project -->
-
          <Widget {project} org={org.address} {config} />
+
          <Widget {project} org={org.address} {config}>
+
            <span slot="stateHash">
+
              <span class="mobile">commit {formatCommit(project.anchor.stateHash)}</span>
+
              <span class="desktop">commit {project.anchor.stateHash}</span>
+
            </span>
+
          </Widget>
        {/if}
      </div>
    {/each}
modified src/base/projects/Widget.svelte
@@ -5,7 +5,7 @@
  import * as proj from '@app/project';
  import Loading from '@app/Loading.svelte';
  import Blockies from '@app/Blockies.svelte';
-
  import { formatCommit, formatRadicleUrn } from '@app/utils';
+
  import { formatRadicleUrn } from '@app/utils';

  enum Status { Loading, Loaded, Error }

@@ -41,7 +41,7 @@
          urn: project.id,
          org,
          user,
-
          commit: project.anchor.stateHash,
+
          commit: project.anchor?.stateHash,
        })
      );
    }
@@ -128,8 +128,9 @@
    </div>
    <div class="description">{info.meta.description}</div>
    <div class="anchor">
-
      <span class="mobile">commit {formatCommit(project.anchor.stateHash)}</span>
-
      <span class="desktop">commit {project.anchor.stateHash}</span>
+
      <span class="commit">
+
        <slot name="stateHash"></slot>
+
      </span>
      <span>
        {#each info.meta.maintainers as urn}
          <span class="avatar">
@@ -147,11 +148,11 @@
      {/if}
    </div>
    <div class="anchor">
-
      <span class="commit mobile">commit {formatCommit(project.anchor.stateHash)}</span>
-
      <span class="commit desktop">commit {project.anchor.stateHash}</span>
+
      <span class="commit">
+
        <slot name="stateHash"></slot>
+
      </span>
      <span class="actions">
-
        <slot name="actions">
-
        </slot>
+
        <slot name="actions" />
      </span>
    </div>
  {/if}
modified src/base/registrations/registrar.ts
@@ -6,35 +6,15 @@ import type { TypedDataSigner } from '@ethersproject/abstract-signer';
import * as session from '@app/session';
import { Failure } from '@app/error';
import type { Config } from '@app/config';
-
import { isDomain, unixTime } from '@app/utils';
+
import { unixTime } from '@app/utils';
import { assert } from '@app/error';
+
import { Seed } from '@app/base/seeds/Seed';

export interface Registration {
  profile: EnsProfile;
  resolver: EnsResolver;
}

-
export class Seed {
-
  id?: string;
-
  host?: string;
-
  git?: string;
-
  api?: string;
-

-
  constructor(id?: string, host?: string, git?: string, api?: string) {
-
    if (id && /^[a-z0-9]+$/.test(id)) {
-
      this.id = id;
-
    }
-
    if (host && isDomain(host)) {
-
      this.host = host;
-
    }
-
    if (api && isDomain(api)) {
-
      this.api = api;
-
    }
-
    if (git && isDomain(git)) {
-
      this.git = git;
-
    }
-
  }
-
}

export interface EnsProfile {
  name: string;
@@ -113,8 +93,9 @@ export async function getRegistration(name: string, config: Config, resolver?: E
      url,
      avatar,
      seed: new Seed(
-
        seedId,
+
        config,
        seedHost,
+
        seedId,
        seedGit,
        seedApi,
      ),
@@ -161,7 +142,7 @@ export async function getSeed(name: string, config: Config, resolver?: EnsResolv
    resolver.getText('eth.radicle.seed.api'),
  ]);

-
  return new Seed(id, host, git, api);
+
  return new Seed(config, host, id, git, api);
}

export function registrar(config: Config): ethers.Contract {
added src/base/seeds/Routes.svelte
@@ -0,0 +1,11 @@
+
<script lang="ts">
+
  import { Route } from "svelte-routing";
+
  import View from '@app/base/seeds/View.svelte';
+
  import type { Config } from '@app/config';
+

+
  export let config: Config;
+
</script>
+

+
<Route path="/seeds/:seed" let:params>
+
  <View {config} seedAddress={params.seed}/>
+
</Route>
added src/base/seeds/Seed.ts
@@ -0,0 +1,46 @@
+
import * as api from '@app/api';
+
import type { Config } from '@app/config';
+
import { getInfo, getProjects } from '@app/project';
+
import type { Info, Project } from "@app/project";
+
import { isDomain } from '@app/utils';
+

+
export interface SeedInfo {
+
  version: string;
+
}
+

+
export class Seed {
+
  host?: string;
+
  id?: string;
+
  git?: string;
+
  api?: string;
+
  config: Config;
+

+
  constructor(config: Config, host?: string, id?: string, git?: string, api?: string) {
+
    if (id && /^[a-z0-9]+$/.test(id)) {
+
      this.id = id;
+
    }
+
    if (host && isDomain(host)) {
+
      this.host = host;
+
    }
+
    if (api && isDomain(api)) {
+
      this.api = api;
+
    }
+
    if (git && isDomain(git)) {
+
      this.git = git;
+
    }
+
    this.config = config.withSeed(this);
+
  }
+
  async getInfo(): Promise<SeedInfo> {
+
    return api.get(``, {}, this.config);
+
  }
+
  async getPeer(): Promise<{ id: string }> {
+
    return api.get("peer", {}, this.config);
+
  }
+
  async getProject(urn: string): Promise<Info> {
+
    return getInfo(urn, this.config);
+
  }
+
  async getProjects(): Promise<Project[]> {
+
    const result = await getProjects(this.config);
+
    return result.map((project: any) => ({ ...project, id: project.urn }));
+
  }
+
}
added src/base/seeds/View.svelte
@@ -0,0 +1,169 @@
+
<script lang="ts">
+
  import type { Config } from "@app/config";
+
  import { Seed } from "@app/base/seeds/Seed";
+
  import Widget from "@app/base/projects/Widget.svelte";
+
  import * as utils from "@app/utils";
+
  import Loading from "@app/Loading.svelte";
+

+
  export let config: Config;
+
  export let seedAddress: string;
+

+
  const seed = new Seed(config, seedAddress);
+

+
  let seedCopied = false;
+
  const copySeed = (seedId: string, seedHost: string) => {
+
    return () => utils.toClipboard(utils.formatSeedAddress(seedId, seedHost, config)).then(() => {
+
      seedCopied = true;
+
      setTimeout(() => {
+
        seedCopied = false;
+
      }, 3000);
+
    });
+
  };
+
</script>
+

+
<style>
+
  main {
+
    padding: 5rem 0;
+
    width: 54rem;
+
  }
+
  main > header {
+
    display: flex;
+
    align-items: center;
+
    justify-content: left;
+
    margin-bottom: 2rem;
+
  }
+
  main > header > * {
+
    margin: 0 1rem 0 0;
+
  }
+
  .info {
+
    display: flex;
+
    flex-direction: column;
+
    justify-content: center;
+
    align-items: left;
+
  }
+
  .fields {
+
    display: grid;
+
    grid-template-columns: 5rem 4fr 2fr;
+
    grid-gap: 1rem 2rem;
+
  }
+
  .fields > div {
+
    justify-self: start;
+
    align-self: center;
+
    height: 2rem;
+
    line-height: 2rem;
+
  }
+
  .title {
+
    display: flex;
+
    align-items: center;
+
  }
+
  .projects {
+
    margin-top: 2rem;
+
  }
+
  .projects .project {
+
    margin-bottom: 1rem;
+
  }
+
  .mobile {
+
    display: none !important;
+
  }
+
  .desktop {
+
    display: block !important;
+
  }
+
  @media (max-width: 720px) {
+
    main {
+
      width: 100%;
+
      padding: 1.5rem;
+
    }
+
    .fields {
+
      grid-template-columns: 5rem auto;
+
    }
+
    .mobile {
+
      display: block !important;
+
    }
+
    .desktop {
+
      display: none !important;
+
    }
+
  }
+
</style>
+

+
<svelte:head>
+
  <title>{seed.host}</title>
+
</svelte:head>
+

+
{#await seed.getInfo()}
+
  <main class="off-centered">
+
    <Loading center />
+
  </main>
+
{:then info}
+
  <main>
+
    <header>
+
      <div class="info">
+
        <span class="title">
+
          <span class="bold">
+
            {seed.host}
+
          </span>
+
        </span>
+
      </div>
+
    </header>
+

+
    <div class="fields">
+
      <!-- Seed Address -->
+
      <div class="label">Seed</div>
+
      {#if info.version === "0.2.0" && seed.host}
+
        {#await seed.getPeer() then peer}
+
          <div class="mobile">
+
            <button class="tiny faded" disabled={seedCopied} on:click={copySeed(peer.id, seed.host)}>
+
              {#if seedCopied}
+
                Copy ✓
+
              {:else}
+
                Copy
+
              {/if}
+
            </button>
+
          </div>
+
          <div class="seed-address desktop">
+
            <span class="seed-icon">🌱</span>
+
            {utils.formatSeedId(peer.id)}@{seed.host}
+
            <span class="faded">:{seed.config.seed.link.port}</span>
+
          </div>
+
          <div class="desktop">
+
            <button
+
              class="tiny faded"
+
              disabled={seedCopied}
+
              on:click={copySeed(peer.id, seed.host)}
+
            >
+
              {#if seedCopied}
+
                Copy ✓
+
              {:else}
+
                Copy
+
              {/if}
+
            </button>
+
          </div>
+
        {/await}
+
      {:else}
+
        <div class="seed-address subtle">N/A</div>
+
        <div class="desktop" />
+
      {/if}
+
      <!-- API Port -->
+
      <div class="label">API Port</div>
+
      <div>{seed.config.seed.api.port}</div>
+
      <div class="desktop" />
+
      <!-- API Version -->
+
      <div class="label">Version</div>
+
      <div>{info.version}</div>
+
      <div class="desktop" />
+
    </div>
+
    <!-- Seed Projects -->
+
    {#if info.version === "0.2.0"}
+
      {#await seed.getProjects() then projects}
+
        <div class="projects">
+
          {#each projects as project}
+
            <div class="project">
+
              <Widget {project} {config} />
+
            </div>
+
          {/each}
+
        </div>
+
      {/await}
+
    {:else}
+
      <div class="projects subtle">For seed project listing, update http-api to v0.2.0</div>
+
    {/if}
+
  </main>
+
{/await}
modified src/config.ts
@@ -7,7 +7,7 @@ import { Core } from '@self.id/core';
import WalletConnect from "@walletconnect/client";
import config from "@app/config.json";
import { WalletConnectSigner } from "./WalletConnectSigner";
-
import type { Seed } from "@app/base/registrations/registrar";
+
import type { Seed } from '@app/base/seeds/Seed';

declare global {
  interface Window {
modified src/profile.ts
@@ -1,9 +1,10 @@
-
import type { EnsProfile, Seed } from "@app/base/registrations/registrar";
+
import type { EnsProfile } from "@app/base/registrations/registrar";
import type { BasicProfile } from '@datamodels/identity-profile-basic';
import {
  isAddress, formatCAIP10Address, formatIpfsFile, resolveEnsProfile, resolveIdxProfile, parseUsername, parseEnsLabel
} from "@app/utils";
import type { Config } from "@app/config";
+
import type { Seed } from "@app/base/seeds/Seed";

export interface IProfile {
  address: string;
modified src/project.ts
@@ -82,6 +82,10 @@ export async function getInfo(urn: string, config: Config): Promise<Info> {
  return api.get(`projects/${urn}`, {}, config);
}

+
export async function getProjects(config: Config): Promise<any> {
+
  return api.get("projects", {}, config);
+
}
+

export async function getTree(
  urn: string,
  commit: string,
modified src/utils.ts
@@ -417,8 +417,10 @@ export function isMarkdownPath(path: string): boolean {
}

// Check whether the given input string is a domain, eg. `alt-clients.radicle.xyz.
+
// Also accepts in dev env 0.0.0.0 as domain
export function isDomain(input: string): boolean {
-
  return /^[a-z][a-z0-9.-]+$/.test(input) && /\.[a-z]+$/.test(input);
+
  return (/^[a-z][a-z0-9.-]+$/.test(input) && /\.[a-z]+$/.test(input))
+
    || (!import.meta.env.PROD && /^0.0.0.0$/.test(input)) ;
}

// Propose a Gnosis Safe multi-sig transaction.