Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add "My Orgs" section to orgs page
Alexis Sellier committed 4 years ago
commit 589ae569ef6b39fe993fac7c7e6fdb0c28975356
parent f4f08a32c48745a2fbbf0a664ce49540f1b65223
6 files changed +108 -29
modified src/base/orgs/Index.svelte
@@ -1,17 +1,15 @@
<script lang="ts">
  import type { SvelteComponent } from 'svelte';
-
  import { Link } from 'svelte-routing';
  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 Blockies from '@app/Blockies.svelte';
  import Loading from '@app/Loading.svelte';
+
  import List from './List.svelte';

  export let config: Config;

  const onCreate = () => modal = Create;
-

  let modal: typeof SvelteComponent | null = null;

  $: account = $session && $session.address;
@@ -33,15 +31,18 @@
    align-items: center;
  }

-
  button.create {
-
    margin-left: 1.5rem;
+
  .my-orgs {
+
    margin-bottom: 3rem;
+
  }
+
  .orgs-empty {
+
    margin-left: 3rem;
+
    padding: 1rem 0 2rem 0;
+
    font-style: italic;
+
    color: var(--color-foreground-faded);
  }

-
  .org {
-
    width: 3rem;
-
    height: 3rem;
-
    margin: 3rem;
-
    display: inline-block;
+
  button.create {
+
    margin-left: 1.5rem;
  }

  .loading {
@@ -50,24 +51,39 @@
</style>

<main>
+
  {#if account}
+
    <div class="my-orgs">
+
      <header>
+
        <span>My <strong>Orgs</strong></span>
+
        <button class="create small secondary" on:click={onCreate} disabled={!account}>
+
          Create
+
        </button>
+
      </header>
+

+
      {#await Org.getOrgsByMember(account, config)}
+
        <div class="loading">
+
          <Loading center />
+
        </div>
+
      {:then orgs}
+
        <List {orgs}>
+
          <div class="orgs-empty">Orgs you are a member of show up here.</div>
+
        </List>
+
      {/await}
+
    </div>
+
  {/if}
+

+
  <header>
+
    <span><strong>Orgs</strong> of the Radicle network</span>
+
  </header>
+

  {#await Org.getAll(config)}
    <div class="loading">
      <Loading center />
    </div>
  {:then orgs}
-
    <header>
-
      <span><strong>Orgs</strong> of the Radicle network.</span>
-
      <button class="create small secondary" on:click={onCreate} disabled={!account}>
-
        Create
-
      </button>
-
    </header>
-
    {#each orgs as org}
-
      <div class="org">
-
        <Link to={`/orgs/${org.address}`}>
-
          <Blockies glowOnHover address={org.address} />
-
        </Link>
-
      </div>
-
    {/each}
+
    <List {orgs}>
+
      <div class="orgs-empty">There are no orgs.</div>
+
    </List>
  {/await}
</main>

added src/base/orgs/List.svelte
@@ -0,0 +1,26 @@
+
<script lang="ts">
+
  import { Link } from 'svelte-routing';
+
  import type { Org } from '@app/base/orgs/Org';
+
  import Blockies from '@app/Blockies.svelte';
+

+
  export let orgs: Org[];
+
</script>
+

+
<style>
+
  .org {
+
    width: 3rem;
+
    height: 3rem;
+
    margin: 3rem;
+
    display: inline-block;
+
  }
+
</style>
+

+
{#each orgs as org}
+
  <div class="org">
+
    <Link to={`/orgs/${org.address}`}>
+
      <Blockies glowOnHover address={org.address} />
+
    </Link>
+
  </div>
+
{:else}
+
  <slot />
+
{/each}
modified src/base/orgs/Org.ts
@@ -18,7 +18,6 @@ const GetProjects = `
  }
`;

-
// TODO: Add timestamps to org creation.
const GetOrgs = `
  query GetOrgs {
    orgs {
@@ -28,6 +27,26 @@ const GetOrgs = `
  }
`;

+
const GetSafes = `
+
  query GetSafes($owners: [String!]!) {
+
    wallets(where: { owners_contains: $owners }) {
+
      id
+
      owners
+
    }
+
  }
+
`;
+

+
const GetOrgsByOwner = `
+
  query GetOrgsByOwner($owners: [String!]!) {
+
    orgs(where: { owner_in: $owners }) {
+
      id
+
      owner
+
      creator
+
      timestamp
+
    }
+
  }
+
`;
+

export class Org {
  address: string;
  owner: string;
@@ -75,7 +94,9 @@ export class Org {
  }

  async getProjects(config: Config): Promise<Array<Project>> {
-
    const result = await utils.querySubgraph(GetProjects, { org: this.address }, config);
+
    const result = await utils.querySubgraph(
+
      config.orgs.subgraph, GetProjects, { org: this.address }
+
    );
    const projects: Project[] = [];

    for (const p of result.projects) {
@@ -117,7 +138,7 @@ export class Org {
  }

  static async getAll(config: Config): Promise<Array<Org>> {
-
    const result = await utils.querySubgraph(GetOrgs, {}, config);
+
    const result = await utils.querySubgraph(config.orgs.subgraph, GetOrgs, {});
    const orgs: Org[] = [];

    for (const o of result.orgs) {
@@ -162,6 +183,19 @@ export class Org {
    }
  }

+
  static async getOrgsByMember(memberAddr: string, config: Config): Promise<Org[]> {
+
    const safeResult = await utils.querySubgraph(
+
      config.safe.subgraph, GetSafes, { owners: [memberAddr] }
+
    );
+
    const wallets: { id: string }[] = safeResult.wallets;
+
    const owners = wallets.map(wallet => wallet.id).concat([memberAddr]);
+
    const orgsResult = await utils.querySubgraph(config.orgs.subgraph, GetOrgsByOwner, { owners });
+

+
    return orgsResult.orgs.map((o: { id: string; owner: string }) => {
+
      return new Org(o.id, o.owner);
+
    });
+
  }
+

  static async createMultiSig(
    owners: [string],
    threshold: number,
modified src/config.json
@@ -16,6 +16,7 @@
    },
    "safe": {
      "api": "https://safe-transaction.gnosis.io/api/v1",
+
      "subgraph": null,
      "viewer": "https://gnosis-safe.io/app/#/safes"
    },
    "tokens": []
@@ -37,6 +38,7 @@
    },
    "safe": {
      "api": null,
+
      "subgraph": null,
      "viewer": null
    },
    "tokens": []
@@ -58,6 +60,7 @@
    },
    "safe": {
      "api": "https://safe-transaction.rinkeby.gnosis.io/api/v1",
+
      "subgraph": "https://api.thegraph.com/subgraphs/name/radicle-dev/gnosis-safe-rinkeby",
      "viewer": "https://rinkeby.gnosis-safe.io/app/#/safes"
    },
    "tokens": []
modified src/config.ts
@@ -19,7 +19,7 @@ export class Config {
  gasLimits: { createOrg: number };
  provider: ethers.providers.JsonRpcProvider;
  signer: ethers.Signer & TypedDataSigner | null;
-
  safe: { api: string | null; viewer: string | null };
+
  safe: { api: string | null; subgraph: string; viewer: string | null };
  abi: { [contract: string]: string[] };
  seed: { api?: string };
  tokens: string[];
modified src/utils.ts
@@ -95,11 +95,11 @@ export function safeLink(addr: string, config: Config): string {

// Query a subgraph.
export async function querySubgraph(
+
  url: string,
  query: string,
  variables: Record<string, any>,
-
  config: Config
): Promise<null | any> {
-
  const response = await fetch(config.orgs.subgraph, {
+
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',