Skip to content

Commit

Permalink
Add breadcrumbs
Browse files Browse the repository at this point in the history
  • Loading branch information
rudolfs committed Sep 12, 2024
1 parent 33e0b52 commit 66f1b48
Show file tree
Hide file tree
Showing 10 changed files with 354 additions and 46 deletions.
46 changes: 46 additions & 0 deletions src/components/Avatar.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script lang="ts">
import { createIcon } from "@app/lib/blockies";
export let nodeId: string;
function createContainer(source: string) {
source = source.replace("did:key:", "");
const seed = source.toLowerCase();
const avatar = createIcon({
seed,
size: 8,
scale: 16,
});
return avatar.toDataURL();
}
</script>

<style>
.avatar {
display: block;
width: inherit;
object-fit: cover;
background-size: cover;
background-repeat: no-repeat;
width: 1rem;
height: 1rem;
clip-path: polygon(
0 2px,
2px 2px,
2px 0,
calc(100% - 2px) 0,
calc(100% - 2px) 2px,
100% 2px,
100% calc(100% - 2px),
calc(100% - 2px) calc(100% - 2px),
calc(100% - 2px) calc(100% - 2px),
calc(100% - 2px) 100%,
2px 100%,
2px calc(100% - 2px),
0 calc(100% - 2px)
);
background-color: red;
}
</style>

<img title={nodeId} src={createContainer(nodeId)} class="avatar" alt="avatar" />
91 changes: 54 additions & 37 deletions src/components/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
.header {
padding: 0 0.5rem;
gap: 0.25rem;
height: 3rem;
height: 5rem;
}
.wrapper {
width: 100%;
Expand All @@ -27,7 +27,7 @@
top: 0;
left: 0.5rem;
right: 0.5rem;
height: 3rem;
height: 5rem;
z-index: -1;
background-color: var(--color-background-float);
Expand All @@ -53,44 +53,61 @@
</style>

<div class="header global-flex">
<div class="wrapper global-flex">
<div class="wrapper-left global-flex" style:gap="0">
<div class="global-flex" style:gap="0">
<NakedButton
variant="ghost"
onclick={() => {
window.history.back();
}}>
<Icon name="arrow-left" />
</NakedButton>
<NakedButton
variant="ghost"
onclick={() => {
window.history.forward();
}}>
<Icon name="arrow-right" />
</NakedButton>
<div
class="global-flex"
style:flex-direction="column"
style:width="100%"
style:align-items="flex-start">
<div class="wrapper global-flex">
<div class="wrapper-left global-flex" style:gap="0">
<div class="global-flex" style:gap="0">
<NakedButton
variant="ghost"
onclick={() => {
window.history.back();
}}>
<Icon name="arrow-left" />
</NakedButton>
<NakedButton
variant="ghost"
onclick={() => {
window.history.forward();
}}>
<Icon name="arrow-right" />
</NakedButton>
</div>
<slot name="icon-left" />
</div>
<slot name="icon-left" />
</div>

<slot name="center" />
<slot name="center" />

<div class="global-flex" style:gap="0.5rem">
<OutlineButton variant="ghost">
<Icon name="offline" />
Offline
</OutlineButton>
<Popover popoverPositionRight="0" popoverPositionTop="3rem">
<NakedButton variant="ghost" slot="toggle" let:toggle onclick={toggle}>
<Icon name="more-vertical" />
</NakedButton>
<Border variant="ghost" slot="popover" stylePadding="0.5rem 1rem">
<div style="display: flex; gap: 2rem; align-items: center;">
Theme <ThemeSwitch />
</div>
</Border>
</Popover>
<div class="global-flex" style:gap="0.5rem">
<OutlineButton variant="ghost">
<Icon name="offline" />
Offline
</OutlineButton>
<Popover popoverPositionRight="0" popoverPositionTop="3rem">
<NakedButton
variant="ghost"
slot="toggle"
let:toggle
onclick={toggle}>
<Icon name="more-vertical" />
</NakedButton>
<Border variant="ghost" slot="popover" stylePadding="0.5rem 1rem">
<div style="display: flex; gap: 2rem; align-items: center;">
Theme <ThemeSwitch />
</div>
</Border>
</Popover>
</div>
</div>
<div
class="global-flex txt-tiny txt-semibold"
style:gap="0.5rem"
style:margin-left="1rem"
style:min-height="1.5rem">
<slot name="breadcrumbs" />
</div>
</div>
<div class="bottom-pixel-corners"></div>
Expand Down
40 changes: 40 additions & 0 deletions src/components/NodeId.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script lang="ts">
import { parseNodeId, truncateId } from "@app/lib/utils";
import Avatar from "./Avatar.svelte";
export let nodeId: string;
export let alias: string | undefined = undefined;
export let styleFontSize: string | undefined = "var(--font-size-small)";
export let styleFontFamily: string | undefined =
"var(--font-family-monospace)";
</script>

<style>
.avatar-alias {
display: flex;
align-items: center;
gap: 0.375rem;
height: 1rem;
font-weight: var(--font-weight-semibold);
}
.no-alias {
color: var(--color-foreground-dim);
}
</style>

<div
class="avatar-alias"
style:font-size={styleFontSize}
style:font-family={styleFontFamily}>
<Avatar {nodeId} />
{#if alias}
<span class="txt-overflow">
{alias}
</span>
{:else}
<span class="no-alias">
{truncateId(parseNodeId(nodeId)?.pubkey || "")}
</span>
{/if}
</div>
125 changes: 125 additions & 0 deletions src/lib/blockies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (c) 2019, Ethereum Name Service

// The random number is a js implementation of the Xorshift PRNG
const randseed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values

function seedrand(seed: string) {
for (let i = 0; i < randseed.length; i++) {
randseed[i] = 0;
}
for (let i = 0; i < seed.length; i++) {
randseed[i % 4] =
(randseed[i % 4] << 5) - randseed[i % 4] + seed.charCodeAt(i);
}
}

function rand(): number {
// Based on Java's String.hashCode(), expanded to 4 32bit values.
const t = randseed[0] ^ (randseed[0] << 11);

randseed[0] = randseed[1];
randseed[1] = randseed[2];
randseed[2] = randseed[3];
randseed[3] = randseed[3] ^ (randseed[3] >> 19) ^ t ^ (t >> 8);

return (randseed[3] >>> 0) / ((1 << 31) >>> 0);
}

function createColor(): string {
// Saturation is the whole color spectrum.
const h = Math.floor(rand() * 360);
// Saturation goes from 40 to 100, it avoids greyish colors.
const s = rand() * 60 + 40 + "%";
// Lightness can be anything from 0 to 100, but probabilities are a bell curve around 50%.
const l = (rand() + rand() + rand() + rand()) * 25 + "%";

return `hsl(${h}, ${s}, ${l})`;
}

function createImageData(size: number): number[] {
const width = size;
const height = size;

const dataWidth = Math.ceil(width / 2);
const mirrorWidth = width - dataWidth;

const data = [];
for (let y = 0; y < height; y++) {
let row = [];
for (let x = 0; x < dataWidth; x++) {
// this makes foreground and background color to have a 43% (1/2.3) probability
// spot color has 13% chance
row[x] = Math.floor(rand() * 2.3);
}
const r = row.slice(0, mirrorWidth);
r.reverse();
row = row.concat(r);

for (let i = 0; i < row.length; i++) {
data.push(row[i]);
}
}

return data;
}

function createCanvas(
imageData: number[],
color: string,
scale: number,
bgcolor: string,
spotcolor: string,
): HTMLCanvasElement {
const c = document.createElement("canvas");
const width = Math.sqrt(imageData.length);
c.width = c.height = width * scale;

const cc = c.getContext("2d");

if (!cc) throw new Error("Can't get 2D context");

cc.fillStyle = bgcolor;
cc.fillRect(0, 0, c.width, c.height);
cc.fillStyle = color;

for (let i = 0; i < imageData.length; i++) {
const row = Math.floor(i / width);
const col = i % width;
// if data is 2, choose spot color, if 1 choose foreground
cc.fillStyle = imageData[i] === 1 ? color : spotcolor;

// if data is 0, leave the background
if (imageData[i]) {
cc.fillRect(col * scale, row * scale, scale, scale);
}
}

return c;
}

export interface Options {
seed: string;
size: number;
scale: number;
color?: string;
bgcolor?: string;
spotcolor?: string;
}

export function createIcon(opts: Options): HTMLCanvasElement {
opts = opts || {};
const size = opts.size || 8;
const scale = opts.scale || 4;
const seed =
opts.seed || Math.floor(Math.random() * Math.pow(10, 16)).toString(16);

seedrand(seed);

const color = opts.color || createColor();
const bgcolor = opts.bgcolor || createColor();
const spotcolor = opts.spotcolor || createColor();
const imageData = createImageData(size);
const canvas = createCanvas(imageData, color, scale, bgcolor, spotcolor);

return canvas;
}
34 changes: 34 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,40 @@ export function formatRepositoryId(id: string): string {
return id;
}

export function formatNodeId(id: string): string {
const parsedId = parseNodeId(id);

if (parsedId) {
return `${parsedId.prefix}${truncateId(parsedId.pubkey)}`;
}

return id;
}

export function parseNodeId(
nid: string,
): { prefix: string; pubkey: string } | undefined {
const match = /^(did:key:)?(z[a-zA-Z0-9]+)$/.exec(nid);
if (match) {
let hex: Uint8Array | undefined = undefined;
try {
hex = bs58.decode(match[2].substring(1));
} catch (error) {
console.error("utils.parseNodId: Not able to decode received NID", error);
return undefined;
}
// This checks also that the first 2 bytes are equal
// to the ed25519 public key type used.
if (hex && !(hex.byteLength === 34 && hex[0] === 0xed && hex[1] === 1)) {
return undefined;
}

return { prefix: match[1] || "did:key:", pubkey: match[2] };
}

return undefined;
}

export function parseRepositoryId(
rid: string,
): { prefix: string; pubkey: string } | undefined {
Expand Down
8 changes: 8 additions & 0 deletions src/views/Home.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import CopyableId from "@app/components/CopyableId.svelte";
import Header from "@app/components/Header.svelte";
import RepoCard from "@app/components/RepoCard.svelte";
import NodeId from "@app/components/NodeId.svelte";
export let repos: RepoInfo[];
export let config: Config;
Expand Down Expand Up @@ -68,6 +69,13 @@
<svelte:fragment slot="center">
<CopyableId id={`did:key:${config.publicKey}`} />
</svelte:fragment>
<svelte:fragment slot="breadcrumbs">
<NodeId
nodeId={config.publicKey}
alias={config.alias}
styleFontFamily="var(--font-family-sans-serif)"
styleFontSize="var(--font-size-tiny)" />
</svelte:fragment>
</Header>
</div>
<div style:padding="1rem">
Expand Down
Loading

0 comments on commit 66f1b48

Please sign in to comment.