Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add peer selector
Sebastian Martinez committed 4 years ago
commit 83373ca507cb690bc09e8f1c11788ad3b6457a47
parent 0a5fd211f847099837ee53519491c867bffc63d2
9 files changed +188 -38
modified src/Icon.svelte
@@ -24,6 +24,11 @@
      name: "ellipsis",
      size: 24,
      data: `<path d="M7 12a2 2 0 1 1-4.001-.001A2 2 0 0 1 7 12zm12-2a2 2 0 1 0 .001 4.001A2 2 0 0 0 19 10zm-7 0a2 2 0 1 0 .001 4.001A2 2 0 0 0 12 10z"/>`
+
    },
+
    {
+
      name: "fork",
+
      size: 17,
+
      data: `<circle cx="6.5" cy="13.5" r="2" stroke="#5555FF"/><circle cx="10.5" cy="2.5" r="2" stroke="#5555FF"/><circle cx="2.5" cy="2.5" r="2" stroke="#5555FF"/><path d="M6.5 11.5C6.5 7 2.5 8 2.5 5.5C2.5 3.9 2.5 4.66667 2.5 4" stroke="#5555FF"/><path d="M6.5 11.5C6.5 7 10.5 8 10.5 5.5C10.5 3.9 10.5 4.66667 10.5 4" stroke="#5555FF"/>`
    }
  ];
  const svg = icons.find(e => e.name === name);
modified src/base/projects/BranchSelector.svelte
@@ -22,10 +22,16 @@
    return 0;
  };

+
  let branchLabel: string;
  branches = branches.sort(sortBranches);

-
  // Casting commit to string, since the commit will always be defined here
  $: commit = getOid(project.head, revision, branches);
+
  $: isLabel = commit == project.head || !isOid(revision);
+
  $: if (commit == project.head) {
+
    branchLabel = project.meta.defaultBranch;
+
  } else if (!isOid(revision)) {
+
    branchLabel = revision;
+
  }
</script>

<style>
@@ -70,6 +76,15 @@
    display: none;
    position: absolute;
  }
+
  .hidden {
+
    display: none;
+
  }
+
  .pointer {
+
    cursor: pointer;
+
  } 
+
  .branch-dropdown.branch-dropdown-without-label {
+
    margin-top: 1.6rem;
+
  }
  .branch-dropdown.branch-dropdown-visible {
    display: block;
  }
@@ -90,18 +105,12 @@
  <!-- Check for branches listing feature -->
  {#if branches.length > 0}
    <span>
-
      <div on:click={() => toggleDropdown("branch")} class="stat branch" class:not-allowed={!branches}>
-
        {#if commit === project.head}
-
          {project.meta.defaultBranch}
-
        <!-- If commit is no sha1 commit show branch or tag name -->
-
        {:else if !isOid(revision)}
-
          {revision}
-
        {:else}
-
          Browse...
-
        {/if}
+
      <div on:click={() => toggleDropdown("branch")} class="stat branch" class:not-allowed={!branches} class:hidden={!isLabel}>
+
        {branchLabel}
      </div>
      <div
        class="dropdown branch-dropdown"
+
        class:branch-dropdown-without-label={!isLabel}
        class:branch-dropdown-visible={branchesDropdown}
      >
        {#each branches as [name,]}
@@ -109,15 +118,15 @@
        {/each}
      </div>
    </span>
-
    {#if commit === project.head || !isOid(revision)}
+
    {#if isLabel}
      <div class="hash">
        {formatCommit(commit)}
      </div>
    {:else}
-
      <div class="hash desktop">
+
      <div class="hash desktop" class:pointer={!isLabel} on:click={() => toggleDropdown("branch")}>
        {commit}
      </div>
-
      <div class="hash mobile">
+
      <div class="hash mobile" class:pointer={!isLabel} on:click={() => toggleDropdown("branch")}>
        {formatCommit(commit)}
      </div>
    {/if}
modified src/base/projects/Browser.svelte
@@ -23,6 +23,7 @@
  export let user: string | null = null;
  export let seed: string | null = null;
  export let tree: proj.Tree;
+
  export let peer: proj.Peer;
  export let project: proj.Info;
  export let branches: [string, string][];
  export let locator: string; // eg. "master/README.md"
@@ -76,13 +77,13 @@
    if (path === undefined) path = state.path;

    if (org) {
-
      navigate(proj.path({ urn, org, revision, path }));
+
      navigate(proj.path({ peer, urn, org, revision, path }));
    } else if (user) {
-
      navigate(proj.path({ urn, user, revision, path }));
+
      navigate(proj.path({ peer, urn, user, revision, path }));
    } else if (seed) {
-
      navigate(proj.path({ urn, seed, revision, path }));
+
      navigate(proj.path({ peer, urn, seed, revision, path }));
    } else {
-
      navigate(proj.path({ urn, revision, path }));
+
      navigate(proj.path({ peer, urn, revision, path }));
    }
  };

modified src/base/projects/Header.svelte
@@ -7,6 +7,7 @@
  import type { Info, Tree } from "@app/project";
  import type { Profile } from '@app/profile';
  import BranchSelector from './BranchSelector.svelte';
+
  import PeerSelector from './PeerSelector.svelte';
  import { createEventDispatcher } from 'svelte';

  const dispatch = createEventDispatcher();
@@ -21,8 +22,14 @@
  export let branches: [string, string][] = [];
  export let content: ProjectContent;
  export let revision: string;
+
  // If peerSelector should be showed.
+
  export let peerSelector: boolean;
+
  // Currently selected peer.
+
  export let peer: string;
+
  // Listing of available peers, empty array if none available.
+
  export let peers: string[];

-
  let dropdownState: { [key: string]: boolean } = { clone: false, seed: false, branch: false };
+
  let dropdownState: { [key: string]: boolean } = { clone: false, seed: false, branch: false, peer: false };
  function toggleDropdown(input: string) {
    Object.keys(dropdownState).map((key: string) => {
      if (input === key) dropdownState[key] = !dropdownState[key];
@@ -32,11 +39,15 @@

  // Switches between the browser and commit view
  const toggleContent = (input: ProjectContent) => {
-
    dispatch("routeParamsChange", { content: content === input ? ProjectContent.Tree : input, revision, path });
+
    dispatch("routeParamsChange", { content: content === input ? ProjectContent.Tree : input, revision, peer, path });
+
  };
+

+
  const updatePeer = (newPeer: string) => {
+
    dispatch("routeParamsChange", { content, revision, peer: newPeer, path });
  };

  const updateRevision = (newRevision: string) => {
-
    dispatch("routeParamsChange", { content, revision: newRevision, path });
+
    dispatch("routeParamsChange", { content, revision: newRevision, peer, path });
  };

  const GetAllAnchors = `
@@ -209,11 +220,16 @@
</style>

<header>
+
  {#if peers.length > 0 && peerSelector}
+
    <PeerSelector {peers} {toggleDropdown} {peer}
+
      bind:peersDropdown={dropdownState.peer}
+
      on:peerChanged={(event) => updatePeer(event.detail)} />
+
  {/if}
  <BranchSelector {branches} {project} {revision} {toggleDropdown}
    bind:branchesDropdown={dropdownState.branch}
    on:revisionChanged={(event) => updateRevision(event.detail)} />
-
  <div class="anchor">
-
    {#if anchors}
+
  {#if anchors}
+
    <div class="anchor">
      {#await getAnchor}
        <Loading small margins />
      {:then anchor}
@@ -250,8 +266,8 @@
          <span class="anchor-label">❌</span>
        </span>
      {/await}
-
    {/if}
-
  </div>
+
    </div>
+
  {/if}
  {#if config.seed.git.host}
    <span>
      <div class="clone" on:click={() => toggleDropdown("clone")}>
added src/base/projects/PeerSelector.svelte
@@ -0,0 +1,88 @@
+
<script lang="ts">
+
  import Icon from "@app/Icon.svelte";
+
  import { formatSeedId } from "@app/utils";
+
  import { createEventDispatcher } from "svelte";
+

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

+
  const dispatch = createEventDispatcher();
+

+
  const switchPeer = (peer: string) => {
+
    dispatch("peerChanged", peer);
+
  };
+
</script>
+

+
<style>
+
  .selector {
+
    display: flex;
+
    align-items: center;
+
    justify-content: center;
+
    font-family: var(--font-family-monospace);
+
  }
+
  .selector .peer {
+
    cursor: pointer;
+
    padding: 0.5rem 0.75rem;
+
    color: var(--color-secondary);
+
    background-color: var(--color-secondary-background);
+
    border-radius: 0.25rem;
+
  }
+
  .selector .peer.not-allowed {
+
    cursor: not-allowed;
+
  }
+
  .peer:hover {
+
    background-color: var(--color-foreground-background-lighter);
+
  }
+
  .item {
+
    cursor: pointer;
+
    padding: 0.3rem;
+
  }
+
  .item:hover {
+
    background-color: var(--color-foreground-background-lighter);
+
  }
+
  .dropdown {
+
    background-color: var(--color-foreground-background);
+
    padding: 1rem;
+
    margin-top: 0.5rem;
+
    border-radius: 0.25rem;
+
    display: none;
+
    position: absolute;
+
  }
+
  .peer-dropdown.peer-dropdown-visible {
+
    display: block;
+
  }
+
  .stat {
+
    display: flex;
+
    align-items: center;
+
    font-family: var(--font-family-monospace);
+
    padding: 0.5rem 0.75rem;
+
    background: var(--color-foreground-background);
+
  }
+
  @media (max-width: 720px) {
+
    .dropdown {
+
      left: 32px;
+
      z-index: 10;
+
    }
+
  }
+
</style>
+

+
<div class="selector">
+
  <span>
+
    <div on:click={() => toggleDropdown("peer")} class="stat peer" class:not-allowed={!peers}>
+
      <Icon name="fork" width={25} height={17} />
+
      {#if peer}
+
        {formatSeedId(peer)}
+
      {/if}
+
    </div>
+
    <div
+
      class="dropdown peer-dropdown"
+
      class:peer-dropdown-visible={peersDropdown}
+
    >
+
      {#each peers as peer}
+
        <div class="item" on:click={() => switchPeer(peer)}>{peer}</div>
+
      {/each}
+
    </div>
+
  </span>
+
</div>
modified src/base/projects/ProjectContentRoutes.svelte
@@ -1,6 +1,6 @@
<script lang="ts">
  import type { Config } from "@app/config";
-
  import type { Info, ProjectContent, Tree } from "@app/project";
+
  import type { Info, Peer, ProjectContent, Tree } from "@app/project";
  import { Route, Router } from "svelte-routing";
  import Browser from "./Browser.svelte";
  import History from "./Commit/History.svelte";
@@ -12,6 +12,7 @@
  export let tree: Tree;
  export let user: string;
  export let seed: string;
+
  export let peer: Peer;
  export let branches: [string, string][];
  export let content: ProjectContent;
  export let revision: string;
@@ -21,21 +22,21 @@
<Router>
  <!-- The default action is to render Browser with the default branch head -->
  <Route path="/">
-
    <Browser {urn} {org} {user} {seed} {config} {tree} {project} {branches}
+
    <Browser {urn} {org} {user} {seed} {config} {tree} {project} {branches} {peer}
      locator={project.head}
      bind:content={content}
      bind:path={path}
      bind:revision={revision} />
  </Route>
  <Route path="/tree">
-
    <Browser {urn} {org} {user} {seed} {config} {tree} {project} {branches}
+
    <Browser {urn} {org} {user} {seed} {config} {tree} {project} {branches} {peer}
      locator={project.head}
      bind:content={content}
      bind:path={path}
      bind:revision={revision} />
  </Route>
  <Route path="/tree/*" let:params>
-
    <Browser {urn} {org} {user} {seed} {config} {tree} {project} {branches}
+
    <Browser {urn} {org} {user} {seed} {config} {tree} {project} {branches} {peer}
      locator={params["*"]}
      bind:content={content}
      bind:path={path}
modified src/base/projects/Routes.svelte
@@ -13,6 +13,10 @@
  <View {config} seed={params.seed} urn={params.urn} />
</Route>

+
<Route path="/seeds/:seed/projects/:urn/remotes/:peer/*" let:params>
+
  <View {config} seed={params.seed} peer={params.peer} urn={params.urn} />
+
</Route>
+

<!-- With an Org context -->

<Route path="/orgs/:org/projects/:urn/*" let:params>
modified src/base/projects/View.svelte
@@ -7,7 +7,7 @@
  import Avatar from '@app/Avatar.svelte';
  import { Profile, ProfileType } from '@app/profile';
  import type { Info } from '@app/project';
-
  import { formatOrg } from '@app/utils';
+
  import { formatOrg, formatSeedId } from '@app/utils';
  import { getOid } from '@app/project';
  import { Seed } from '@app/base/seeds/Seed';

@@ -18,6 +18,7 @@
  export let org = "";
  export let user = "";
  export let seed = "";
+
  export let peer = "";
  export let config: Config;

  let parentName = formatOrg(org || user, config);
@@ -42,13 +43,19 @@
    const cfg = seed ? config.withSeed(seed) : config;
    const info = await proj.getInfo(urn, cfg);
    projectInfo = info;
+
    let branches = Array([info.meta.defaultBranch, info.head]) as [string, string][];
+
    let peers: proj.Peer[] = [];

    // Checks for delegates returned from seed node, as feature check of the seed node
    if (info.meta.delegates) {
-
      const branches = await proj.getBranchesByPeer(urn, info.meta.delegates[0], cfg);
-
      return { project: info, branches: [...Object.entries(branches.heads)], peer: info.meta.delegates[0], config: cfg, profile };
+
      // Check for selected peer to override available branches.
+
      if (peer) {
+
        const branchesByPeer = await proj.getBranchesByPeer(urn, peer || info.meta.delegates[0], cfg);
+
        branches = [...Object.entries(branchesByPeer.heads)];
+
      }
+
      peers = await proj.getPeers(urn, cfg);
    }
-
    return { project: info, branches: Array([info.meta.defaultBranch, info.head]) as [string, string][], config: cfg, profile };
+
    return { project: info, branches, peers, config: cfg, profile };
  });

  const parentUrl = (profile: Profile) => {
@@ -69,14 +76,15 @@
    }
  }

-
  function updateRouteParams({ detail: newParams }: { detail: { path: string; revision: string; content: proj.ProjectContent } }) {
-
    let newLocation = proj.path({ urn, user, org, seed, content: newParams.content, revision: newParams.revision, path: newParams.path });
+
  function updateRouteParams({ detail: newParams }: { detail: { path: string; revision: string; peer: string; content: proj.ProjectContent } }) {
+
    let newLocation = proj.path({ urn, user, org, seed, content: newParams.content, peer: newParams.peer, revision: newParams.revision, path: newParams.path });
    if (newLocation !== window.location.pathname) {
      navigate(newLocation);
    }
    if (content !== newParams.content) content = newParams.content;
    if (revision !== newParams.revision) revision = newParams.revision;
    if (path !== newParams.path) path = newParams.path;
+
    if (peer !== newParams.peer) peer = newParams.peer;
  }

  const back = () => window.history.back();
@@ -161,19 +169,24 @@
          <span class="divider">/</span>
        {/if}
        <Link to={projectRoot}>{result.project.meta.name}</Link>
+
        {#if peer}
+
          <span class="divider" title={peer}>/ {formatSeedId(peer)}</span> 
+
        {/if}
      </div>
      <div class="urn">{urn}</div>
      <div class="description">{result.project.meta.description}</div>
    </header>
    {#await proj.getTree(urn, getOid(result.project.head, revision, result.branches), "/", config) then tree}
-
      <Header {urn} {tree} {revision} {content} {path}
+
      <Header {urn} {tree} {revision} {content} {path} {peer}
        anchors={result.profile?.anchorsAccount ?? org}
+
        peerSelector={!!seed}
        config={result.config}
        project={result.project}
        branches={result.branches}
        profile={result.profile}
+
        peers={result.peers}
        on:routeParamsChange={updateRouteParams} />
-
      <ProjectContentRoutes {urn} {org} {user} {seed} {tree}
+
      <ProjectContentRoutes {urn} {org} {user} {seed} {tree} {peer}
        project={result.project}
        branches={result.branches}
        config={result.config}
modified src/project.ts
@@ -7,6 +7,10 @@ export type Urn = string;
export type Peer = string;
export type Branch = { [key: string]: string };

+
export interface ProjectListing {
+
  name: string;
+
  urn: Urn;
+
}
export interface Project {
  id: string;
  anchor: {
@@ -101,7 +105,7 @@ export async function getCommits(urn: string, commit: string, config: Config): P
  return api.get(`projects/${urn}/commits/${commit}`, {}, config);
}

-
export async function getProjects(config: Config): Promise<any> {
+
export async function getProjects(config: Config): Promise<ProjectListing[]> {
  return api.get("projects", {}, config);
}

@@ -109,6 +113,10 @@ export async function getBranchesByPeer(urn: string, peer: string, config: Confi
  return api.get(`projects/${urn}/remotes/${peer}`, {}, config);
}

+
export async function getPeers(urn: string, config: Config): Promise<Peer[]> {
+
  return api.get(`projects/${urn}/remotes`, {}, config);
+
}
+

export async function getTree(
  urn: string,
  commit: string,
@@ -143,12 +151,13 @@ export function path(
    org?: string;
    user?: string;
    seed?: string;
+
    peer?: string;
    content?: ProjectContent;
    revision?: string;
    path?: string;
  }
): string {
-
  const { urn, org, user, seed, content, revision, path } = opts;
+
  const { urn, org, user, seed, peer, content, revision, path } = opts;
  const result = [];

  if (org) {
@@ -160,6 +169,10 @@ export function path(
  }
  result.push("projects", urn);

+
  if (peer) {
+
    result.push("remotes", peer);
+
  }
+

  switch (content) {
    case ProjectContent.History:
      result.push("history");