Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Refactor project browser
Sebastian Martinez committed 4 years ago
commit 4b2c14e42e2ed790134d445d1615924d0d0673f9
parent b3929fef74a0289cd30acea2e4d2a0773013c96c
3 files changed +321 -264
modified src/base/projects/Browser.svelte
@@ -1,8 +1,6 @@
<script lang="ts">
  import { navigate } from 'svelte-routing';
  import type { Config } from '@app/config';
-
  import type { Profile } from '@app/profile';
-
  import { ethers } from "ethers";
  import * as proj from '@app/project';
  import Loading from '@app/Loading.svelte';
  import * as utils from '@app/utils';
@@ -23,18 +21,12 @@
  export let commit: string;
  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;
  export let user: string | null = null;
-
  export let project: proj.Info;
+
  export let tree: proj.Tree;

  // When the component is loaded the first time, the blob is yet to be loaded.
  let state: State = { status: Status.Loading, path };
-
  // Whether the clone dropdown is visible.
-
  let cloneDropdown = false;
-
  // Whether the seed dropdown is visible.
-
  let seedDropdown = false;
  // Whether the mobile file tree is visible.
  let mobileFileTree = false;

@@ -116,149 +108,10 @@
  // 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 = anchors ? getAllAnchors(anchors, urn) : null;
  $: loadingPath = state.status == Status.Loading ? state.path : null;
</script>

<style>
-
  main > header {
-
    font-size: 0.75rem;
-
    padding: 0 2rem 0 8rem;
-
    margin-bottom: 2rem;
-
    display: flex;
-
    align-items: center;
-
    justify-content: left;
-
    flex-wrap: wrap;
-
    gap: 0.5rem;
-

-
  }
-
  main > header > * {
-
    border-radius: 0.25rem;
-
    min-width: max-content;
-
  }
-

-
  .commit {
-
    display: flex;
-
    align-items: center;
-
    justify-content: center;
-
    font-family: var(--font-family-monospace);
-
  }
-
  .commit .branch {
-
    padding: 0.5rem 0.75rem;
-
    color: var(--color-secondary);
-
    background-color: var(--color-secondary-background);
-
    border-radius: inherit;
-
    border-top-right-radius: 0;
-
    border-bottom-right-radius: 0;
-
  }
-
  .commit .hash {
-
    display: inline-block;
-
    color: var(--color-secondary);
-
    background-color: var(--color-secondary-background);
-
    padding: 0.5rem 0.75rem;
-
    border-radius: inherit;
-
  }
-
  .branch + .hash {
-
    background-color: var(--color-secondary-background-darker);
-
    border-radius: inherit;
-
    border-top-left-radius: 0;
-
    border-bottom-left-radius: 0;
-
  }
-

-
  .anchor {
-
    display: inline-flex;
-
  }
-
  .anchor-widget {
-
    display: flex;
-
    padding: 0.5rem 0.75rem;
-
    border-radius: inherit;
-
    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-label {
-
    font-family: var(--font-family-monospace);
-
    margin-right: 0.5rem;
-
  }
-
  .anchor-label:last-child {
-
    margin-right: 0;
-
  }
-
  .anchor-latest {
-
    cursor: default;
-
  }
-

-
  .seed {
-
    cursor: pointer;
-
    border-radius: inherit;
-
  }
-
  .seed:hover {
-
    background-color: var(--color-foreground-background-lighter);
-
  }
-

-
  .clone {
-
    color: var(--color-primary);
-
    background-color: var(--color-primary-background);
-
    font-family: var(--font-family-monospace);
-
    padding: 0.5rem 0.75rem;
-
    border-radius: 0.25rem;
-
    cursor: pointer;
-
    user-select: none;
-
  }
-
  .clone:hover {
-
    background-color: var(--color-primary-background-lighter);
-
  }
-
  .dropdown {
-
    background-color: var(--color-foreground-background);
-
    padding: 1rem;
-
    margin-top: 0.5rem;
-
    border-radius: 0.25rem;
-
    display: none;
-
    position: absolute;
-
  }
-
  .clone-dropdown.clone-dropdown-visible {
-
    display: block;
-
  }
-
  .clone-dropdown input {
-
    color: var(--color-primary);
-
    background: var(--color-primary-background);
-
  }
-
  .seed-dropdown input {
-
    color: var(--color-foreground-90);
-
    background: var(--color-foreground-background-lighter);
-
  }
-
  .seed-dropdown.seed-dropdown-visible {
-
    display: block;
-
  }
-
  .dropdown input {
-
    font-size: 0.75rem;
-
    font-family: var(--font-family-monospace);
-
    padding: 0.5rem;
-
    border: none;
-
    outline: none;
-
    width: 24rem;
-
    text-overflow: ellipsis !important;
-
    border-radius: 0.25rem;
-
  }
-
  .dropdown label {
-
    display: block;
-
    color: var(--color-foreground-faded);
-
    padding: 0.5rem 0.5rem 0 0.25rem;
-
    font-size: 0.75rem;
-
  }
-

-
  .stat {
-
    font-family: var(--font-family-monospace);
-
    padding: 0.5rem 0.75rem;
-
    background: var(--color-foreground-background);
-
  }
-

  .center-content {
    margin: 0 auto;
  }
@@ -305,12 +158,9 @@
  }

  @media (max-width: 960px) {
-
    main > header, .container {
+
    .container {
      padding-left: 2rem;
    }
-
    main > header {
-
      margin-bottom: 1.5rem;
-
    }
  }

  @media (max-width: 720px) {
@@ -325,10 +175,6 @@
    .source-tree {
      padding-top: 1rem;
    }
-
    .dropdown {
-
      left: 32px;
-
      z-index: 10;
-
    }
    .container {
      flex-direction: column;
    }
@@ -343,99 +189,6 @@
</style>

<main>
-
  {#await proj.getTree(urn, commit, "/", config)}
-
    <!-- Loading -->
-
  {:then tree}
-
    <header>
-
      <div class="commit">
-
        {#if commit === project.head}
-
          <div class="branch">
-
            {project.meta.defaultBranch}
-
          </div>
-
          <div class="hash">
-
            {utils.formatCommit(commit)}
-
          </div>
-
        {:else}
-
          <div class="hash desktop">
-
            {commit}
-
          </div>
-
          <div class="hash mobile">
-
            {utils.formatCommit(commit)}
-
          </div>
-
        {/if}
-
      </div>
-
      <div class="anchor">
-
        {#if anchors}
-
          {#await getAnchor}
-
            <Loading small margins />
-
          {:then anchor}
-
            {#if anchor}
-
              <!-- commit is head and latest anchor  -->
-
              {#if commit == anchor[0] && commit === project.head}
-
                <span class="anchor-widget anchor-latest">
-
                  <span class="anchor-label" title="{anchors}">latest 🔐</span>
-
                </span>
-
              <!-- commit is not head but latest anchor  -->
-
              {:else if commit == anchor[0] && commit !== project.head}
-
                <span class="anchor-widget" on:click={() => navigateBrowser(project.head)}>
-
                  <span class="anchor-label" title="{anchors}">latest 🔐</span>
-
                </span>
-
              <!-- commit is not head a stale anchor  -->
-
              {:else if anchor?.includes(commit)}
-
                <span class="anchor-widget" on:click={() => navigateBrowser(anchor[0])}>
-
                  <span class="anchor-label" title="{anchors}">stale 🔒</span>
-
                </span>
-
              <!-- commit is not anchored, could be head or any other commit  -->
-
              {:else}
-
                <span class="anchor-widget not-anchored" on:click={() => navigateBrowser(anchor[0])}>
-
                  <span class="anchor-label">not anchored 🔓</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">
-
                <span class="anchor-label">not anchored 🔓</span>
-
              </span>
-
            {/if}
-
          {/await}
-
        {/if}
-
      </div>
-
      {#if config.seed.git.host}
-
        <span>
-
          <div class="clone" on:click={() => cloneDropdown = !cloneDropdown}>
-
            Clone
-
          </div>
-
          <div class="dropdown clone-dropdown" class:clone-dropdown-visible={cloneDropdown}>
-
            <input readonly name="clone-url" value="https://{config.seed.git.host}/{utils.parseRadicleId(urn)}"/>
-
            <label for="clone-url">Use Git to clone this repository from the URL above.</label>
-
          </div>
-
        </span>
-
      {/if}
-
      <span>
-
        {#if config.seed.api.host}
-
          <div class="stat seed" on:click={() => seedDropdown = !seedDropdown} title="Project data is fetched from this seed">
-
            <span>{config.seed.api.host}</span>
-
          </div>
-
        {/if}
-
        <div class="dropdown seed-dropdown" class:seed-dropdown-visible={seedDropdown}>
-
          {#if config.seed.link.id && config.seed.link.host}
-
            <input readonly
-
              name="clone-url"
-
              value={utils.formatSeedAddress(config.seed.link.id, config.seed.link.host, config)}/>
-
            <label for="seed-url">Bootstrap your Radicle node with this seed.</label>
-
          {:else if profile}
-
            <label for="#">Seed ID is not set for {profile.name}.</label>
-
          {/if}
-
        </div>
-
      </span>
-
      <div class="stat">
-
        <strong>{tree.stats.commits}</strong> commit(s)
-
      </div>
-
      <div class="stat">
-
        <strong>{tree.stats.contributors}</strong> contributor(s)
-
      </div>
-
    </header>
-

    <!-- Mobile navigation -->
    <nav class="mobile">
      <button class="small browse secondary center-content" on:click={toggleMobileFileTree}>
@@ -477,13 +230,4 @@
        </div>
      {/if}
    </div>
-
  {:catch err}
-
    <div class="container center-content">
-
      <div class="error error-message text-xsmall">
-
        <!-- TODO: Differentiate between (1) commit doesn't exist and (2) failed
-
             to fetch - this needs a change to the backend. -->
-
        API request to <code class="text-xsmall">{err.url}</code> failed
-
      </div>
-
    </div>
-
  {/await}
</main>
added src/base/projects/Header.svelte
@@ -0,0 +1,292 @@
+
<script lang="ts">
+
  import type { Config } from '@app/config';
+
  import * as utils from '@app/utils';
+
  import Loading from '@app/Loading.svelte';
+
  import { Org } from '@app/base/orgs/Org';
+
  import type { Info, Tree } from '@app/project';
+
  import type { Profile } from '@app/profile';
+
  import { createEventDispatcher } from "svelte";
+
  
+
  export let config: Config;
+
  export let anchors: string | null = null;
+
  export let urn: string;
+
  export let commit: string;
+
  export let project: Info;
+
  export let profile: Profile | null = null;
+
  export let tree: Tree;
+

+
  // Whether the clone dropdown is visible.
+
  let cloneDropdown = false;
+
  // Whether the seed dropdown is visible.
+
  let seedDropdown = false;
+
  const dispatch = createEventDispatcher();
+
  const onClick = ({ detail: newCommit }: { detail: string }): void => {
+
    dispatch("commitChange", newCommit);
+
  };
+

+
  $: getAnchor = anchors ? Org.getAnchor(anchors, urn, config) : null;
+
</script>
+

+
<style>
+
  header {
+
    font-size: 0.75rem;
+
    padding: 0 2rem 0 8rem;
+
    margin-bottom: 2rem;
+
    display: flex;
+
    align-items: center;
+
    justify-content: left;
+
    flex-wrap: wrap;
+
    gap: 0.5rem;
+

+
  }
+
  header > * {
+
    border-radius: 0.25rem;
+
    min-width: max-content;
+
  }
+

+
  .commit {
+
    display: flex;
+
    align-items: center;
+
    justify-content: center;
+
    font-family: var(--font-family-monospace);
+
  }
+
  .commit .branch {
+
    padding: 0.5rem 0.75rem;
+
    color: var(--color-secondary);
+
    background-color: var(--color-secondary-background);
+
    border-radius: inherit;
+
    border-top-right-radius: 0;
+
    border-bottom-right-radius: 0;
+
  }
+
  .commit .hash {
+
    display: inline-block;
+
    color: var(--color-secondary);
+
    background-color: var(--color-secondary-background);
+
    padding: 0.5rem 0.75rem;
+
    border-radius: inherit;
+
  }
+
  .branch + .hash {
+
    background-color: var(--color-secondary-background-darker);
+
    border-radius: inherit;
+
    border-top-left-radius: 0;
+
    border-bottom-left-radius: 0;
+
  }
+

+
  .anchor {
+
    display: inline-flex;
+
  }
+
  .anchor-widget {
+
    display: flex;
+
    padding: 0.5rem 0.75rem;
+
    border-radius: inherit;
+
    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-label {
+
    font-family: var(--font-family-monospace);
+
    margin-right: 0.5rem;
+
  }
+
  .anchor-label:last-child {
+
    margin-right: 0;
+
  }
+
  .anchor-latest {
+
    cursor: default;
+
  }
+

+
  .seed {
+
    cursor: pointer;
+
    border-radius: inherit;
+
  }
+
  .seed:hover {
+
    background-color: var(--color-foreground-background-lighter);
+
  }
+

+
  .clone {
+
    color: var(--color-primary);
+
    background-color: var(--color-primary-background);
+
    font-family: var(--font-family-monospace);
+
    padding: 0.5rem 0.75rem;
+
    border-radius: 0.25rem;
+
    cursor: pointer;
+
    user-select: none;
+
  }
+
  .clone:hover {
+
    background-color: var(--color-primary-background-lighter);
+
  }
+
  .dropdown {
+
    background-color: var(--color-foreground-background);
+
    padding: 1rem;
+
    margin-top: 0.5rem;
+
    border-radius: 0.25rem;
+
    display: none;
+
    position: absolute;
+
  }
+
  .clone-dropdown.clone-dropdown-visible {
+
    display: block;
+
  }
+
  .clone-dropdown input {
+
    color: var(--color-primary);
+
    background: var(--color-primary-background);
+
  }
+
  .seed-dropdown input {
+
    color: var(--color-foreground-90);
+
    background: var(--color-foreground-background-lighter);
+
  }
+
  .seed-dropdown.seed-dropdown-visible {
+
    display: block;
+
  }
+
  .dropdown input {
+
    font-size: 0.75rem;
+
    font-family: var(--font-family-monospace);
+
    padding: 0.5rem;
+
    border: none;
+
    outline: none;
+
    width: 24rem;
+
    text-overflow: ellipsis !important;
+
    border-radius: 0.25rem;
+
  }
+
  .dropdown label {
+
    display: block;
+
    color: var(--color-foreground-faded);
+
    padding: 0.5rem 0.5rem 0 0.25rem;
+
    font-size: 0.75rem;
+
  }
+

+
  .stat {
+
    font-family: var(--font-family-monospace);
+
    padding: 0.5rem 0.75rem;
+
    background: var(--color-foreground-background);
+
  }
+
  @media (max-width: 960px) {
+
    header {
+
      padding-left: 2rem;
+
    }
+
    header {
+
      margin-bottom: 1.5rem;
+
    }
+
  }
+
  @media (max-width: 720px) {
+
    .dropdown {
+
      left: 32px;
+
      z-index: 10;
+
    }
+
  }
+
</style>
+

+
<header>
+
  <div class="commit">
+
    {#if commit === project.head}
+
      <div class="branch">
+
        {project.meta.defaultBranch}
+
      </div>
+
      <div class="hash">
+
        {utils.formatCommit(commit)}
+
      </div>
+
    {:else}
+
      <div class="hash desktop">
+
        {commit}
+
      </div>
+
      <div class="hash mobile">
+
        {utils.formatCommit(commit)}
+
      </div>
+
    {/if}
+
  </div>
+
  <div class="anchor">
+
    {#if anchors}
+
      {#await getAnchor}
+
        <Loading small margins />
+
      {:then anchor}
+
        {#if anchor === commit}
+
          {#if commit === project.head}
+
            <span class="anchor-widget anchor-latest">
+
              <span class="anchor-label" title={anchors}>anchored 🔒</span>
+
            </span>
+
          {:else}
+
            <span
+
              class="anchor-widget"
+
              on:click={() => onClick({ detail: project.head })}
+
            >
+
              <span class="anchor-label" title={anchors}>anchored 🔒</span>
+
            </span>
+
          {/if}
+
        {:else if anchor}
+
          <span
+
            class="anchor-widget not-anchored"
+
            on:click={() => onClick({ detail: anchor })}
+
          >
+
            <span class="anchor-label">not anchored 🔓</span>
+
          </span>
+
        {:else}
+
          <span class="anchor-widget not-anchored not-allowed">
+
            <span class="anchor-label">not anchored 🔓</span>
+
          </span>
+
        {/if}
+
      {/await}
+
    {/if}
+
  </div>
+
  {#if config.seed.git.host}
+
    <span>
+
      <div class="clone" on:click={() => (cloneDropdown = !cloneDropdown)}>
+
        Clone
+
      </div>
+
      <div
+
        class="dropdown clone-dropdown"
+
        class:clone-dropdown-visible={cloneDropdown}
+
      >
+
        <input
+
          readonly
+
          name="clone-url"
+
          value="https://{config.seed.git.host}/{utils.parseRadicleId(urn)}"
+
        />
+
        <label for="clone-url"
+
          >Use Git to clone this repository from the URL above.</label
+
        >
+
      </div>
+
    </span>
+
  {/if}
+
  <span>
+
    {#if config.seed.api.host}
+
      <div
+
        class="stat seed"
+
        on:click={() => (seedDropdown = !seedDropdown)}
+
        title="Project data is fetched from this seed"
+
      >
+
        <span>{config.seed.api.host}</span>
+
      </div>
+
    {/if}
+
    <div
+
      class="dropdown seed-dropdown"
+
      class:seed-dropdown-visible={seedDropdown}
+
    >
+
      {#if config.seed.link.id && config.seed.link.host}
+
        <input
+
          readonly
+
          name="clone-url"
+
          value={utils.formatSeedAddress(
+
            config.seed.link.id,
+
            config.seed.link.host,
+
            config
+
          )}
+
        />
+
        <label for="seed-url">Bootstrap your Radicle node with this seed.</label
+
        >
+
      {:else if profile}
+
        <label for="#">Seed ID is not set for {profile.name}.</label>
+
      {/if}
+
    </div>
+
  </span>
+
  <div class="stat">
+
    <strong>{tree.stats.commits}</strong> commit(s)
+
  </div>
+
  <div class="stat">
+
    <strong>{tree.stats.contributors}</strong> contributor(s)
+
  </div>
+
</header>
modified src/base/projects/View.svelte
@@ -10,6 +10,7 @@
  import { formatOrg } from '@app/utils';

  import Browser from './Browser.svelte';
+
  import Header from './Header.svelte';

  export let urn: string;
  export let org = "";
@@ -46,6 +47,10 @@
      : `/users/${profile.nameOrAddress}`;
  };

+
  const onCommitChange = ({ detail: newCommit }: { detail: string }): void => {
+
    commit = newCommit;
+
  };
+

  $: if (projectInfo) {
    const baseName = parentName
      ? `${parentName}/${projectInfo.meta.name}`
@@ -142,12 +147,28 @@
      <div class="urn">{urn}</div>
      <div class="description">{result.project.meta.description}</div>
    </header>
-
    <Browser {urn} {org} {user} {path}
-
      anchors={result.profile?.anchorsAccount ?? org}
-
      profile={result.profile}
-
      project={result.project}
-
      commit={commit || result.project.head}
-
      config={result.config} />
+
    {#await proj.getTree(urn, commit, "/", config)}
+
      <!-- Loading -->
+
    {:then tree}
+
      <Header {urn} {tree}
+
        anchors={result.profile?.anchorsAccount ?? org}
+
        commit={commit || result.project.head}
+
        config={result.config}
+
        project={result.project}
+
        on:commitChange={onCommitChange}
+
      /> 
+
      <Browser {urn} {org} {user} {path} {tree}
+
        commit={commit || result.project.head}
+
        config={result.config} />
+
    {:catch err}
+
      <div class="container center-content">
+
        <div class="error error-message text-xsmall">
+
          <!-- TODO: Differentiate between (1) commit doesn't exist and (2) failed
+
               to fetch - this needs a change to the backend. -->
+
          API request to <code class="text-xsmall">{err.url}</code> failed
+
        </div>
+
      </div>
+
    {/await}
  {:catch}
    <Modal subtle>
      <span slot="title">🏜️</span>