mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-29 08:10:59 +08:00
Users can specify anchors address in ENS
Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
@@ -27,9 +27,9 @@
|
||||
export let config: Config;
|
||||
export let path: string;
|
||||
export let profile: Profile | null = null;
|
||||
export let anchors: string | null = null;
|
||||
export let org: string | null = null;
|
||||
|
||||
const orgAddress = org ?? undefined;
|
||||
export let user: string | null = null;
|
||||
|
||||
// When the component is loaded the first time, the blob is yet to be loaded.
|
||||
let state: State = { status: Status.Idle };
|
||||
@@ -61,7 +61,13 @@
|
||||
const blob = await loadBlob(path);
|
||||
getBlob = new Promise(resolve => resolve(blob));
|
||||
|
||||
navigate(proj.path({ urn, org: orgAddress, commit, path }));
|
||||
if (org) {
|
||||
navigate(proj.path({ urn, org, commit, path }));
|
||||
} else if (user) {
|
||||
navigate(proj.path({ urn, user, commit, path }));
|
||||
} else {
|
||||
navigate(proj.path({ urn, commit, path }));
|
||||
}
|
||||
};
|
||||
|
||||
const fetchTree = async (path: string) => {
|
||||
@@ -71,7 +77,7 @@
|
||||
// This is reactive to respond to path changes that don't originate from this
|
||||
// component, eg. when using the browser's "back" button.
|
||||
$: getBlob = loadBlob(path);
|
||||
$: getAnchor = orgAddress ? Org.getAnchor(orgAddress, urn, config) : null;
|
||||
$: getAnchor = anchors ? Org.getAnchor(anchors, urn, config) : null;
|
||||
$: loadingPath = state.status == Status.Loading ? state.path : null;
|
||||
</script>
|
||||
|
||||
@@ -244,16 +250,16 @@
|
||||
{:then tree}
|
||||
<header>
|
||||
<div class="commit">
|
||||
commit {commit}
|
||||
{commit}
|
||||
</div>
|
||||
<div class="anchor">
|
||||
{#if orgAddress}
|
||||
{#if anchors}
|
||||
{#await getAnchor}
|
||||
<Loading small margins />
|
||||
{:then anchor}
|
||||
{#if anchor === commit}
|
||||
<span class="anchor-widget">
|
||||
<span class="anchor-label" title="{orgAddress}">anchored 🔒</span>
|
||||
<span class="anchor-label" title="{anchors}">anchored 🔒</span>
|
||||
</span>
|
||||
{:else}
|
||||
<span class="anchor-widget not-anchored">
|
||||
|
||||
@@ -31,3 +31,17 @@
|
||||
<Route path="/orgs/:org/projects/:urn" let:params>
|
||||
<View {config} org={params.org} urn={params.urn} path="/" />
|
||||
</Route>
|
||||
|
||||
<!-- With a User context -->
|
||||
|
||||
<Route path="/users/:user/projects/:urn/head/*" let:params>
|
||||
<View {config} user={params.user} urn={params.urn} path={params['*'] || "/"} />
|
||||
</Route>
|
||||
|
||||
<Route path="/users/:user/projects/:urn/:commit/*" let:params>
|
||||
<View {config} user={params.user} urn={params.urn} commit={params.commit} path={params["*"] || "/"} />
|
||||
</Route>
|
||||
|
||||
<Route path="/users/:user/projects/:urn" let:params>
|
||||
<View {config} user={params.user} urn={params.urn} path="/" />
|
||||
</Route>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import Modal from '@app/Modal.svelte';
|
||||
import Avatar from '@app/Avatar.svelte';
|
||||
import { Org } from '@app/base/orgs/Org';
|
||||
import type { Profile } from '@app/profile';
|
||||
import { Profile, ProfileType } from '@app/profile';
|
||||
import type { Info } from '@app/project';
|
||||
import { formatOrg, isAddressEqual } from '@app/utils';
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
export let urn: string;
|
||||
export let org = "";
|
||||
export let user = "";
|
||||
export let commit = "";
|
||||
export let config: Config;
|
||||
export let path: string;
|
||||
@@ -24,22 +25,24 @@
|
||||
let getProject = new Promise<Profile | null>(resolve => {
|
||||
if (org) {
|
||||
Org.getProjectProfile(org, config).then(p => resolve(p));
|
||||
} else if (user) {
|
||||
Profile.get(user, ProfileType.Project, config).then(p => resolve(p));
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
}).then(async (orgProfile) => {
|
||||
const seedHost = orgProfile?.seedHost;
|
||||
const seedId = orgProfile?.seedId || undefined;
|
||||
}).then(async (profile) => {
|
||||
const seedHost = profile?.seedHost;
|
||||
const seedId = profile?.seedId || undefined;
|
||||
const cfg = seedHost ? config.withSeed(seedHost, seedId) : config;
|
||||
const info = await proj.getInfo(urn, cfg);
|
||||
|
||||
projectInfo = info;
|
||||
|
||||
return { project: info, config: cfg, org: orgProfile };
|
||||
return { project: info, config: cfg, profile };
|
||||
});
|
||||
|
||||
const parentUrl = (profile: Profile) => {
|
||||
return org === profile.name || isAddressEqual(org, profile.address)
|
||||
return org
|
||||
? `/orgs/${profile.nameOrAddress}`
|
||||
: `/users/${profile.nameOrAddress}`;
|
||||
};
|
||||
@@ -111,9 +114,9 @@
|
||||
{:then result}
|
||||
<header>
|
||||
<div class="title bold">
|
||||
{#if result.org}
|
||||
<a class="org-avatar" title={result.org.nameOrAddress} href={parentUrl(result.org)}>
|
||||
<Avatar source={result.org.avatar || result.org.address} address={result.org.address}/>
|
||||
{#if result.profile}
|
||||
<a class="org-avatar" title={result.profile.nameOrAddress} href={parentUrl(result.profile)}>
|
||||
<Avatar source={result.profile.avatar || result.profile.address} address={result.profile.address}/>
|
||||
</a>
|
||||
<span class="divider">/</span>
|
||||
{/if}
|
||||
@@ -122,7 +125,9 @@
|
||||
<div class="urn">{urn}</div>
|
||||
<div class="description">{result.project.meta.description}</div>
|
||||
</header>
|
||||
<Browser {urn} {org} profile={result.org} {path}
|
||||
<Browser {urn} {org} {user} {path}
|
||||
anchors={result.profile?.projectAnchors ?? org}
|
||||
profile={result.profile}
|
||||
commit={commit || result.project.head}
|
||||
config={result.config} />
|
||||
{:catch}
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
|
||||
export let project: proj.Project;
|
||||
export let config: Config;
|
||||
export let org: string | undefined;
|
||||
export let org: string | undefined = undefined;
|
||||
export let user: string | undefined = undefined;
|
||||
|
||||
let state: State = { status: Status.Loading };
|
||||
let info: proj.Info | null = null;
|
||||
@@ -36,6 +37,7 @@
|
||||
proj.path({
|
||||
urn: project.id,
|
||||
org,
|
||||
user,
|
||||
commit: project.anchor.stateHash,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -86,6 +86,9 @@
|
||||
{ name: "seed.id", label: "Seed ID", validate: "id", placeholder: "hynkyndc6w3p8urucakobzncqny7xxtw88...",
|
||||
description: "The Device ID of a Radicle Link node that hosts entities associated with this name.",
|
||||
value: r.profile.seedId, editable: true },
|
||||
{ name: "project.anchors", label: "Anchors", validate: "address", placeholder: "Ethereum address, eg. 0x4a9cf21...bc91...",
|
||||
description: "The alternative Ethereum address under which associated project anchors can be found.",
|
||||
value: r.profile.projectAnchors, editable: true },
|
||||
];
|
||||
state = { status: Status.Found, registration: r, owner };
|
||||
} else {
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface EnsProfile {
|
||||
address: string | null;
|
||||
seedId: string | null;
|
||||
seedHost: string | null;
|
||||
projectAnchors: string | null;
|
||||
url: string | null;
|
||||
avatar: string | null;
|
||||
twitter: string | null;
|
||||
@@ -74,11 +75,12 @@ export async function getRegistration(name: string, config: Config, resolver?: E
|
||||
resolver.getText('url'),
|
||||
resolver.getText('eth.radicle.seed.id'),
|
||||
resolver.getText('eth.radicle.seed.host'),
|
||||
resolver.getText('eth.radicle.project.anchors'),
|
||||
resolver.getText('com.twitter'),
|
||||
resolver.getText('com.github'),
|
||||
]);
|
||||
|
||||
const [address, avatar, url, seedId, seedHost, twitter, github] =
|
||||
const [address, avatar, url, seedId, seedHost, projectAnchors, twitter, github] =
|
||||
meta.map(r => r.status == "fulfilled" ? r.value : null);
|
||||
|
||||
return {
|
||||
@@ -89,6 +91,7 @@ export async function getRegistration(name: string, config: Config, resolver?: E
|
||||
avatar,
|
||||
seedId,
|
||||
seedHost,
|
||||
projectAnchors,
|
||||
address,
|
||||
twitter,
|
||||
github,
|
||||
@@ -126,6 +129,16 @@ export async function getSeedId(name: string, config: Config, resolver?: EnsReso
|
||||
return resolver.getText('eth.radicle.seed.id');
|
||||
}
|
||||
|
||||
export async function getProjectAnchorsAddress(name: string, config: Config, resolver?: EnsResolver): Promise<string | null> {
|
||||
name = name.toLowerCase();
|
||||
|
||||
resolver = resolver ?? await config.provider.getResolver(name);
|
||||
if (! resolver) {
|
||||
return null;
|
||||
}
|
||||
return resolver.getText('eth.radicle.project.anchors');
|
||||
}
|
||||
|
||||
export function registrar(config: Config): ethers.Contract {
|
||||
return new ethers.Contract(config.registrar.address, config.abi.registrar, config.provider);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ export async function setRecords(name: string, records: EnsRecord[], resolver: E
|
||||
break;
|
||||
case "seed.id":
|
||||
case "seed.host":
|
||||
case "project.anchors":
|
||||
calls.push(
|
||||
iface.encodeFunctionData("setText", [node, "eth.radicle." + r.name, r.value])
|
||||
);
|
||||
|
||||
+26
-18
@@ -104,8 +104,14 @@
|
||||
<div class="fields">
|
||||
<!-- Address -->
|
||||
<div class="label">Address</div>
|
||||
<div><Address noAvatar {config} address={profile.address} /></div>
|
||||
<div><Address {config} address={profile.address} /></div>
|
||||
<div></div>
|
||||
<!-- Project anchors -->
|
||||
{#if profile.projectAnchors}
|
||||
<div class="label">Anchors</div>
|
||||
<div><Address {config} address={profile.projectAnchors} /></div>
|
||||
<div></div>
|
||||
{/if}
|
||||
<!-- Profile -->
|
||||
<div class="label">Profile</div>
|
||||
<div>
|
||||
@@ -117,23 +123,25 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="projects">
|
||||
{#await Org.getOrgsByOwner(profile.address, config)}
|
||||
<Loading center fadeIn />
|
||||
{:then orgs}
|
||||
{#each orgs as org}
|
||||
{#await org.getProjects(config) then projects}
|
||||
{#each projects as project}
|
||||
<div class="project">
|
||||
<Project {project} org={org.address} config={profile.config(config)} />
|
||||
</div>
|
||||
{/each}
|
||||
{:catch err}
|
||||
<Message error>
|
||||
<strong>Error: </strong> failed to load projects: {err.message}.
|
||||
</Message>
|
||||
{/await}
|
||||
{/each}
|
||||
{/await}
|
||||
{#if profile.projectAnchors}
|
||||
{#await Org.get(profile.projectAnchors, config)}
|
||||
<Loading center fadeIn />
|
||||
{:then org}
|
||||
{#if org}
|
||||
{#await org.getProjects(config) then projects}
|
||||
{#each projects as project}
|
||||
<div class="project">
|
||||
<Project {project} user={addressOrName} config={profile.config(config)} />
|
||||
</div>
|
||||
{/each}
|
||||
{:catch err}
|
||||
<Message error>
|
||||
<strong>Error: </strong> failed to load projects: {err.message}.
|
||||
</Message>
|
||||
{/await}
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
{/await}
|
||||
|
||||
@@ -84,6 +84,11 @@ export class Profile {
|
||||
return this.profile?.ens?.seedId ?? undefined;
|
||||
}
|
||||
|
||||
// Using undefined as return type if nothing to be returned since it works better with <a href> links
|
||||
get projectAnchors(): string | undefined {
|
||||
return this.profile?.ens?.projectAnchors ?? undefined;
|
||||
}
|
||||
|
||||
// Get the name, and if not available, the address.
|
||||
get nameOrAddress(): string {
|
||||
return this.name ?? this.address;
|
||||
|
||||
+4
-2
@@ -107,13 +107,15 @@ export async function getReadme(
|
||||
}
|
||||
|
||||
export function path(
|
||||
opts: { urn: string; org?: string; commit?: string; path?: string }
|
||||
opts: { urn: string; org?: string; user?: string; commit?: string; path?: string }
|
||||
): string {
|
||||
const { urn, org, commit, path } = opts;
|
||||
const { urn, org, user, commit, path } = opts;
|
||||
const result = [];
|
||||
|
||||
if (org) {
|
||||
result.push("orgs", org);
|
||||
} else if (user) {
|
||||
result.push("users", user);
|
||||
}
|
||||
result.push("projects", urn);
|
||||
|
||||
|
||||
+4
-2
@@ -7,7 +7,7 @@ import type { Config } from '@app/config';
|
||||
import config from "@app/config.json";
|
||||
import { assert } from '@app/error';
|
||||
import type { EnsProfile } from "@app/base/registrations/registrar";
|
||||
import { getAvatar, getSeedHost, getSeedId, getRegistration } from '@app/base/registrations/registrar';
|
||||
import { getAvatar, getSeedHost, getSeedId, getProjectAnchorsAddress, getRegistration } from '@app/base/registrations/registrar';
|
||||
import type { BasicProfile } from "@ceramicstudio/idx-constants";
|
||||
import { ProfileType } from '@app/profile';
|
||||
|
||||
@@ -301,12 +301,13 @@ export async function resolveEnsProfile(addressOrName: string, profileType: Prof
|
||||
if (profileType === ProfileType.Project) {
|
||||
promises.push(getSeedHost(name, config, resolver));
|
||||
promises.push(getSeedId(name, config, resolver));
|
||||
promises.push(getProjectAnchorsAddress(name, config, resolver));
|
||||
} else if (profileType === ProfileType.Minimal) {
|
||||
promises.push(Promise.resolve(null));
|
||||
}
|
||||
|
||||
const project = await Promise.allSettled(promises);
|
||||
const [avatar, address, seedHost, seedId] =
|
||||
const [avatar, address, seedHost, seedId, projectAnchors] =
|
||||
project.map(r => r.status == "fulfilled" ? r.value : null);
|
||||
|
||||
return {
|
||||
@@ -315,6 +316,7 @@ export async function resolveEnsProfile(addressOrName: string, profileType: Prof
|
||||
address,
|
||||
seedHost,
|
||||
seedId,
|
||||
projectAnchors,
|
||||
url: null,
|
||||
twitter: null,
|
||||
github: null,
|
||||
|
||||
Reference in New Issue
Block a user