mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-28 07:50:47 +08:00
Add RepoCard component
This commit is contained in:
Generated
+27
-1
@@ -20,6 +20,7 @@
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"@types/node": "^20.9.0",
|
||||
"baconjs": "^3.0.19",
|
||||
"bs58": "^6.0.0",
|
||||
"eslint": "^9.9.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.43.0",
|
||||
@@ -31,7 +32,8 @@
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript-eslint": "^8.4.0",
|
||||
"vite": "^5.4.2"
|
||||
"vite": "^5.4.2",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.9.0"
|
||||
@@ -1447,6 +1449,12 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/base-x": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.0.tgz",
|
||||
"integrity": "sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
@@ -1481,6 +1489,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/bs58": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz",
|
||||
"integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"base-x": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -3203,6 +3220,15 @@
|
||||
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
|
||||
"integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.23.8",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
||||
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -28,6 +28,7 @@
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"@types/node": "^20.9.0",
|
||||
"baconjs": "^3.0.19",
|
||||
"bs58": "^6.0.0",
|
||||
"eslint": "^9.9.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.43.0",
|
||||
@@ -39,6 +40,7 @@
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript-eslint": "^8.4.0",
|
||||
"vite": "^5.4.2"
|
||||
"vite": "^5.4.2",
|
||||
"zod": "^3.23.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,3 +15,16 @@ html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.global-oid {
|
||||
color: var(--color-foreground-emphasized);
|
||||
font-size: var(--font-size-small);
|
||||
font-family: var(--font-family-monospace);
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
.global-flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
export let variant: "primary" | "secondary" | "ghost";
|
||||
export let stylePadding: string | undefined = undefined;
|
||||
export let styleHeight: string | undefined = undefined;
|
||||
export let styleMinHeight: string | undefined = undefined;
|
||||
export let styleWidth: string | undefined = undefined;
|
||||
|
||||
$: style = `--button-color-1: var(--color-fill-${variant});`;
|
||||
</script>
|
||||
@@ -121,16 +123,27 @@
|
||||
"p3-1 p3-2 p3-3 p3-4 p3-5"
|
||||
"p4-1 p4-2 p4-3 p4-4 p4-5"
|
||||
"p5-1 p5-2 p5-3 p5-4 p5-5";
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.container:hover .p2-3,
|
||||
.container:hover .p3-2,
|
||||
.container:hover .p3-3,
|
||||
.container:hover .p3-4,
|
||||
.container:hover .p4-3 {
|
||||
background-color: var(--color-background-float);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
style:width={styleWidth}
|
||||
class="container"
|
||||
on:click
|
||||
role="button"
|
||||
tabindex="0"
|
||||
{style}
|
||||
style:min-height={styleMinHeight}
|
||||
style:height={styleHeight}>
|
||||
<div class="pixel p1-1"></div>
|
||||
<div class="pixel p1-2"></div>
|
||||
@@ -146,7 +159,7 @@
|
||||
|
||||
<div class="pixel p3-1"></div>
|
||||
<div class="pixel p3-2"></div>
|
||||
<div class="pixel p3-3 txt-semibold txt-small" style:padding={stylePadding}>
|
||||
<div class="pixel p3-3" style:padding={stylePadding}>
|
||||
<slot />
|
||||
</div>
|
||||
<div class="pixel p3-4"></div>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<script lang="ts">
|
||||
export let variant: "primary" | "secondary" | "ghost" | "transparent";
|
||||
export let variant:
|
||||
| "delegate"
|
||||
| "ghost"
|
||||
| "primary"
|
||||
| "private"
|
||||
| "secondary"
|
||||
| "transparent";
|
||||
export let stylePadding: string | undefined = undefined;
|
||||
export let styleHeight: string | undefined = undefined;
|
||||
export let styleWidth: string | undefined = undefined;
|
||||
|
||||
$: style =
|
||||
variant === "transparent"
|
||||
@@ -34,6 +41,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
}
|
||||
.p2-3 {
|
||||
grid-area: p2-3;
|
||||
@@ -78,13 +86,14 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
{style}
|
||||
style:width={styleWidth}
|
||||
style:height={styleHeight}>
|
||||
<div class="pixel p1-1"></div>
|
||||
<div class="pixel p1-2"></div>
|
||||
<div class="pixel p1-3"></div>
|
||||
|
||||
<div class="pixel p2-1"></div>
|
||||
<div class="pixel p2-2 txt-semibold txt-small" style:padding={stylePadding}>
|
||||
<div class="pixel p2-2" style:padding={stylePadding}>
|
||||
<slot />
|
||||
</div>
|
||||
<div class="pixel p2-3"></div>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
}} />
|
||||
</div>
|
||||
<Fill variant="ghost" stylePadding="0 0.5rem" styleHeight="32px">
|
||||
{currentPage}
|
||||
<span class="txt-small txt-semibold">{currentPage}</span>
|
||||
</Fill>
|
||||
<Border variant="ghost" stylePadding="0 0.25rem" styleHeight="32px">
|
||||
<Icon name="plus" />
|
||||
@@ -60,7 +60,8 @@
|
||||
|
||||
<div class="flex-item" style:gap="0.5rem">
|
||||
<Border variant="ghost" stylePadding="0 0.5rem" styleHeight="32px">
|
||||
<Icon name="offline" /> Offline
|
||||
<Icon name="offline" />
|
||||
<span class="txt-small txt-semibold">Offline</span>
|
||||
</Border>
|
||||
<Popover popoverPositionRight="0" popoverPositionTop="3rem">
|
||||
<Border
|
||||
|
||||
@@ -12,8 +12,11 @@
|
||||
| "diff"
|
||||
| "file"
|
||||
| "inbox"
|
||||
| "issue"
|
||||
| "lock"
|
||||
| "moon"
|
||||
| "offline"
|
||||
| "patch"
|
||||
| "plus"
|
||||
| "repo"
|
||||
| "seedling"
|
||||
@@ -133,6 +136,38 @@
|
||||
<path d="M6 9H10L10 10H6L6 9Z" />
|
||||
<path d="M3 13H13V14H3L3 13Z" />
|
||||
<path d="M3 2H13V3H3L3 2Z" />
|
||||
{:else if name === "issue"}
|
||||
<path d="M6 13H8V14H6V13Z" />
|
||||
<path d="M10 13L8 13V14L10 14V13Z" />
|
||||
<path d="M3 5.99999L3 7.99999H2L2 5.99999H3Z" />
|
||||
<path d="M13 5.99999V7.99999H14V5.99999H13Z" />
|
||||
<path d="M4 12H6V13L4 13V12Z" />
|
||||
<path d="M12 12H10V13L12 13V12Z" />
|
||||
<path d="M4 3.99999V5.99999H3L3 3.99999H4Z" />
|
||||
<path d="M12 3.99999V5.99999L13 5.99999V3.99999H12Z" />
|
||||
<path d="M4 9.99999L4 12H3L3 9.99999H4Z" />
|
||||
<path d="M12 9.99998V12H13V9.99998H12Z" />
|
||||
<path d="M6 3.99998L4 3.99999L4 2.99998L6 2.99999V3.99998Z" />
|
||||
<path d="M10 3.99998L12 3.99999V2.99998L10 2.99998V3.99998Z" />
|
||||
<path d="M3 7.99999L3 9.99999H2L2 7.99999H3Z" />
|
||||
<path d="M13 7.99999V9.99998L14 9.99999V7.99999H13Z" />
|
||||
<path d="M8 2.99998L6 2.99999V1.99998L8 1.99998V2.99998Z" />
|
||||
<path d="M8 2.99998L10 2.99998L10 1.99998L8 1.99998V2.99998Z" />
|
||||
<path d="M7 5.99999H9V6.99999H7V5.99999Z" />
|
||||
<path d="M7 8.99999H9V9.99998H7V8.99999Z" />
|
||||
<path d="M10 6.99998V8.99998L9 8.99999L9 6.99999L10 6.99998Z" />
|
||||
<path d="M7 6.99999L7 8.99999L6 8.99998V6.99998L7 6.99999Z" />
|
||||
{:else if name === "lock"}
|
||||
<path d="M6 2H10V3H6V2Z" />
|
||||
<path d="M10 3L11 3V4H10V3Z" />
|
||||
<path d="M6 3H5V4H6L6 3Z" />
|
||||
<path d="M11 4L12 4V7H11V4Z" />
|
||||
<path d="M5 4H4V7H5V4Z" />
|
||||
<path d="M2 7H3V13H2V7Z" />
|
||||
<path d="M3 13H13V14H3L3 13Z" />
|
||||
<path d="M13 7H14V13H13V7Z" />
|
||||
<path d="M3 6H13V7H3L3 6Z" />
|
||||
<path d="M7 8H9V11H7V8Z" />
|
||||
{:else if name === "moon"}
|
||||
<path d="M4 3H6V4H4V3Z" />
|
||||
<path d="M3 4L4 4L4 6H3V4Z" />
|
||||
@@ -195,6 +230,39 @@
|
||||
<path d="M4 11L5 11V12L4 12L4 11Z" />
|
||||
<path d="M3 12H4L4 13H3L3 12Z" />
|
||||
<path d="M2 13L3 13L3 14H2V13Z" />
|
||||
{:else if name === "patch"}
|
||||
<path d="M9 2H10V3H9V2Z" />
|
||||
<path d="M10 2L11 2V3L10 3V2Z" />
|
||||
<path d="M13 6H14V7H13V6Z" />
|
||||
<path d="M13 5H14L14 6H13L13 5Z" />
|
||||
<path d="M11 2L12 2V3L11 3V2Z" />
|
||||
<path d="M7 6H8V7H7V6Z" />
|
||||
<path d="M6 7H7V8H6V7Z" />
|
||||
<path d="M2 11H3V12H2V11Z" />
|
||||
<path d="M2 10H3L3 11H2L2 10Z" />
|
||||
<path d="M12 3H13V4H12V3Z" />
|
||||
<path d="M8 7L9 7V8H8V7Z" />
|
||||
<path d="M7 8L8 8V9H7V8Z" />
|
||||
<path d="M3 12L4 12V13H3L3 12Z" />
|
||||
<path d="M13 4H14V5H13L13 4Z" />
|
||||
<path d="M9 8H10V9H9L9 8Z" />
|
||||
<path d="M8 9L9 9L9 10H8V9Z" />
|
||||
<path d="M4 13H5V14H4L4 13Z" />
|
||||
<path d="M5 13L6 13V14L5 14V13Z" />
|
||||
<path d="M8 3H9V4H8V3Z" />
|
||||
<path d="M12 7L13 7L13 8H12V7Z" />
|
||||
<path d="M7 4H8V5H7V4Z" />
|
||||
<path d="M11 8H12V9H11V8Z" />
|
||||
<path d="M6 5H7V6H6V5Z" />
|
||||
<path d="M10 9L11 9V10H10V9Z" />
|
||||
<path d="M5 6H6V7H5V6Z" />
|
||||
<path d="M9 10H10V11H9V10Z" />
|
||||
<path d="M4 7H5L5 8H4V7Z" />
|
||||
<path d="M8 11H9V12H8V11Z" />
|
||||
<path d="M3 8H4V9H3V8Z" />
|
||||
<path d="M7 12L8 12L8 13H7V12Z" />
|
||||
<path d="M2 9L3 9L3 10H2L2 9Z" />
|
||||
<path d="M6 13H7V14H6V13Z" />
|
||||
{:else if name === "plus"}
|
||||
<path d="M7.00002 2H9.00002V14H7.00002V2Z" />
|
||||
<path d="M14 7V9L2.00002 9L2.00002 7L14 7Z" />
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import type { Repo } from "@app/lib/api/repo";
|
||||
|
||||
import { formatRepositoryId, formatTimestamp } from "@app/lib/utils";
|
||||
|
||||
import Border from "./Border.svelte";
|
||||
import Fill from "./Fill.svelte";
|
||||
import Icon from "./Icon.svelte";
|
||||
|
||||
// TODO: Pass this via repo.
|
||||
export let updatedAt: number = 1725360130;
|
||||
|
||||
export let repo: Repo;
|
||||
export let selfDid: string;
|
||||
|
||||
$: project = repo.payloads["xyz.radicle.project"];
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.header {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 1rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
color: var(--color-fill-gray);
|
||||
margin-top: 4px;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<Border variant="ghost" styleWidth="100%" stylePadding="8px 12px">
|
||||
<div class="container txt-small">
|
||||
<div class="global-flex header">
|
||||
<div class="global-flex">
|
||||
<Fill styleWidth="1.5rem" styleHeight="24px" variant="ghost">
|
||||
{project.data.name[0]}
|
||||
</Fill>{project.data.name}
|
||||
</div>
|
||||
<div class="global-flex">
|
||||
{#if repo.visibility.type === "private"}
|
||||
<Fill variant="private" styleWidth="24px" styleHeight="24px">
|
||||
<div style:color="var(--color-foreground-yellow)">
|
||||
<Icon name="lock" />
|
||||
</div>
|
||||
</Fill>
|
||||
{/if}
|
||||
{#if repo.delegates.find(x => x.id === selfDid)}
|
||||
<Fill variant="delegate" styleWidth="24px" styleHeight="24px">
|
||||
<div style:color="var(--color-fill-primary)">
|
||||
<Icon name="delegate" />
|
||||
</div>
|
||||
</Fill>
|
||||
{/if}
|
||||
<div class="global-flex">
|
||||
<Fill variant="ghost" styleHeight="24px" stylePadding="0 4px">
|
||||
<Icon name="seedling" />
|
||||
{repo.seeding}
|
||||
</Fill>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="title">Radicle Heartwood Protocol & Stack</div>
|
||||
|
||||
<div class="global-oid">{formatRepositoryId(repo.rid)}</div>
|
||||
|
||||
<div class="global-flex footer">
|
||||
<div class="global-flex">
|
||||
<div class="global-flex" style:gap="4px"><Icon name="issue" /> 4</div>
|
||||
<div class="global-flex" style:gap="4px"><Icon name="patch" /> 6</div>
|
||||
</div>
|
||||
<span style:color="var(--color-fill-gray)">
|
||||
Updated {formatTimestamp(updatedAt)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Border>
|
||||
@@ -0,0 +1,8 @@
|
||||
import { object, string, z } from "zod";
|
||||
|
||||
export type Author = z.infer<typeof authorSchema>;
|
||||
|
||||
export const authorSchema = object({
|
||||
id: string(),
|
||||
alias: string().optional(),
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"rid": "rad:z3trNYnLWS11cJWC6BbxDs5niGo82",
|
||||
"payloads": {
|
||||
"xyz.radicle.project": {
|
||||
"data": {
|
||||
"name": "rips",
|
||||
"description": "Radicle Improvement Proposals (RIPs)",
|
||||
"defaultBranch": "master"
|
||||
},
|
||||
"meta": {
|
||||
"head": "329dee9a4b65169ea3889a7da239892b705d0d68",
|
||||
"patches": {
|
||||
"open": 0,
|
||||
"draft": 2,
|
||||
"archived": 0,
|
||||
"merged": 0
|
||||
},
|
||||
"issues": {
|
||||
"open": 1,
|
||||
"closed": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delegates": [
|
||||
{
|
||||
"id": "did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
|
||||
"alias": "cloudhead"
|
||||
}
|
||||
],
|
||||
"threshold": 1,
|
||||
"visibility": {
|
||||
"type": "private"
|
||||
},
|
||||
"seeding": 29
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
array,
|
||||
literal,
|
||||
number,
|
||||
object,
|
||||
optional,
|
||||
string,
|
||||
union,
|
||||
z,
|
||||
} from "zod";
|
||||
|
||||
import { authorSchema } from "./author";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const repoSchema = object({
|
||||
rid: string(),
|
||||
payloads: object({
|
||||
"xyz.radicle.project": object({
|
||||
data: object({
|
||||
name: string(),
|
||||
description: string(),
|
||||
defaultBranch: string(),
|
||||
}),
|
||||
meta: object({
|
||||
head: string(),
|
||||
patches: object({
|
||||
open: number(),
|
||||
draft: number(),
|
||||
archived: number(),
|
||||
merged: number(),
|
||||
}),
|
||||
issues: object({
|
||||
open: number(),
|
||||
closed: number(),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
delegates: array(authorSchema),
|
||||
threshold: number(),
|
||||
visibility: union([
|
||||
object({ type: literal("public") }),
|
||||
object({ type: literal("private"), allow: optional(array(string())) }),
|
||||
]),
|
||||
seeding: number(),
|
||||
});
|
||||
|
||||
export type Repo = z.infer<typeof repoSchema>;
|
||||
@@ -1,3 +1,75 @@
|
||||
import bs58 from "bs58";
|
||||
|
||||
export const unreachable = (value: never): never => {
|
||||
throw new Error(`Unreachable code: ${value}`);
|
||||
};
|
||||
|
||||
export function formatRepositoryId(id: string): string {
|
||||
const parsedId = parseRepositoryId(id);
|
||||
|
||||
if (parsedId) {
|
||||
return `${parsedId.prefix}${truncateId(parsedId.pubkey)}`;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
export function parseRepositoryId(
|
||||
rid: string,
|
||||
): { prefix: string; pubkey: string } | undefined {
|
||||
const match = /^(rad:)?(z[a-zA-Z0-9]+)$/.exec(rid);
|
||||
if (match) {
|
||||
const hex = bs58.decode(match[2].substring(1));
|
||||
if (hex.byteLength !== 20) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { prefix: match[1] || "rad:", pubkey: match[2] };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function truncateId(pubkey: string): string {
|
||||
return `${pubkey.substring(0, 6)}…${pubkey.slice(-6)}`;
|
||||
}
|
||||
|
||||
export const formatTimestamp = (
|
||||
timestamp: number,
|
||||
current = new Date().getTime(),
|
||||
): string => {
|
||||
const units: Record<string, number> = {
|
||||
year: 24 * 60 * 60 * 1000 * 365,
|
||||
month: (24 * 60 * 60 * 1000 * 365) / 12,
|
||||
day: 24 * 60 * 60 * 1000,
|
||||
hour: 60 * 60 * 1000,
|
||||
minute: 60 * 1000,
|
||||
second: 1000,
|
||||
};
|
||||
|
||||
// Multiplying timestamp with 1000 to convert from seconds to milliseconds
|
||||
timestamp = timestamp * 1000;
|
||||
const rtf = new Intl.RelativeTimeFormat("en", {
|
||||
numeric: "auto",
|
||||
style: "long",
|
||||
});
|
||||
const elapsed = current - timestamp;
|
||||
|
||||
if (elapsed > units["year"]) {
|
||||
return "more than a year ago";
|
||||
} else if (elapsed < 0) {
|
||||
return "now"; // If elapsed is a negative number we are dealing with an item from the future, and we return "now"
|
||||
}
|
||||
|
||||
for (const u in units) {
|
||||
if (elapsed > units[u] || u === "second") {
|
||||
// We convert the division result to a negative number to get "XX [unit] ago"
|
||||
return rtf.format(
|
||||
Math.round(elapsed / units[u]) * -1,
|
||||
u as Intl.RelativeTimeFormatUnit,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new Date(timestamp).toUTCString();
|
||||
};
|
||||
|
||||
+45
-2
@@ -1,8 +1,51 @@
|
||||
<script lang="ts">
|
||||
import type { Repo } from "@app/lib/api/repo";
|
||||
|
||||
import repoFixture from "@app/lib/api/fixtures/heartwood-repo.json";
|
||||
|
||||
import Header from "@app/components/Header.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import RepoCard from "@app/components/RepoCard.svelte";
|
||||
|
||||
const selfDid = "did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT";
|
||||
</script>
|
||||
|
||||
<Header currentPage="Repositories" />
|
||||
<style>
|
||||
.layout {
|
||||
padding: 1rem;
|
||||
}
|
||||
.repo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(21rem, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
👉 <Link route={{ resource: "designSystem" }}>Design System</Link>
|
||||
<Header currentPage="Repositories" />
|
||||
<div class="layout">
|
||||
<div class="repo-grid">
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
<RepoCard repo={repoFixture as Repo} {selfDid} />
|
||||
</div>
|
||||
|
||||
<div style:margin-top="1rem">
|
||||
👉 <Link route={{ resource: "designSystem" }}>Design System</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user