mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-18 19:30:27 +08:00
@@ -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
@@ -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;
|
||||
|
||||
@@ -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)}>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
@@ -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 }));
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user