Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add delegate info to peer selector
Sebastian Martinez committed 4 years ago
commit 856b53dae82c161daec8a97cb282a35f3bb84737
parent d69907be4534b5440077118af938bf51f7c7caf4
7 files changed +149 -21
modified public/index.css
@@ -31,6 +31,7 @@
	--color-light: #add0e4;
	--color-yellow: #ffff99;
	--color-yellow-background: #ffff9911;
+
	--color-yellow-background-lighter: #ffff9922;
	--color-positive: #53db53;
	--color-positive-background: #53db5311;
	--color-positive-1: #11332b;
@@ -51,6 +52,7 @@
	--color-foreground-faded: #777788;
	--color-foreground-subtle: #444455;
	--color-foreground-subtler: #333344;
+
	--color-foreground-even-subtler: #292936;
	--color-foreground-background: #121a21;
	--color-foreground-background-subtle: #10171e;
	--color-foreground-background-lighter: #151f24;
@@ -337,6 +339,10 @@ label.input {
	font-size: 0.75rem;
	line-height: 1.6;
}
+
.badge.primary {
+
	color: var(--color-primary);
+
	background: var(--color-primary-background);
+
}
.badge.tertiary {
	color: var(--color-tertiary);
	background: var(--color-tertiary-background);
modified src/Dropdown.svelte
@@ -1,7 +1,8 @@
<script lang="ts">
  import { createEventDispatcher } from "svelte";

-
  export let items: string[];
+
  export let items: { key: string; value: string; badge: string | null }[];
+
  export let selected: string | null;
  export let visible = false;

  const dispatch = createEventDispatcher();
@@ -17,15 +18,18 @@
    margin-top: 0.5rem;
    border-radius: 0.25rem;
    position: absolute;
-
    box-shadow: 8px 8px 24px var(--color-shadow);
+
    box-shadow: 16px 16px 32px 32px var(--color-shadow);
  }
  .dropdown-item {
    cursor: pointer;
    padding: 0.5rem 1rem;
  }
-
  .dropdown-item:hover {
+
  .dropdown-item:hover, .selected {
    background-color: var(--color-foreground-background-lighter);
  }
+
  .dropdown .badge {
+
    margin: 0;
+
  }

  @media (max-width: 720px) {
    .dropdown {
@@ -37,8 +41,12 @@

{#if visible}
  <div class="dropdown">
-
    {#each items as item}
-
      <div class="dropdown-item" on:click={() => onSelect(item)}>{item}</div>
+
    {#each items as {key, value, badge}}
+
      <div class="dropdown-item" class:selected={value === selected} on:click={() => onSelect(value)} title={value}>{@html key}
+
        {#if badge}
+
          <span class="badge primary">{badge}</span>
+
        {/if}
+
      </div>
    {/each}
  </div>
{/if}
modified src/base/projects/BranchSelector.svelte
@@ -17,7 +17,7 @@

  let branchLabel: string | null = null;

-
  $: branchList = Object.keys(branches).sort();
+
  $: branchList = Object.keys(branches).sort().map(b => ({ key: b, value: b, badge: null }));
  $: showSelector = branchList.length > 1;
  $: head = branches[project.defaultBranch];
  $: commit = getOid(revision, branches) || head;
@@ -81,6 +81,7 @@
        </div>
        <Dropdown
          items={branchList}
+
          selected={branchLabel}
          visible={branchesDropdown}
          on:select={(e) => switchBranch(e.detail)} />
      </span>
modified src/base/projects/Header.svelte
@@ -73,16 +73,16 @@
  }

  .clone {
-
    color: var(--color-primary);
-
    background-color: var(--color-primary-background);
+
    color: var(--color-yellow);
+
    background-color: var(--color-yellow-background);
    font-family: var(--font-family-monospace);
    padding: 0.5rem 0.75rem;
    border-radius: 0.25rem;
    cursor: pointer;
    user-select: none;
  }
-
  .clone:hoverr {
-
    background-color: var(--color-primary-background-lighter);
+
  .clone:hover {
+
    background-color: var(--color-yellow-background-lighter);
  }
  .commit-count {
    cursor: pointer;
@@ -103,8 +103,8 @@
    display: block;
  }
  .clone-dropdown input {
-
    color: var(--color-primary);
-
    background: var(--color-primary-background);
+
    color: var(--color-yellow);
+
    background: var(--color-yellow-background);
  }
  .dropdown input {
    font-size: 0.75rem;
added src/base/projects/PeerSelector.spec.ts
@@ -0,0 +1,80 @@
+
import PeerSelector from "./PeerSelector.svelte";
+
import { mount } from "radicle-svelte-unit-test";
+
import { styles } from "@test/support/index";
+

+
const defaultProps = {
+
  peer: "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
+
  peers: [
+
    {
+
      "id": "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
+
      "name": "sebastinez",
+
      "delegate": true
+
    }
+
  ],
+
  toggleDropdown: () => console.log("toggle"),
+
};
+

+
describe('PeerSelector', function () {
+
  it("Render correctly with default props", () => {
+
    mount(PeerSelector, {
+
      props: defaultProps
+
    }, styles);
+
    cy.get("span.peer-id").should("has.text", "sebastinez");
+
    cy.get("span.badge").should("has.text", "delegate");
+
  });
+

+
  it("Test Peer selection", () => {
+
    mount(PeerSelector, {
+
      props: {
+
        ...defaultProps, peers: [
+
          {
+
            "id": "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
+
            "name": "sebastinez",
+
            "delegate": false
+
          },
+
          {
+
            "id": "hydkkkf5ksbe5fuszdhpqhytu3q36gwagj874wxwpo5a8ti8coygh1",
+
            "name": "cloudhead",
+
            "delegate": true
+
          },
+
        ],
+
        peersDropdown: true
+
      }, callbacks: {
+
        peerChanged: cy.stub().as("peerChanged")
+
      }
+
    }, styles);
+
    cy.get("div.dropdown > div").last().click();
+
    cy.get("@peerChanged")
+
      .should("be.calledOnce")
+
      .its("firstCall.args.0.detail")
+
      .should("equal", "hydkkkf5ksbe5fuszdhpqhytu3q36gwagj874wxwpo5a8ti8coygh1");
+
  });
+
  it("If no peers are provided, no dropdown should be showed", () => {
+
    mount(PeerSelector, {
+
      props: {
+
        ...defaultProps,
+
        peers: [],
+
        peersDropdown: true
+
      }
+
    }, styles);
+
    cy.get("div.dropdown > div.dropdown-item").should("not.exist");
+
  });
+
  it("If peer identity is not being resolved, fallback to peer id", () => {
+
    mount(PeerSelector, {
+
      props: {
+
        ...defaultProps,
+
        peers: [
+
          {
+
            "id": "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
+
          }
+
        ],
+
        peersDropdown: true
+
      }
+
    }, styles);
+
    cy.get("span.peer-id").should("has.text", "hyyg55…p7ofue");
+
    cy.get("span.badge").should("not.exist");
+
    cy.get("div.dropdown > .dropdown-item")
+
      .first()
+
      .should("contain", "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue");
+
  });
+
});
modified src/base/projects/PeerSelector.svelte
@@ -1,14 +1,29 @@
<script lang="ts">
-
  import { createEventDispatcher } from "svelte";
+
  import { createEventDispatcher, onMount } from "svelte";
  import Icon from "@app/Icon.svelte";
  import Dropdown from "@app/Dropdown.svelte";
  import { formatSeedId } from "@app/utils";
+
  import type { Peer } from "@app/project";

  export let peer: string | null = null;
-
  export let peers: (string | { id: string })[];
+
  export let peers: Peer[];
  export let toggleDropdown: (input: string) => void;
  export let peersDropdown = false;

+
  let meta: Peer | undefined;
+
  // List of items to be created for the Dropdown component.
+
  let items: { key: string; value: string; badge: string | null }[] = [];
+

+
  onMount(() => {
+
    meta = peers.find(p => p.id === peer);
+
    items = peers.map(p => {
+
      if (! p.name) console.debug("Not able to resolve peer identity for: ", p.id);
+
      let key = p.name ? `<strong>${p.name}</strong> ${p.id}` : p.id;
+

+
      return { key, value: p.id, badge: p.delegate ? "delegate" : null };
+
    });
+
  });
+

  const dispatch = createEventDispatcher();
  const switchPeer = (peer: string) => {
    dispatch("peerChanged", peer);
@@ -32,8 +47,11 @@
  .selector .peer.not-allowed {
    cursor: not-allowed;
  }
+
  .selector .badge {
+
    margin: 0;
+
  }
  .peer-id {
-
    margin-left: 0.5rem;
+
    margin: 0 0.5rem;
  }
  .peer:hover {
    background-color: var(--color-foreground-background-lighter);
@@ -52,14 +70,23 @@
  <span>
    <div on:click={() => toggleDropdown("peer")} class="stat peer" class:not-allowed={!peers}>
      <Icon name="fork" width={15} height={15} />
-
      {#if peer}
+
      {#if meta}
+
        <span class="peer-id">
+
          {meta.name ?? formatSeedId(meta.id)}
+
        </span>
+
        {#if meta.delegate}
+
          <span class="badge primary">delegate</span>
+
        {/if}
+
      <!-- If the delegate metadata is not found -->
+
      {:else if peer}
        <span class="peer-id">
          {formatSeedId(peer)}
        </span>
      {/if}
    </div>
    <Dropdown
-
      items={peers.map((p) => typeof(p) == "string" ? p : p.id)}
+
      {items}
+
      selected={peer}
      visible={peersDropdown}
      on:select={(e) => switchPeer(e.detail)}
    />
modified src/project.ts
@@ -86,6 +86,12 @@ export interface Remote {
  heads: Branches;
}

+
export interface Peer {
+
  id: PeerId;
+
  name: string;
+
  delegate: boolean;
+
}
+

export interface Browser {
  content: ProjectContent;
  revision: string | null;
@@ -203,12 +209,12 @@ export class Project implements ProjectInfo {
  maintainers: Urn[];
  delegates: PeerId[];
  seed: Seed;
-
  peers: (string | { id: string })[];
+
  peers: Peer[];
  branches: Branches;
  profile: Profile | null;
  anchors: string[];

-
  constructor(urn: string, info: ProjectInfo, seed: Seed, peers: (string | { id: string })[], branches: Branches, profile: Profile | null, anchors: string[]) {
+
  constructor(urn: string, info: ProjectInfo, seed: Seed, peers: Peer[], branches: Branches, profile: Profile | null, anchors: string[]) {
    this.urn = urn;
    this.head = info.head;
    this.name = info.name;
@@ -258,7 +264,7 @@ export class Project implements ProjectInfo {
    return api.get(`projects/${urn}/remotes/${peer}`, {}, host);
  }

-
  static async getRemotes(urn: string, host: api.Host): Promise<(PeerId | { id: string })[]> {
+
  static async getRemotes(urn: string, host: api.Host): Promise<Peer[]> {
    return api.get(`projects/${urn}/remotes`, {}, host);
  }

@@ -331,7 +337,7 @@ export class Project implements ProjectInfo {
    // Older versions of http-api don't include the URN.
    if (! info.urn) info.urn = urn;

-
    const peers: (PeerId | { id: string })[] = info.delegates
+
    const peers: Peer[] = info.delegates
      ? await Project.getRemotes(urn, seed.api)
      : [];