mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-27 15:40:36 +08:00
Add delegate info to peer selector
This commit also adds a highlight to the currently selected peer and branch. Signed-off-by: Sebastian Martinez <sebastinez@me.com> Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
@@ -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);
|
||||
|
||||
+13
-5
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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)}
|
||||
/>
|
||||
|
||||
+10
-4
@@ -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)
|
||||
: [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user