Add seeds page

Signed-off-by: Sebastian Martinez <sebastinez@me.com>
This commit is contained in:
Sebastian Martinez
2021-12-07 11:11:34 +01:00
parent 68395bceb2
commit 72d5b3b34c
13 changed files with 275 additions and 40 deletions
+2
View File
@@ -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} />
+7 -1
View File
@@ -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;
+5 -3
View File
@@ -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)}>
+11 -1
View File
@@ -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}
+9 -8
View File
@@ -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}
+5 -24
View File
@@ -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 {
+11
View File
@@ -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>
+46
View File
@@ -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 }));
}
}
+169
View File
@@ -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}
+1 -1
View File
@@ -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 {
+2 -1
View File
@@ -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;
+4
View File
@@ -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,
+3 -1
View File
@@ -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.