mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-10 07:10:35 +08:00
Remove deprecated org parts
Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
@@ -6,8 +6,6 @@
|
||||
import Home from "@app/base/home/Index.svelte";
|
||||
import Vesting from "@app/base/vesting/Index.svelte";
|
||||
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";
|
||||
@@ -92,10 +90,8 @@
|
||||
<Vesting {config} session={$session} />
|
||||
</Route>
|
||||
<Registrations {config} session={$session} />
|
||||
<Orgs />
|
||||
<Seeds {config} session={$session} />
|
||||
<Faucet {config} />
|
||||
<Users />
|
||||
<Resolver {config} />
|
||||
<Route path="/:addressOrName" let:params>
|
||||
<Profile addressOrName={params.addressOrName} {config} />
|
||||
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Link } from "svelte-routing";
|
||||
import Avatar from "@app/Avatar.svelte";
|
||||
import type { Profile } from "@app/profile";
|
||||
import type { Config } from "@app/config";
|
||||
import { formatName, formatAddress } from "@app/utils";
|
||||
import type { Seed } from "@app/base/seeds/Seed";
|
||||
|
||||
export let profile:
|
||||
| Profile
|
||||
| {
|
||||
address: string;
|
||||
avatar?: string;
|
||||
name?: string;
|
||||
}
|
||||
| null = null;
|
||||
export let seed: Seed | null = null;
|
||||
export let config: Config;
|
||||
export let path: string;
|
||||
export let members: string[] = [];
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.card {
|
||||
width: 12rem;
|
||||
height: 10.5rem;
|
||||
margin-right: 1.5rem;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
.card::last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
.card:hover {
|
||||
background: var(--color-foreground-background-lighter);
|
||||
}
|
||||
.card-avatar {
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
}
|
||||
.card-label {
|
||||
color: var(--color-foreground-90);
|
||||
display: inline-block;
|
||||
font-weight: var(--font-weight-medium);
|
||||
margin-top: 0.75rem;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 0rem;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.card-members {
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--color-foreground-faded);
|
||||
}
|
||||
|
||||
.card.seed {
|
||||
width: 14rem;
|
||||
}
|
||||
.card.seed .card-label {
|
||||
max-width: 12rem;
|
||||
}
|
||||
.card.seed .seed-emoji {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.card {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<Link to={path}>
|
||||
<div class="card" class:seed={!!seed}>
|
||||
<div class="card-avatar">
|
||||
{#if profile}
|
||||
<Avatar
|
||||
source={profile.avatar ?? profile.address}
|
||||
title={profile.address} />
|
||||
{:else if seed}
|
||||
<span class="seed-emoji">
|
||||
{seed.emoji}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="card-label">
|
||||
{#if profile}
|
||||
{#if profile.name}
|
||||
{formatName(profile.name, config)}
|
||||
{:else}
|
||||
{formatAddress(profile.address)}
|
||||
{/if}
|
||||
{:else if seed}
|
||||
{seed.host}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="card-members">
|
||||
{#if profile}
|
||||
{#if members.length > 0}
|
||||
{members.length} member(s)
|
||||
{:else}
|
||||
{formatAddress(profile.address)}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
@@ -1,71 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { Profile, ProfileType } from "@app/profile";
|
||||
import Card from "@app/Card.svelte";
|
||||
import type { Org } from "@app/base/orgs/Org";
|
||||
import type { Config } from "@app/config";
|
||||
import type { Seed } from "@app/base/seeds/Seed";
|
||||
|
||||
export let config: Config;
|
||||
export let orgs: Org[] = [];
|
||||
export let profiles: Profile[] = [];
|
||||
export let seeds: Seed[] = [];
|
||||
|
||||
const orgMembers: Record<string, string[]> = {};
|
||||
|
||||
onMount(async () => {
|
||||
const promises = orgs.map(org => {
|
||||
org.getMembers(config).then(members => {
|
||||
orgMembers[org.address] = members;
|
||||
});
|
||||
});
|
||||
|
||||
Promise.all(promises);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.list {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="list">
|
||||
{#each orgs as org}
|
||||
{#await Profile.get(org.name ?? org.address, ProfileType.Minimal, config)}
|
||||
<Card
|
||||
profile={{ address: org.address }}
|
||||
{config}
|
||||
path={`/${org.address}`} />
|
||||
{:then profile}
|
||||
{#if orgMembers[profile.address]?.length}
|
||||
<Card
|
||||
{profile}
|
||||
{config}
|
||||
path={`/${profile.nameOrAddress}`}
|
||||
members={orgMembers[profile.address]} />
|
||||
{:else}
|
||||
<Card {profile} {config} path={`/${profile.nameOrAddress}`} />
|
||||
{/if}
|
||||
{/await}
|
||||
{/each}
|
||||
|
||||
{#each profiles as profile}
|
||||
<Card {profile} {config} path={`/${profile.nameOrAddress}`} />
|
||||
{/each}
|
||||
|
||||
{#each seeds as seed}
|
||||
<Card {seed} {config} path={`/seeds/${seed.host}`} />
|
||||
{/each}
|
||||
|
||||
{#if !orgs.length && !profiles.length && !seeds.length}
|
||||
<slot />
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,92 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { marked } from "marked";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { markdownExtensions as extensions } from "@app/utils";
|
||||
|
||||
export let options: {
|
||||
label: string;
|
||||
value: string;
|
||||
description?: string[];
|
||||
}[];
|
||||
export let name: string;
|
||||
export let selected = "";
|
||||
export let disabled = false;
|
||||
|
||||
marked.use({ extensions });
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
main {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.options {
|
||||
text-align: left;
|
||||
}
|
||||
.options label {
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
.options label:last-child {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.options label,
|
||||
.options input {
|
||||
cursor: pointer;
|
||||
}
|
||||
.options input {
|
||||
margin-right: 0.75rem;
|
||||
display: inline-block;
|
||||
}
|
||||
.options .description {
|
||||
color: var(--color-secondary);
|
||||
font-size: var(--font-size-small);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.options .description:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.description :global(a) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.description :global(em) {
|
||||
font-style: italic;
|
||||
}
|
||||
.description :global(strong) {
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
.desktop {
|
||||
display: block !important;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.desktop {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<main>
|
||||
<div class="options">
|
||||
{#each options as option}
|
||||
<label for={option.value}>
|
||||
<input
|
||||
type="radio"
|
||||
{disabled}
|
||||
checked={selected === option.value}
|
||||
{name}
|
||||
id={option.value}
|
||||
value={option.value}
|
||||
on:click={() => dispatch("changed", option.value)} />
|
||||
{option.label}
|
||||
</label>
|
||||
{#if option.description}
|
||||
<div class="description desktop">
|
||||
{@html marked(option.description.join("\n"))}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</main>
|
||||
+3
-8
@@ -6,7 +6,7 @@
|
||||
import Icon from "@app/Icon.svelte";
|
||||
import SetName from "@app/ens/SetName.svelte";
|
||||
import SeedAddress from "@app/SeedAddress.svelte";
|
||||
import TransferOwnership from "@app/base/orgs/TransferOwnership.svelte";
|
||||
import TransferOwnership from "@app/components/TransferOwnership.svelte";
|
||||
import Link from "@app/Link.svelte";
|
||||
import { getBalance, Profile, ProfileType } from "@app/profile";
|
||||
import Loading from "@app/Loading.svelte";
|
||||
@@ -16,7 +16,7 @@
|
||||
import Message from "@app/Message.svelte";
|
||||
import Error from "@app/Error.svelte";
|
||||
import { User } from "@app/base/users/User";
|
||||
import Projects from "@app/base/orgs/View/Projects.svelte";
|
||||
import Projects from "@app/base/seeds/View/Projects.svelte";
|
||||
import { MissingReverseRecord, NotFoundError } from "@app/error";
|
||||
import NotFound from "@app/NotFound.svelte";
|
||||
import RadicleUrn from "@app/RadicleUrn.svelte";
|
||||
@@ -448,12 +448,7 @@
|
||||
{/if}
|
||||
{#if profile.seed?.valid}
|
||||
<Async fetch={profile.seed.getProjects(10, profile.id)} let:result>
|
||||
<Projects
|
||||
{profile}
|
||||
seed={profile.seed}
|
||||
{account}
|
||||
projects={result}
|
||||
{config} />
|
||||
<Projects {profile} seed={profile.seed} projects={result} />
|
||||
</Async>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { ethers } from "ethers";
|
||||
import type { Config } from "./config";
|
||||
import { decodeRadicleId, formatProjectHash, querySubgraph } from "@app/utils";
|
||||
|
||||
const GetAllAnchors = `
|
||||
query GetAllAnchors($project: Bytes!, $org: ID!) {
|
||||
anchors(orderBy: timestamp, orderDirection: desc, where: { objectId: $project, org: $org }) {
|
||||
multihash
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface AnchorObject {
|
||||
timestamp: number;
|
||||
multihash: string;
|
||||
}
|
||||
|
||||
export async function getProjectAnchors(
|
||||
urn: string,
|
||||
anchorsStorage: string,
|
||||
config: Config,
|
||||
): Promise<string[]> {
|
||||
const unpadded = decodeRadicleId(urn);
|
||||
const id = ethers.utils.hexZeroPad(unpadded, 32);
|
||||
const allAnchors = await querySubgraph(config.orgs.subgraph, GetAllAnchors, {
|
||||
project: id,
|
||||
org: anchorsStorage,
|
||||
});
|
||||
|
||||
return allAnchors.anchors.map((anchor: AnchorObject) =>
|
||||
formatProjectHash(ethers.utils.arrayify(anchor.multihash)),
|
||||
);
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { navigate } from "svelte-routing";
|
||||
import Modal from "@app/Modal.svelte";
|
||||
import Error from "@app/Error.svelte";
|
||||
import type { Err } from "@app/error";
|
||||
import { Org } from "@app/base/orgs/Org";
|
||||
import type { Config } from "@app/config";
|
||||
import Loading from "@app/Loading.svelte";
|
||||
import Options from "@app/Options.svelte";
|
||||
import Address from "@app/Address.svelte";
|
||||
import Button from "@app/Button.svelte";
|
||||
|
||||
export let config: Config;
|
||||
export let owner: string;
|
||||
|
||||
enum State {
|
||||
Idle,
|
||||
Signing,
|
||||
Pending,
|
||||
Success,
|
||||
}
|
||||
|
||||
enum Governance {
|
||||
Existing = "existing",
|
||||
Quorum = "quorum",
|
||||
}
|
||||
|
||||
const orgTypes = [
|
||||
{
|
||||
label: "Multi-signature",
|
||||
description: [
|
||||
"Creates an org with a multi-signature contract as its owner, and the specified account as the first member.",
|
||||
"A [Gnosis Safe](https://gnosis-safe.io) will be deployed for your org.",
|
||||
"Transactions such as anchoring have to be approved by a quorum of signers.",
|
||||
],
|
||||
value: Governance.Quorum,
|
||||
},
|
||||
{
|
||||
label: "Existing owner",
|
||||
description: [
|
||||
`Creates an org with the specified account as the sole owner.`,
|
||||
`Org transactions such as anchoring are signed and executed from that account.`,
|
||||
`This option allows for using an existing contract or EOA as the owner of the org.`,
|
||||
],
|
||||
value: Governance.Existing,
|
||||
},
|
||||
];
|
||||
|
||||
let state = State.Idle;
|
||||
let error: Err | null = null;
|
||||
let org: Org | null = null;
|
||||
let governance = Governance.Quorum;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const createOrg = async () => {
|
||||
state = State.Signing;
|
||||
|
||||
try {
|
||||
const tx =
|
||||
governance === Governance.Quorum
|
||||
? await Org.createMultiSig([owner], 1, config)
|
||||
: await Org.create(owner, config);
|
||||
|
||||
state = State.Pending;
|
||||
|
||||
const receipt = await tx.wait();
|
||||
org = Org.fromReceipt(receipt);
|
||||
state = State.Success;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
|
||||
state = State.Idle;
|
||||
error = e;
|
||||
}
|
||||
};
|
||||
|
||||
const onGovernanceChanged = (event: { detail: string }) => {
|
||||
switch (event.detail) {
|
||||
case "existing":
|
||||
governance = Governance.Existing;
|
||||
break;
|
||||
case "quorum":
|
||||
governance = Governance.Quorum;
|
||||
break;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.fields {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 7fr;
|
||||
grid-gap: 0.5rem 2rem;
|
||||
}
|
||||
.fields > span {
|
||||
justify-self: start;
|
||||
align-self: center;
|
||||
height: 2rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
.configuration {
|
||||
max-width: 35rem;
|
||||
text-align: left;
|
||||
}
|
||||
.governance {
|
||||
background-color: var(--color-secondary-background);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1rem 1rem 0rem 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
label[for="address"] {
|
||||
margin-left: 1.5rem;
|
||||
font-size: var(--font-size-tiny);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
input[name="address"] {
|
||||
margin: 0.5rem 0 0 0;
|
||||
width: 100%;
|
||||
}
|
||||
.notice {
|
||||
font-size: var(--font-size-small);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-yellow);
|
||||
background-color: var(--color-yellow-background);
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if error}
|
||||
<Error {error} floating on:close />
|
||||
{:else if org}
|
||||
<!-- Org created -->
|
||||
<Modal floating on:close>
|
||||
<span slot="title">🎉</span>
|
||||
|
||||
<span slot="subtitle">
|
||||
<span class="txt-bold">Your org was successfully created.</span>
|
||||
</span>
|
||||
|
||||
<span slot="body">
|
||||
<div class="fields">
|
||||
<span class="label">Address</span>
|
||||
<span><Address address={org.address} {config} /></span>
|
||||
|
||||
<span class="label">Owner</span>
|
||||
<span><Address resolve address={org.owner} {config} /></span>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<span slot="actions">
|
||||
<Button
|
||||
variant="foreground"
|
||||
on:click={() => navigate(`/${org?.address}`)}>
|
||||
Done
|
||||
</Button>
|
||||
</span>
|
||||
</Modal>
|
||||
{:else}
|
||||
<!-- Org creation flow -->
|
||||
<Modal floating on:close center>
|
||||
<span slot="title">
|
||||
<div>🎪</div>
|
||||
<span>Create an Org</span>
|
||||
</span>
|
||||
|
||||
<span slot="subtitle">
|
||||
{#if state === State.Idle}
|
||||
<div class="highlight">Select how you'd like to create your org</div>
|
||||
{:else if state === State.Signing}
|
||||
<div class="highlight">
|
||||
Please confirm the transaction in your wallet.
|
||||
</div>
|
||||
{:else if state === State.Pending}
|
||||
<div class="highlight">Waiting for transaction to be processed...</div>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span slot="body">
|
||||
{#if state === State.Idle}
|
||||
<div class="configuration">
|
||||
<div class="notice">
|
||||
<span class="txt-bold">Notice:</span>
|
||||
Orgs V1 are being deprecated. It is recommended not to create new orgs
|
||||
at this point.
|
||||
</div>
|
||||
|
||||
<div class="governance">
|
||||
<Options
|
||||
name="governance"
|
||||
disabled={state !== State.Idle}
|
||||
selected={governance}
|
||||
options={orgTypes}
|
||||
on:changed={onGovernanceChanged} />
|
||||
</div>
|
||||
|
||||
<label class="input" for="address">Ethereum account address</label>
|
||||
<input
|
||||
name="address"
|
||||
class="small"
|
||||
type="text"
|
||||
maxlength="42"
|
||||
bind:value={owner} />
|
||||
</div>
|
||||
{:else}
|
||||
<Loading center small />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span slot="actions">
|
||||
{#if state === State.Idle}
|
||||
<Button
|
||||
on:click={createOrg}
|
||||
variant="primary"
|
||||
waiting={[State.Signing, State.Pending].includes(state)}
|
||||
disabled={state !== State.Idle}>
|
||||
Create
|
||||
</Button>
|
||||
|
||||
<Button on:click={() => dispatch("close")} variant="text">Close</Button>
|
||||
{/if}
|
||||
</span>
|
||||
</Modal>
|
||||
{/if}
|
||||
@@ -1,122 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { SvelteComponent } from "svelte";
|
||||
import { session } from "@app/session";
|
||||
import Create from "@app/base/orgs/Create.svelte";
|
||||
import { Org } from "@app/base/orgs/Org";
|
||||
import type { Config } from "@app/config";
|
||||
import Loading from "@app/Loading.svelte";
|
||||
import Message from "@app/Message.svelte";
|
||||
import Cards from "@app/Cards.svelte";
|
||||
import { setOpenGraphMetaTag } from "@app/utils";
|
||||
import Button from "@app/Button.svelte";
|
||||
|
||||
export let config: Config;
|
||||
|
||||
setOpenGraphMetaTag([
|
||||
{ prop: "og:title", content: "Radicle Orgs" },
|
||||
{ prop: "og:description", content: "Orgs of the Radicle Network" },
|
||||
{ prop: "og:url", content: window.location.href },
|
||||
]);
|
||||
|
||||
const onCreate = () => (modal = Create);
|
||||
let modal: typeof SvelteComponent | null = null;
|
||||
|
||||
$: account = $session && $session.address;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
main {
|
||||
width: 100%;
|
||||
padding: 3rem;
|
||||
display: block;
|
||||
align-items: space-between;
|
||||
justify-content: space-between;
|
||||
}
|
||||
main header {
|
||||
color: var(--color-secondary);
|
||||
font-size: var(--font-size-medium);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1rem 0;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.my-orgs {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.orgs-empty {
|
||||
padding: 2rem 0 1rem 0;
|
||||
}
|
||||
|
||||
.loading {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<svelte:head>
|
||||
<title>Radicle: Orgs</title>
|
||||
</svelte:head>
|
||||
|
||||
<main>
|
||||
{#if account}
|
||||
<div class="my-orgs">
|
||||
<header>
|
||||
<span>
|
||||
My <span class="txt-bold">Orgs</span>
|
||||
</span>
|
||||
<Button variant="secondary" on:click={onCreate} disabled={!account}>
|
||||
Create
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
{#await Org.getOrgsByMember(account, config)}
|
||||
<div class="loading">
|
||||
<Loading center />
|
||||
</div>
|
||||
{:then orgs}
|
||||
<Cards {config} {orgs}>
|
||||
<div class="orgs-empty txt-missing">
|
||||
Orgs you are a member of show up here.
|
||||
</div>
|
||||
</Cards>
|
||||
{:catch}
|
||||
<div>
|
||||
<Message error>
|
||||
<span class="txt-bold">Error:</span>
|
||||
failed to load orgs.
|
||||
</Message>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<header>
|
||||
<span>
|
||||
<span class="txt-bold">Orgs</span>
|
||||
of the Radicle network
|
||||
</span>
|
||||
</header>
|
||||
|
||||
{#await Org.getAll(config)}
|
||||
<div class="loading">
|
||||
<Loading center />
|
||||
</div>
|
||||
{:then orgs}
|
||||
<Cards {config} {orgs}>
|
||||
<div class="orgs-empty">There are no orgs.</div>
|
||||
</Cards>
|
||||
{:catch}
|
||||
<div>
|
||||
<Message error>
|
||||
<span class="txt-bold">Error:</span>
|
||||
failed to load orgs.
|
||||
</Message>
|
||||
</div>
|
||||
{/await}
|
||||
</main>
|
||||
|
||||
<svelte:component
|
||||
this={modal}
|
||||
owner={account}
|
||||
{config}
|
||||
on:close={() => (modal = null)} />
|
||||
@@ -1,37 +1,12 @@
|
||||
import * as ethers from "ethers";
|
||||
import type { TransactionResponse } from "@ethersproject/providers";
|
||||
import type { ContractReceipt } from "@ethersproject/contracts";
|
||||
import { OperationType } from "@gnosis.pm/safe-core-sdk-types";
|
||||
|
||||
import { assert } from "@app/error";
|
||||
import * as utils from "@app/utils";
|
||||
import * as cache from "@app/cache";
|
||||
import type { SafeMultisigTransactionListResponse } from "@gnosis.pm/safe-service-client";
|
||||
import type SafeServiceClient from "@gnosis.pm/safe-service-client";
|
||||
import type { Safe } from "@app/utils";
|
||||
import type { Config } from "@app/config";
|
||||
import type { PendingAnchor, Anchor } from "@app/project";
|
||||
|
||||
const GetProjects = `
|
||||
query GetProjects($org: ID!) {
|
||||
projects(where: { org: $org }) {
|
||||
anchor {
|
||||
objectId
|
||||
multihash
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const GetOrgs = `
|
||||
query GetOrgs {
|
||||
orgs {
|
||||
id
|
||||
owner
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const GetSafesByOwners = `
|
||||
query GetSafesByOwners($owners: [String!]!) {
|
||||
@@ -198,119 +173,6 @@ export class Org {
|
||||
return members.includes(address.toLowerCase());
|
||||
}
|
||||
|
||||
async getProjects(config: Config): Promise<Anchor[]> {
|
||||
const result = await utils.querySubgraph(
|
||||
config.orgs.subgraph,
|
||||
GetProjects,
|
||||
{ org: this.address },
|
||||
);
|
||||
const projects: Anchor[] = [];
|
||||
|
||||
for (const p of result.projects) {
|
||||
try {
|
||||
const proj: Anchor = {
|
||||
confirmed: true,
|
||||
id: utils.formatRadicleId(ethers.utils.arrayify(p.anchor.objectId)),
|
||||
anchor: {
|
||||
stateHash: utils.formatProjectHash(
|
||||
ethers.utils.arrayify(p.anchor.multihash),
|
||||
),
|
||||
},
|
||||
};
|
||||
projects.push(proj);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
return projects;
|
||||
}
|
||||
|
||||
async getPendingProjects(config: Config): Promise<PendingAnchor[]> {
|
||||
if (!config.safe.client) return [];
|
||||
|
||||
try {
|
||||
const orgAddr = ethers.utils.getAddress(this.address);
|
||||
const response = await getPendingProjects(
|
||||
ethers.utils.getAddress(this.owner),
|
||||
config.safe.client,
|
||||
);
|
||||
const projects: PendingAnchor[] = [];
|
||||
|
||||
for (const tx of response.results || []) {
|
||||
if (tx.data && tx.to === orgAddr) {
|
||||
const anchor = parseAnchorTx(tx.data, config);
|
||||
const confirmations = tx.confirmations?.map(t => t.owner) || [];
|
||||
|
||||
if (anchor) {
|
||||
projects.push({
|
||||
id: anchor.id,
|
||||
anchor: { stateHash: anchor.stateHash },
|
||||
confirmations,
|
||||
safeTxHash: tx.safeTxHash,
|
||||
confirmed: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return projects;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static async getAnchor(
|
||||
orgAddr: string,
|
||||
urn: string,
|
||||
config: Config,
|
||||
): Promise<string | null> {
|
||||
const org = new ethers.Contract(orgAddr, config.abi.org, config.provider);
|
||||
const unpadded = utils.decodeRadicleId(urn);
|
||||
const id = ethers.utils.zeroPad(unpadded, 32);
|
||||
|
||||
try {
|
||||
const [, hash] = await org.anchors(id);
|
||||
const anchor = utils.formatProjectHash(ethers.utils.arrayify(hash));
|
||||
|
||||
return anchor;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static async getMulti(ids: string[], config: Config): Promise<Array<Org>> {
|
||||
const results = await Promise.all(ids.map(addr => Org.get(addr, config)));
|
||||
const orgs = results.filter((org): org is Org => org !== null);
|
||||
|
||||
return orgs;
|
||||
}
|
||||
|
||||
static async getAll(config: Config): Promise<Array<Org>> {
|
||||
const result = await utils.querySubgraph(config.orgs.subgraph, GetOrgs, {});
|
||||
const orgs: Org[] = [];
|
||||
|
||||
for (const o of result.orgs) {
|
||||
try {
|
||||
orgs.push(new Org(o.id, o.owner));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
return orgs;
|
||||
}
|
||||
|
||||
static fromReceipt(receipt: ContractReceipt): Org | null {
|
||||
const event = receipt.events?.find(e => e.event === "OrgCreated");
|
||||
|
||||
if (event && event.args) {
|
||||
const address = event.args[0];
|
||||
const owner = event.args[1];
|
||||
|
||||
return new Org(address, owner);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static async get(addressOrName: string, config: Config): Promise<Org | null> {
|
||||
const org = await getOrgContract(addressOrName, config);
|
||||
|
||||
@@ -354,62 +216,6 @@ export class Org {
|
||||
|
||||
return orgs.map(o => new Org(o.id, o.owner));
|
||||
}
|
||||
|
||||
static async createMultiSig(
|
||||
owners: [string],
|
||||
threshold: number,
|
||||
config: Config,
|
||||
): Promise<TransactionResponse> {
|
||||
assert(config.signer);
|
||||
|
||||
const orgFactory = new ethers.Contract(
|
||||
config.orgFactory.address,
|
||||
config.abi.orgFactory,
|
||||
config.signer,
|
||||
);
|
||||
|
||||
return orgFactory["createOrg(address[],uint256)"](owners, threshold, {
|
||||
gasLimit: config.gasLimits.createOrg,
|
||||
});
|
||||
}
|
||||
|
||||
static async create(
|
||||
owner: string,
|
||||
config: Config,
|
||||
): Promise<TransactionResponse> {
|
||||
assert(config.signer);
|
||||
|
||||
const orgFactory = new ethers.Contract(
|
||||
config.orgFactory.address,
|
||||
config.abi.orgFactory,
|
||||
config.signer,
|
||||
);
|
||||
|
||||
return orgFactory["createOrg(address)"](owner, {
|
||||
gasLimit: config.gasLimits.createOrg,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function parseAnchorTx(
|
||||
data: string,
|
||||
config: Config,
|
||||
): { id: string; stateHash: string } | null {
|
||||
const iface = new ethers.utils.Interface(config.abi.org);
|
||||
const parsedTx = iface.parseTransaction({ data });
|
||||
|
||||
if (parsedTx.name === "anchor") {
|
||||
const encodedProjectUrn = parsedTx.args[0];
|
||||
const encodedCommitHash = parsedTx.args[2];
|
||||
const id = utils.formatRadicleId(
|
||||
ethers.utils.arrayify(`${encodedProjectUrn}`),
|
||||
);
|
||||
const byteArray = ethers.utils.arrayify(encodedCommitHash);
|
||||
const stateHash = utils.formatProjectHash(byteArray);
|
||||
|
||||
return { id, stateHash };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const getOrgContract = cache.cached(
|
||||
@@ -425,20 +231,3 @@ export const resolveOrgOwner = cache.cached(
|
||||
},
|
||||
org => org.address,
|
||||
);
|
||||
|
||||
export const getPendingProjects = cache.cached(
|
||||
async (
|
||||
owner: string,
|
||||
client: SafeServiceClient,
|
||||
): Promise<SafeMultisigTransactionListResponse> => {
|
||||
try {
|
||||
return await client.getPendingTransactions(
|
||||
ethers.utils.getAddress(owner),
|
||||
);
|
||||
} catch (e) {
|
||||
return { count: 0, results: [] };
|
||||
}
|
||||
},
|
||||
owner => owner,
|
||||
{ max: 1000, ttl: 5 * 60 * 1000 }, // Cache results for 5 minutes.
|
||||
);
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Route } from "svelte-routing";
|
||||
import Redirect from "@app/Redirect.svelte";
|
||||
import NotFound from "@app/NotFound.svelte";
|
||||
</script>
|
||||
|
||||
<Route path="/orgs">
|
||||
<NotFound
|
||||
title="404"
|
||||
subtitle="Radicle Orgs are in the process of being re-designed." />
|
||||
</Route>
|
||||
|
||||
<Route path="/orgs/:addressOrName" let:params>
|
||||
<Redirect to="/{params.addressOrName}" />
|
||||
</Route>
|
||||
@@ -1,267 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ethers } from "ethers";
|
||||
import type { Safe } from "@app/utils";
|
||||
import type { PendingAnchor } from "@app/project";
|
||||
import type { Config } from "@app/config";
|
||||
import * as utils from "@app/utils";
|
||||
import Modal from "@app/Modal.svelte";
|
||||
import Avatar from "@app/Avatar.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import Badge from "@app/Badge.svelte";
|
||||
import Button from "@app/Button.svelte";
|
||||
|
||||
export let safe: Safe;
|
||||
export let anchor: PendingAnchor;
|
||||
export let account: string;
|
||||
export let config: Config;
|
||||
|
||||
enum State {
|
||||
Idle,
|
||||
Confirm,
|
||||
Signing,
|
||||
Submitting,
|
||||
Success,
|
||||
Execute,
|
||||
Failed,
|
||||
}
|
||||
|
||||
enum Action {
|
||||
Sign,
|
||||
Execute,
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let state = State.Idle;
|
||||
let error: string | null = null;
|
||||
let action: null | Action = null;
|
||||
|
||||
const close = () => {
|
||||
action = null;
|
||||
state = State.Idle;
|
||||
};
|
||||
|
||||
const pending = safe.threshold - anchor.confirmations.length;
|
||||
const executeTransaction = async (safeTxHash: string) => {
|
||||
try {
|
||||
action = Action.Execute;
|
||||
state = State.Signing;
|
||||
const txResult = await utils.executeSignedSafeTransaction(
|
||||
safe.address,
|
||||
safeTxHash,
|
||||
config,
|
||||
);
|
||||
|
||||
state = State.Submitting;
|
||||
await txResult.transactionResponse?.wait();
|
||||
|
||||
state = State.Success;
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
error = err.message;
|
||||
state = State.Failed;
|
||||
}
|
||||
};
|
||||
|
||||
const confirmAnchor = async (safeTxHash: string) => {
|
||||
try {
|
||||
action = Action.Sign;
|
||||
state = State.Signing;
|
||||
const signature = await utils.signSafeTransaction(
|
||||
safe.address,
|
||||
safeTxHash,
|
||||
config,
|
||||
);
|
||||
|
||||
state = State.Submitting;
|
||||
await config.safe.client?.confirmTransaction(safeTxHash, signature.data);
|
||||
|
||||
state = State.Success;
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
error = err.message;
|
||||
state = State.Failed;
|
||||
}
|
||||
};
|
||||
|
||||
$: isSigned = anchor.confirmations.includes(ethers.utils.getAddress(account));
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.confirmations {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.table {
|
||||
display: grid;
|
||||
grid-template-columns: 5rem 4fr;
|
||||
grid-gap: 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
.table > *:nth-child(odd) {
|
||||
/* Labels */
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
.table > *:nth-child(even) {
|
||||
/* Values */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
}
|
||||
.avatars {
|
||||
display: flex;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<span class="confirmations">
|
||||
{#if pending > 0}
|
||||
<span class="txt-bold">{pending}</span>
|
||||
signature(s) pending
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<div class="avatars">
|
||||
{#each anchor.confirmations as signee}
|
||||
<Avatar inline source={signee} title={signee} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Check whether the threshold has been matched or passed -->
|
||||
{#if pending <= 0}
|
||||
<Button
|
||||
on:click={() => {
|
||||
action = Action.Execute;
|
||||
state = State.Confirm;
|
||||
}}
|
||||
variant="foreground"
|
||||
size="small">
|
||||
<Avatar inline source={account} title={account} /> Execute
|
||||
</Button>
|
||||
<!-- Check whether or not we've signed this proposal -->
|
||||
{:else if isSigned}
|
||||
<Badge variant="caution">✓ signed</Badge>
|
||||
{:else}
|
||||
<Button
|
||||
on:click={() => {
|
||||
action = Action.Sign;
|
||||
state = State.Confirm;
|
||||
}}
|
||||
variant="foreground"
|
||||
size="small">
|
||||
Confirm
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
<!-- We've initiated an action -->
|
||||
{#if state !== State.Idle && action === Action.Sign}
|
||||
<Modal floating>
|
||||
<span slot="title">
|
||||
<div>⚓</div>
|
||||
<div>Anchor project</div>
|
||||
</span>
|
||||
|
||||
<span slot="subtitle">
|
||||
{#if state === State.Confirm}
|
||||
<span>Initiate the transaction...</span>
|
||||
{:else if state === State.Signing}
|
||||
<span>Sign the transaction in your wallet...</span>
|
||||
{:else if state === State.Submitting}
|
||||
<span>Transaction is being confirmed...</span>
|
||||
{:else if state === State.Success}
|
||||
<span>Transaction confirmed.</span>
|
||||
{:else if state === State.Failed}
|
||||
<span>Transaction failed</span>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span slot="body">
|
||||
{#if state === State.Confirm}
|
||||
<div class="table">
|
||||
<div>Project</div>
|
||||
<span class="txt-monospace">{anchor.id}</span>
|
||||
<div>Hash</div>
|
||||
<span class="txt-monospace">{anchor.anchor.stateHash}</span>
|
||||
</div>
|
||||
{:else if state === State.Failed}
|
||||
<div>{error}</div>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span slot="actions">
|
||||
{#if state === State.Confirm}
|
||||
<Button
|
||||
variant="primary"
|
||||
on:click={() => confirmAnchor(anchor.safeTxHash)}>
|
||||
Confirm
|
||||
</Button>
|
||||
<Button variant="text" on:click={close}>Cancel</Button>
|
||||
{:else if state === State.Success || state === State.Failed}
|
||||
<Button
|
||||
variant="foreground"
|
||||
on:click={() => {
|
||||
close();
|
||||
dispatch("success");
|
||||
}}>
|
||||
Done
|
||||
</Button>
|
||||
{/if}
|
||||
</span>
|
||||
</Modal>
|
||||
{:else if state !== State.Idle && action === Action.Execute}
|
||||
<Modal floating>
|
||||
<span slot="title">
|
||||
<div>⚡</div>
|
||||
<div>Execute Safe Transaction</div>
|
||||
</span>
|
||||
|
||||
<span slot="subtitle">
|
||||
{#if state === State.Confirm}
|
||||
<span>Initiate the transaction...</span>
|
||||
{:else if state === State.Signing}
|
||||
<span>Sign the transaction in your wallet...</span>
|
||||
{:else if state === State.Submitting}
|
||||
<span>Transaction is being confirmed...</span>
|
||||
{:else if state === State.Success}
|
||||
<span>Transaction confirmed.</span>
|
||||
{:else if state === State.Failed}
|
||||
<span>Transaction failed</span>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span slot="body">
|
||||
{#if state === State.Confirm}
|
||||
<div class="table">
|
||||
<div>TxHash</div>
|
||||
<span class="txt-monospace">
|
||||
{utils.formatHash(anchor.safeTxHash)}
|
||||
</span>
|
||||
<div>Quorum</div>
|
||||
<span class="txt-monospace">
|
||||
{anchor.confirmations.length} of {safe.threshold}
|
||||
</span>
|
||||
</div>
|
||||
{:else if state === State.Failed}
|
||||
<div>{error}</div>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span slot="actions">
|
||||
{#if state === State.Confirm}
|
||||
<Button
|
||||
variant="primary"
|
||||
on:click={() => executeTransaction(anchor.safeTxHash)}>
|
||||
Confirm
|
||||
</Button>
|
||||
<Button variant="text" on:click={close}>Cancel</Button>
|
||||
{:else if state === State.Success || state === State.Failed}
|
||||
<Button
|
||||
variant="foreground"
|
||||
on:click={() => {
|
||||
close();
|
||||
dispatch("success");
|
||||
}}>
|
||||
Done
|
||||
</Button>
|
||||
{/if}
|
||||
</span>
|
||||
</Modal>
|
||||
{/if}
|
||||
@@ -1,93 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let anchors: Array<string> = [];
|
||||
export let head: string | null;
|
||||
export let commit: string;
|
||||
export let noText = false;
|
||||
export let noBg = false;
|
||||
|
||||
const text = !noText;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.anchor-widget {
|
||||
display: flex;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: var(--border-radius-small);
|
||||
color: var(--color-tertiary);
|
||||
background-color: var(--color-tertiary-background);
|
||||
cursor: pointer;
|
||||
}
|
||||
.anchor-widget.not-allowed {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.anchor-widget.not-anchored {
|
||||
color: var(--color-foreground-faded);
|
||||
background-color: var(--color-foreground-background);
|
||||
}
|
||||
.anchor-widget.no-bg {
|
||||
background: none !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.anchor-label {
|
||||
font-family: var(--font-family-monospace);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.anchor-label:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
.anchor-latest {
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if anchors && head}
|
||||
<!-- commit is head and latest anchor -->
|
||||
{#if commit === anchors[0] && commit === head}
|
||||
<span class="anchor-widget anchor-latest" class:no-bg={noBg}>
|
||||
<span class="anchor-label" title={anchors[0]}>
|
||||
{#if text}latest {/if}🔐
|
||||
</span>
|
||||
</span>
|
||||
<!-- commit is not head but latest anchor -->
|
||||
{:else if commit === anchors[0] && commit !== head}
|
||||
<span
|
||||
class="anchor-widget"
|
||||
class:no-bg={noBg}
|
||||
on:click={() => dispatch("click", head)}>
|
||||
<span class="anchor-label" title={anchors[0]}>
|
||||
{#if text}latest {/if}🔐
|
||||
</span>
|
||||
</span>
|
||||
<!-- commit is not head a stale anchor -->
|
||||
{:else if anchors.includes(commit)}
|
||||
<span
|
||||
class="anchor-widget"
|
||||
class:no-bg={noBg}
|
||||
on:click={() => dispatch("click", anchors[0])}>
|
||||
<span class="anchor-label" title={commit}>
|
||||
{#if text}stale {/if}🔒
|
||||
</span>
|
||||
</span>
|
||||
<!-- commit is not anchored, could be head or any other commit -->
|
||||
{:else}
|
||||
<span
|
||||
class="anchor-widget not-anchored"
|
||||
class:no-bg={noBg}
|
||||
on:click={() => dispatch("click", anchors[0])}>
|
||||
<span class="anchor-label">
|
||||
{#if text}not anchored {/if}🔓
|
||||
</span>
|
||||
</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<!-- commit is not head and neither an anchor, and there are no anchors available -->
|
||||
<span class="anchor-widget not-anchored not-allowed" class:no-bg={noBg}>
|
||||
<span class="anchor-label">
|
||||
{#if text}not anchored {/if}🔓
|
||||
</span>
|
||||
</span>
|
||||
{/if}
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { Writable } from "svelte/store";
|
||||
import { navigate } from "svelte-routing";
|
||||
import { Browser, ProjectContent, Project } from "@app/project";
|
||||
import AnchorBadge from "@app/base/profiles/AnchorBadge.svelte";
|
||||
import BranchSelector from "@app/base/projects/BranchSelector.svelte";
|
||||
import CloneButton from "@app/base/projects/CloneButton.svelte";
|
||||
import PeerSelector from "@app/base/projects/PeerSelector.svelte";
|
||||
@@ -12,9 +11,8 @@
|
||||
export let tree: Tree;
|
||||
export let commit: string;
|
||||
export let browserStore: Writable<Browser>;
|
||||
export let noAnchor = false;
|
||||
|
||||
const { urn, peers, branches, seed, anchors } = project;
|
||||
const { urn, peers, branches, seed } = project;
|
||||
|
||||
$: browser = $browserStore;
|
||||
$: revision = browser.revision || commit;
|
||||
@@ -54,9 +52,6 @@
|
||||
border-radius: var(--border-radius-small);
|
||||
min-width: max-content;
|
||||
}
|
||||
.anchor {
|
||||
display: inline-flex;
|
||||
}
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -104,16 +99,6 @@
|
||||
{revision}
|
||||
on:branchChanged={event => updateRevision(event.detail)} />
|
||||
|
||||
{#if !noAnchor}
|
||||
<div class="anchor widget">
|
||||
<AnchorBadge
|
||||
{commit}
|
||||
{anchors}
|
||||
head={project.head}
|
||||
on:click={event => updateRevision(event.detail)} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if seed.git.host}
|
||||
<CloneButton seedHost={seed.git.host} {urn} />
|
||||
{/if}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
{#await project.getRoot(revision)}
|
||||
<Loading center />
|
||||
{:then { tree, commit }}
|
||||
<Header {tree} {commit} {browserStore} {project} noAnchor />
|
||||
<Header {tree} {commit} {browserStore} {project} />
|
||||
|
||||
{#if content === proj.ProjectContent.Tree}
|
||||
<Browser {project} {commit} {tree} {browserStore} />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type * as proj from "@app/project";
|
||||
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";
|
||||
@@ -10,7 +9,6 @@
|
||||
export let project: proj.ProjectInfo;
|
||||
export let seed: { api: Host };
|
||||
export let faded = false;
|
||||
export let anchor: proj.Anchor | null = null;
|
||||
export let compact = false;
|
||||
|
||||
const loadCommits = async () => {
|
||||
@@ -69,9 +67,6 @@
|
||||
article:hover {
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
article:hover .anchor {
|
||||
display: block;
|
||||
}
|
||||
article:hover .activity {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -94,22 +89,10 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
article .anchor-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
article .actions {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
article .commit,
|
||||
article .actions {
|
||||
article .commit {
|
||||
font-family: var(--font-family-monospace);
|
||||
}
|
||||
article.project-faded .anchor {
|
||||
color: var(--color-foreground-faded);
|
||||
}
|
||||
article .id,
|
||||
article .anchor {
|
||||
article .id {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@@ -120,15 +103,9 @@
|
||||
font-family: var(--font-family-monospace);
|
||||
font-size: var(--font-size-tiny);
|
||||
}
|
||||
article .anchor-badge {
|
||||
display: none;
|
||||
}
|
||||
article:hover .id .urn {
|
||||
visibility: visible;
|
||||
}
|
||||
article:hover .anchor-badge {
|
||||
display: block;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
article {
|
||||
min-width: 0;
|
||||
@@ -164,25 +141,6 @@
|
||||
<div class="id">
|
||||
<span class="urn desktop">{project.urn}</span>
|
||||
</div>
|
||||
<div class="anchor">
|
||||
<span class="anchor-info">
|
||||
<span class="actions">
|
||||
<slot name="actions" />
|
||||
</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>
|
||||
</div>
|
||||
{#await loadCommits() then points}
|
||||
<div class="desktop activity">
|
||||
<Diagram
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import SeedAddress from "@app/SeedAddress.svelte";
|
||||
import NotFound from "@app/NotFound.svelte";
|
||||
import Clipboard from "@app/Clipboard.svelte";
|
||||
import Projects from "@app/base/orgs/View/Projects.svelte";
|
||||
import Projects from "@app/base/seeds/View/Projects.svelte";
|
||||
import type { Session } from "@app/session";
|
||||
import Address from "@app/Address.svelte";
|
||||
import SiweConnect from "@app/SiweConnect.svelte";
|
||||
@@ -158,7 +158,7 @@
|
||||
</div>
|
||||
<!-- Seed Projects -->
|
||||
<Async fetch={Project.getProjects(seedHost, { perPage: 10 })} let:result>
|
||||
<Projects {seed} {config} projects={result} />
|
||||
<Projects {seed} projects={result} />
|
||||
</Async>
|
||||
</main>
|
||||
{:catch}
|
||||
|
||||
@@ -1,38 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { navigate } from "svelte-routing";
|
||||
import { onMount } from "svelte";
|
||||
import type { Config } from "@app/config";
|
||||
import * as proj from "@app/project";
|
||||
import Widget from "@app/base/projects/Widget.svelte";
|
||||
import type { Profile } from "@app/profile";
|
||||
import type { ProjectInfo, Anchor, PendingAnchor } from "@app/project";
|
||||
import type { ProjectInfo } from "@app/project";
|
||||
import type { Seed } from "@app/base/seeds/Seed";
|
||||
import AnchorActions from "@app/base/profiles/AnchorActions.svelte";
|
||||
import List from "@app/List.svelte";
|
||||
|
||||
export let seed: Seed;
|
||||
export let profile: Profile | null = null;
|
||||
export let account: string | null = null;
|
||||
export let projects: proj.ProjectInfo[];
|
||||
export let config: Config;
|
||||
|
||||
let anchors: Record<string, Anchor> = {};
|
||||
let pendingAnchors: Record<string, PendingAnchor> = {};
|
||||
// A pointer to the current page of projects added to the listing
|
||||
let page = 0;
|
||||
|
||||
const loadAnchors = async () => {
|
||||
if (profile) {
|
||||
const [pending, confirmed] = await Promise.all([
|
||||
profile.pendingAnchors(config),
|
||||
profile.confirmedAnchors(config),
|
||||
]);
|
||||
|
||||
anchors = confirmed;
|
||||
pendingAnchors = pending;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchMoreProjects = async (): Promise<proj.ProjectInfo[]> => {
|
||||
const projects = await proj.Project.getProjects(seed.api, {
|
||||
perPage: 10,
|
||||
@@ -56,8 +37,6 @@
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
onMount(loadAnchors);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -67,35 +46,15 @@
|
||||
.projects .project {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="projects">
|
||||
<List items={projects} query={fetchMoreProjects}>
|
||||
<svelte:fragment slot="list" let:items>
|
||||
{#each items as project}
|
||||
{@const anchor = anchors[project.urn]}
|
||||
{@const pendingAnchor = pendingAnchors[project.urn]}
|
||||
{#if project.head}
|
||||
<div class="project">
|
||||
<Widget {project} {seed} {anchor} on:click={() => onClick(project)}>
|
||||
<span class="actions" slot="actions">
|
||||
{#if profile?.org?.safe && account && anchor}
|
||||
{#if pendingAnchor}
|
||||
<!-- Pending anchor -->
|
||||
<AnchorActions
|
||||
{account}
|
||||
{config}
|
||||
anchor={pendingAnchor}
|
||||
safe={profile.org.safe}
|
||||
on:success={() => loadAnchors()} />
|
||||
{/if}
|
||||
{/if}
|
||||
</span>
|
||||
</Widget>
|
||||
<Widget {project} {seed} on:click={() => onClick(project)} />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
@@ -1,8 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Route } from "svelte-routing";
|
||||
import Redirect from "@app/Redirect.svelte";
|
||||
</script>
|
||||
|
||||
<Route path="/users/:addressOrName" let:params>
|
||||
<Redirect to="/{params.addressOrName}" />
|
||||
</Route>
|
||||
@@ -9,7 +9,7 @@
|
||||
import Address from "@app/Address.svelte";
|
||||
import Button from "@app/Button.svelte";
|
||||
|
||||
import type { Org } from "./Org";
|
||||
import type { Org } from "@app/base/orgs/Org";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -15,8 +15,6 @@ import { cached } from "@app/cache";
|
||||
import type { Seed, InvalidSeed } from "@app/base/seeds/Seed";
|
||||
import { Org } from "@app/base/orgs/Org";
|
||||
import { NotFoundError, MissingReverseRecord } from "@app/error";
|
||||
import { getProjectAnchors } from "@app/anchors";
|
||||
import type { Anchor, PendingAnchor } from "@app/project";
|
||||
|
||||
export interface IProfile {
|
||||
address: string;
|
||||
@@ -153,63 +151,6 @@ export class Profile {
|
||||
}
|
||||
}
|
||||
|
||||
// Get confirmed anchors.
|
||||
async confirmedAnchors(config: Config): Promise<Record<string, Anchor>> {
|
||||
const org = await this.getAnchorsOrg(config);
|
||||
|
||||
if (org) {
|
||||
const result = await org.getProjects(config);
|
||||
const anchors: Record<string, Anchor> = {};
|
||||
|
||||
for (const anchor of result) {
|
||||
anchors[anchor.id] = anchor;
|
||||
}
|
||||
return anchors;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Get pending anchors.
|
||||
async pendingAnchors(config: Config): Promise<Record<string, PendingAnchor>> {
|
||||
const org = await this.getAnchorsOrg(config);
|
||||
|
||||
if (org) {
|
||||
const result = await org.getPendingProjects(config);
|
||||
const anchors: Record<string, PendingAnchor> = {};
|
||||
|
||||
for (const anchor of result) {
|
||||
anchors[anchor.id] = anchor;
|
||||
}
|
||||
return anchors;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async confirmedProjectAnchors(
|
||||
urn: string,
|
||||
config: Config,
|
||||
): Promise<string[]> {
|
||||
const storage = this.anchorsAccount || this.org?.address;
|
||||
|
||||
if (storage) {
|
||||
return await getProjectAnchors(urn, storage, config);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get the anchors account as an org, or the org, if available.
|
||||
private async getAnchorsOrg(config: Config): Promise<Org | null> {
|
||||
if (this.anchorsAccount) {
|
||||
return await Org.get(this.anchorsAccount, config);
|
||||
} else if (this.org) {
|
||||
return this.org;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Keeping this function private since the desired entrypoint is .get()
|
||||
// All addresses returned from this function should be lowercase.
|
||||
private static async lookupProfile(
|
||||
|
||||
+1
-25
@@ -22,24 +22,6 @@ export type Delegate =
|
||||
id: PeerId;
|
||||
};
|
||||
|
||||
export interface Anchor {
|
||||
confirmed: true;
|
||||
id: string;
|
||||
anchor: {
|
||||
stateHash: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PendingAnchor {
|
||||
confirmed: false;
|
||||
id: string;
|
||||
safeTxHash: string; // Safe transaction hash.
|
||||
confirmations: string[]; // Owner addresses who have confirmed.
|
||||
anchor: {
|
||||
stateHash: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Enumerates the space below the Header component in the projects View component
|
||||
export enum ProjectContent {
|
||||
Tree,
|
||||
@@ -277,7 +259,6 @@ export class Project implements ProjectInfo {
|
||||
peers: Peer[];
|
||||
branches: Branches;
|
||||
profile: Profile | null;
|
||||
anchors: string[];
|
||||
// At the moment we still have seed nodes which won't return neither patches or issues
|
||||
patches?: number;
|
||||
issues?: number;
|
||||
@@ -289,7 +270,6 @@ export class Project implements ProjectInfo {
|
||||
peers: Peer[],
|
||||
branches: Branches,
|
||||
profile: Profile | null,
|
||||
anchors: string[],
|
||||
) {
|
||||
this.urn = urn;
|
||||
this.head = info.head;
|
||||
@@ -304,7 +284,6 @@ export class Project implements ProjectInfo {
|
||||
this.patches = info.patches;
|
||||
this.issues = info.issues;
|
||||
this.profile = profile;
|
||||
this.anchors = anchors;
|
||||
}
|
||||
|
||||
async getRoot(
|
||||
@@ -480,9 +459,6 @@ export class Project implements ProjectInfo {
|
||||
|
||||
const info = await Project.getInfo(id, seed.api);
|
||||
const urn = isRadicleId(id) ? id : info.urn;
|
||||
const anchors = profile
|
||||
? await profile.confirmedProjectAnchors(urn, config)
|
||||
: [];
|
||||
|
||||
// Older versions of http-api don't include the URN.
|
||||
if (!info.urn) info.urn = urn;
|
||||
@@ -503,7 +479,7 @@ export class Project implements ProjectInfo {
|
||||
}
|
||||
}
|
||||
|
||||
return new Project(urn, info, seed, peers, remote.heads, profile, anchors);
|
||||
return new Project(urn, info, seed, peers, remote.heads, profile);
|
||||
}
|
||||
|
||||
static async getMulti(
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
///<reference types="vitest" />
|
||||
import path from "path";
|
||||
import { UserConfig } from "vite";
|
||||
import type { UserConfig } from "vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import { ViteDevServer } from "vite";
|
||||
import type { ViteDevServer } from "vite";
|
||||
import IstanbulPlugin from "vite-plugin-istanbul";
|
||||
import history from "connect-history-api-fallback";
|
||||
import type { Request, Response } from "express-serve-static-core";
|
||||
|
||||
Reference in New Issue
Block a user