Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Show anchor state in project view
Alexis Sellier committed 4 years ago
commit e7ef9e7ad9f97d422150e3860ca30b88c0e035d5
parent 8d5429b62c287b357889289c942d269acaf5a7a9
9 files changed +104 -8
modified public/index.css
@@ -28,6 +28,7 @@
	--color-yellow: #ffff99;
	--color-yellow-background: #ffff9911;
	--color-positive: #53db53;
+
	--color-positive-background: #53db5311;
	--color-positive-1: #21402f;
	--color-positive-2: #2c6837;
	--color-positive-6: #e3ffe3;
modified src/Address.svelte
@@ -6,13 +6,17 @@
  import Blockies from '@app/Blockies.svelte';
  import Loading from '@app/Loading.svelte';
  import type { Config } from '@app/config';
-
  import { identifyAddress, AddressType } from '@app/utils';
+
  import { identifyAddress, formatAddress, AddressType } from '@app/utils';

  export let address: string;
  export let config: Config;
  export let resolve = false;
+
  export let noBadge = false;
+
  export let compact = false;

-
  let checksumAddress = ethers.utils.getAddress(address);
+
  let checksumAddress = compact
+
    ? formatAddress(address)
+
    : ethers.utils.getAddress(address);
  let addressType: AddressType | null = null;
  let addressName: string | null = null;

@@ -31,6 +35,9 @@
    display: flex;
    align-items: center;
  }
+
  .address.no-badge .badge {
+
    display: none;
+
  }
  .icon {
    display: inline-block;
    width: 1rem;
@@ -51,7 +58,7 @@
  }
</style>

-
<div class="address">
+
<div class="address" class:no-badge={noBadge}>
  <span class="icon"><Blockies address={address} /></span>
  {#if addressType === AddressType.Org}
    <a use:link href={`/orgs/${address}`}>{addressLabel}</a>
@@ -65,7 +72,7 @@
      <span class="badge">contract</span>
    {:else if addressType === AddressType.EOA}
      <!-- Don't show anything for EOAs -->
-
    {:else}
+
    {:else if !noBadge}
      <div class="loading"><Loading small /></div>
    {/if}
  {/if}
modified src/Loading.svelte
@@ -3,6 +3,7 @@
  export let color = "secondary";
  export let center = false;
  export let fadeIn = false;
+
  export let margins = false;
</script>

<style>
@@ -15,6 +16,9 @@
  .spinner.center {
    margin: auto auto;
  }
+
  .spinner.margins {
+
    margin: 0 0.5rem;
+
  }

  .spinner > div {
    width: 18px;
@@ -78,7 +82,7 @@
  }
</style>

-
<div class="spinner" class:fade-in={fadeIn} class:small class:center>
+
<div class="spinner" class:fade-in={fadeIn} class:small class:center class:margins>
  <div class="bounce1" style="background-color: var(--color-{color})"></div>
  <div class="bounce2" style="background-color: var(--color-{color})"></div>
  <div class="bounce3" style="background-color: var(--color-{color})"></div>
modified src/base/orgs/Org.ts
@@ -96,6 +96,26 @@ export class Org {
    return projects;
  }

+
  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.parseRadicleId(urn);
+
    const id = ethers.utils.zeroPad(unpadded, 32);
+

+
    try {
+
      const result = await org.anchors(id);
+
      const anchor = utils.formatProjectHash(ethers.utils.arrayify(result[0]));
+

+
      return anchor;
+
    } catch (e) {
+
      console.error(e);
+
      return null;
+
    }
+
  }
+

  static async getAll(config: Config): Promise<Array<Org>> {
    const result = await utils.querySubgraph(GetOrgs, {}, config);
    const orgs: Org[] = [];
modified src/base/projects/Browser.svelte
@@ -2,6 +2,8 @@
  import type { Config } from '@app/config';
  import * as proj from '@app/project';
  import Loading from '@app/Loading.svelte';
+
  import Address from '@app/Address.svelte';
+
  import { Org } from '@app/base/orgs/Org';

  import Tree from './Tree.svelte';
  import Blob from './Blob.svelte';
@@ -11,6 +13,7 @@
  export let config: Config;
  export let path: string;
  export let onSelect: (event: { detail: string }) => void;
+
  export let org: string | null = null;

  let blob: Promise<proj.Blob | null> | null = null;

@@ -23,14 +26,16 @@
  } else {
    blob = proj.getBlob(urn, commit, path, config);
  }
+
  $: getAnchor = org ? Org.getAnchor(org, urn, config) : null;
</script>

<style>
  main > header {
    padding: 0 8rem;
    margin-bottom: 2rem;
+
    display: flex;
  }
-
  .anchor {
+
  .commit {
    display: inline-block;
    font-size: 0.75rem;
    font-family: var(--font-family-monospace);
@@ -40,6 +45,20 @@
    border-radius: 0.25rem;
  }

+
  .anchor {
+
    font-size: 0.75rem;
+
    padding: 0.75rem;
+
    display: inline-block;
+
    color: var(--color-positive);
+
    background-color: var(--color-positive-background);
+
    border-radius: 0.25rem;
+
    margin-left: 0.75rem;
+
    display: flex;
+
  }
+
  .anchor-label {
+
    margin-right: 0.5rem;
+
  }
+

  .center-content {
    margin: 0 auto;
    max-width: var(--content-max-width);
@@ -91,9 +110,21 @@
    Loading..
  {:then tree}
    <header>
-
      <div class="anchor">
+
      <div class="commit">
        commit {commit}
      </div>
+
      {#if org}
+
        {#await getAnchor}
+
          <Loading small margins />
+
        {:then anchor}
+
          {#if anchor === commit}
+
            <span class="anchor">
+
              <span class="anchor-label">anchor</span>
+
              <Address address={org} compact resolve noBadge {config} />
+
            </span>
+
          {/if}
+
        {/await}
+
      {/if}
    </header>
    <div class="container center-content">
      {#if tree.entries.length}
modified src/base/projects/Routes.svelte
@@ -29,3 +29,25 @@
<Route path="/projects/:urn" let:params>
  <View {config} urn={params.urn} path="/" />
</Route>
+

+
<!-- With an Org context -->
+

+
<Route path="/orgs/:org/projects/:urn/head/:path/*" let:params>
+
  <View {config} org={params.org} urn={params.urn} path={joinPaths(params.path, params['*'])} />
+
</Route>
+

+
<Route path="/orgs/:org/projects/:urn/:commit/:path/*" let:params>
+
  <View {config} org={params.org} urn={params.urn} commit={params.commit} path={joinPaths(params.path, params['*'])} />
+
</Route>
+

+
<Route path="/orgs/:org/projects/:urn/:commit" let:params>
+
  <View {config} org={params.org} urn={params.urn} commit={params.commit} path="/" />
+
</Route>
+

+
<Route path="/orgs/:org/projects/:urn/head" let:params>
+
  <View {config} org={params.org} urn={params.urn} path="/" />
+
</Route>
+

+
<Route path="/orgs/:org/projects/:urn" let:params>
+
  <View {config} org={params.org} urn={params.urn} path="/" />
+
</Route>
modified src/base/projects/View.svelte
@@ -13,6 +13,7 @@
  };

  export let urn: string;
+
  export let org: string | null = null;
  export let commit: string | null = null;
  export let config: Config;
  export let path: string;
@@ -76,7 +77,7 @@
      <div class="urn">{urn}</div>
      <div class="description">{project.meta.description}</div>
    </header>
-
    <Browser {urn} commit={commit || project.head} {path} {onSelect} {config} />
+
    <Browser {urn} {org} commit={commit || project.head} {path} {onSelect} {config} />
  {:else}
    <Modal subtle>
      <span slot="title">🏜️</span>
modified src/config.json
@@ -94,6 +94,7 @@
    ],
    "org": [
      "function owner() view returns (address)",
+
      "function anchors(bytes32) view returns (bytes, uint8)",
      "function setOwner(address)",
      "function setName(string, address) returns (bytes32)"
    ],
modified src/utils.ts
@@ -135,6 +135,15 @@ export function formatRadicleId(hash: Uint8Array): string {
  return `rad:git:${new TextDecoder().decode(payload)}`;
}

+
// Parse a Radicle Id (URN).
+
export function parseRadicleId(urn: string): Uint8Array {
+
  const encoded = urn.replace(/^rad:[a-z]+:/, "");
+
  const multihash = multibase.decode(encoded);
+
  const hash = multihashes.decode(multihash);
+

+
  return hash.digest;
+
}
+

// Create a project hash from a hash and format.
export function formatProjectHash(multihash: Uint8Array): string {
  const decoded = multihashes.decode(multihash);