mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-31 01:00:47 +08:00
Add list_repo and config commands
This commit is contained in:
Generated
+1
-11
@@ -32,8 +32,7 @@
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript-eslint": "^8.4.0",
|
||||
"vite": "^5.4.2",
|
||||
"zod": "^3.23.8"
|
||||
"vite": "^5.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.9.0"
|
||||
@@ -3220,15 +3219,6 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -40,7 +40,6 @@
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript-eslint": "^8.4.0",
|
||||
"vite": "^5.4.2",
|
||||
"zod": "^3.23.8"
|
||||
"vite": "^5.4.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ tauri = { version = "2.0.0-rc.0", features = ["isolation"] }
|
||||
tauri-plugin-shell = { version = "2.0.0-rc.0" }
|
||||
tauri-plugin-window-state = "2.0.0-rc.1"
|
||||
thiserror = { version = "1.0.63" }
|
||||
ts-rs = { version = "9.0.1", features = ["serde-json-impl"] }
|
||||
ts-rs = { version = "9.0.1", features = ["serde-json-impl", "no-serde-warnings"] }
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Service configuration.
|
||||
*/
|
||||
export type Config = {
|
||||
/**
|
||||
* Node alias.
|
||||
*/
|
||||
publicKey: string;
|
||||
/**
|
||||
* Node alias.
|
||||
*/
|
||||
alias: string;
|
||||
/**
|
||||
* Default seeding policy.
|
||||
*/
|
||||
seedingPolicy:
|
||||
| { default: "allow"; scope: "followed" | "all" }
|
||||
| { default: "block" };
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { SupportedPayloads } from "./SupportedPayloads";
|
||||
|
||||
/**
|
||||
* Repos info.
|
||||
*/
|
||||
export type RepoInfo = {
|
||||
payloads: SupportedPayloads;
|
||||
delegates: ({ id: string } | { id: string; alias?: string })[];
|
||||
threshold: number;
|
||||
visibility: { type: "public" } | { type: "private"; allow?: string[] };
|
||||
rid: string;
|
||||
seeding: number;
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type SupportedPayloads = {
|
||||
"xyz.radicle.project"?: {
|
||||
data: {
|
||||
defaultBranch: string;
|
||||
description: string;
|
||||
name: string;
|
||||
};
|
||||
meta: {
|
||||
head: string;
|
||||
issues: {
|
||||
open: number;
|
||||
closed: number;
|
||||
};
|
||||
patches: {
|
||||
open: number;
|
||||
draft: number;
|
||||
archived: number;
|
||||
merged: number;
|
||||
};
|
||||
lastCommit: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type JsonValue =
|
||||
| number
|
||||
| string
|
||||
| Array<JsonValue>
|
||||
| { [key: string]: JsonValue };
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod auth;
|
||||
pub mod profile;
|
||||
pub mod repos;
|
||||
@@ -0,0 +1,15 @@
|
||||
use crate::error::Error;
|
||||
use crate::types::config::Config;
|
||||
use crate::AppState;
|
||||
|
||||
/// Get active config.
|
||||
#[tauri::command]
|
||||
pub fn config(ctx: tauri::State<AppState>) -> Result<Config, Error> {
|
||||
let config = Config {
|
||||
public_key: ctx.profile.public_key,
|
||||
alias: ctx.profile.config.node.alias.clone(),
|
||||
seeding_policy: ctx.profile.config.node.seeding_policy,
|
||||
};
|
||||
|
||||
Ok::<_, Error>(config)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use radicle::storage::ReadStorage;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::types;
|
||||
use crate::AppState;
|
||||
|
||||
/// List all repos.
|
||||
#[tauri::command]
|
||||
pub fn list_repos(ctx: tauri::State<AppState>) -> Result<Vec<types::repo::RepoInfo>, Error> {
|
||||
let storage = &ctx.profile.storage;
|
||||
let policies = ctx.profile.policies()?;
|
||||
|
||||
let mut repos = storage.repositories()?.into_iter().collect::<Vec<_>>();
|
||||
repos.sort_by_key(|p| p.rid);
|
||||
|
||||
let infos = repos
|
||||
.into_iter()
|
||||
.filter_map(|info| {
|
||||
if !policies.is_seeding(&info.rid).unwrap_or_default() {
|
||||
return None;
|
||||
}
|
||||
let (repo, doc) = ctx.repo(info.rid).ok()?;
|
||||
let repo_info = ctx.repo_info(&repo, doc).ok()?;
|
||||
|
||||
Some(repo_info)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok::<_, Error>(infos)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use radicle::identity;
|
||||
use radicle::node::AliasStore;
|
||||
|
||||
pub(crate) struct Author<'a>(&'a identity::Did);
|
||||
|
||||
impl<'a> Author<'a> {
|
||||
pub fn new(did: &'a identity::Did) -> Self {
|
||||
Self(did)
|
||||
}
|
||||
|
||||
pub fn as_json(&self, aliases: &impl AliasStore) -> Value {
|
||||
aliases.alias(self.0).map_or(
|
||||
json!({ "id": self.0 }),
|
||||
|alias| json!({ "id": self.0, "alias": alias, }),
|
||||
)
|
||||
}
|
||||
}
|
||||
+76
-3
@@ -1,13 +1,82 @@
|
||||
mod auth;
|
||||
mod commands;
|
||||
mod error;
|
||||
mod json;
|
||||
mod types;
|
||||
|
||||
use auth::authenticate;
|
||||
use serde_json::json;
|
||||
use tauri::Manager;
|
||||
|
||||
use radicle::identity::doc::PayloadId;
|
||||
use radicle::identity::DocAt;
|
||||
use radicle::identity::RepoId;
|
||||
use radicle::issue::cache::Issues;
|
||||
use radicle::node::routing::Store;
|
||||
use radicle::patch::cache::Patches;
|
||||
use radicle::storage::git::Repository;
|
||||
use radicle::storage::{ReadRepository, ReadStorage};
|
||||
|
||||
use commands::{auth, profile, repos};
|
||||
use types::repo::SupportedPayloads;
|
||||
|
||||
struct AppState {
|
||||
profile: radicle::Profile,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn repo_info<R: ReadRepository + radicle::cob::Store>(
|
||||
&self,
|
||||
repo: &R,
|
||||
doc: DocAt,
|
||||
) -> Result<types::repo::RepoInfo, error::Error> {
|
||||
let DocAt { doc, .. } = doc;
|
||||
let rid = repo.id();
|
||||
|
||||
let aliases = self.profile.aliases();
|
||||
let delegates = doc
|
||||
.delegates
|
||||
.into_iter()
|
||||
.map(|did| json::Author::new(&did).as_json(&aliases))
|
||||
.collect::<Vec<_>>();
|
||||
let db = &self.profile.database()?;
|
||||
let seeding = db.count(&rid).unwrap_or_default();
|
||||
|
||||
let project = doc.payload.get(&PayloadId::project()).and_then(|payload| {
|
||||
let (_, head) = repo.head().ok()?;
|
||||
let commit = repo.commit(head).ok()?;
|
||||
let patches = self.profile.patches(repo).ok()?;
|
||||
let patches = patches.counts().ok()?;
|
||||
let issues = self.profile.issues(repo).ok()?;
|
||||
let issues = issues.counts().ok()?;
|
||||
|
||||
Some(json!({
|
||||
"data": payload,
|
||||
"meta": {
|
||||
"issues": issues,
|
||||
"patches": patches,
|
||||
"head": head,
|
||||
"lastCommit": commit.time().seconds(),
|
||||
},
|
||||
}))
|
||||
});
|
||||
|
||||
Ok(types::repo::RepoInfo {
|
||||
payloads: SupportedPayloads { project },
|
||||
delegates,
|
||||
threshold: doc.threshold,
|
||||
visibility: doc.visibility,
|
||||
rid,
|
||||
seeding,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a repository by RID, checking to make sure we're allowed to view it.
|
||||
pub fn repo(&self, rid: RepoId) -> Result<(Repository, DocAt), error::Error> {
|
||||
let repo = self.profile.storage.repository(rid)?;
|
||||
let doc = repo.identity_doc()?;
|
||||
Ok((repo, doc))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
@@ -30,7 +99,11 @@ pub fn run() {
|
||||
})
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.invoke_handler(tauri::generate_handler![authenticate])
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
auth::authenticate,
|
||||
repos::list_repos,
|
||||
profile::config,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
use radicle::crypto::PublicKey;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
use radicle::node::config::DefaultSeedingPolicy;
|
||||
use radicle::node::Alias;
|
||||
|
||||
/// Service configuration.
|
||||
#[derive(Debug, Clone, TS, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct Config {
|
||||
/// Node Public Key in NID format.
|
||||
#[ts(as = "String")]
|
||||
pub public_key: PublicKey,
|
||||
/// Node alias.
|
||||
#[ts(as = "String")]
|
||||
pub alias: Alias,
|
||||
/// Default seeding policy.
|
||||
#[serde(default)]
|
||||
#[ts(type = "{ default: 'allow', scope: 'followed' | 'all' } | { default: 'block' }")]
|
||||
pub seeding_policy: DefaultSeedingPolicy,
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod config;
|
||||
pub mod repo;
|
||||
@@ -0,0 +1,50 @@
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use ts_rs::TS;
|
||||
|
||||
use radicle::identity::RepoId;
|
||||
|
||||
/// Repos info.
|
||||
#[derive(Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct RepoInfo {
|
||||
pub payloads: SupportedPayloads,
|
||||
#[ts(type = "({ id: string } | { id: string, alias?: string })[]")]
|
||||
pub delegates: Vec<Value>,
|
||||
pub threshold: usize,
|
||||
#[ts(type = "{ type: 'public' } | { type: 'private', allow?: string[] }")]
|
||||
pub visibility: radicle::identity::Visibility,
|
||||
#[ts(as = "String")]
|
||||
pub rid: RepoId,
|
||||
pub seeding: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct SupportedPayloads {
|
||||
#[serde(rename = "xyz.radicle.project")]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
#[ts(type = r#"{
|
||||
data: {
|
||||
defaultBranch: string,
|
||||
description: string,
|
||||
name: string,
|
||||
},
|
||||
meta: {
|
||||
head: string,
|
||||
issues: {
|
||||
open: number,
|
||||
closed: number,
|
||||
},
|
||||
patches: {
|
||||
open: number,
|
||||
draft: number,
|
||||
archived: number,
|
||||
merged: number,
|
||||
}
|
||||
lastCommit: number,
|
||||
}
|
||||
}"#)]
|
||||
pub project: Option<Value>,
|
||||
}
|
||||
+2
-2
@@ -7,9 +7,9 @@
|
||||
import { theme } from "@app/components/ThemeSwitch.svelte";
|
||||
import { unreachable } from "@app/lib/utils";
|
||||
|
||||
import AuthenticationError from "@app/views/AuthenticationError.svelte";
|
||||
import DesignSystem from "@app/views/DesignSystem.svelte";
|
||||
import Home from "@app/views/Home.svelte";
|
||||
import AuthenticationError from "@app/views/AuthenticationError.svelte";
|
||||
|
||||
const activeRouteStore = router.activeRouteStore;
|
||||
void router.loadFromLocation();
|
||||
@@ -36,7 +36,7 @@
|
||||
{#if $activeRouteStore.resource === "booting"}
|
||||
<!-- Don't show anything -->
|
||||
{:else if $activeRouteStore.resource === "home"}
|
||||
<Home />
|
||||
<Home {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "authenticationError"}
|
||||
<AuthenticationError {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "designSystem"}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Repo } from "@app/lib/api/repo";
|
||||
import type { RepoInfo } from "@bindings/RepoInfo";
|
||||
|
||||
import { formatRepositoryId, formatTimestamp } from "@app/lib/utils";
|
||||
|
||||
@@ -7,13 +7,10 @@
|
||||
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 repo: RepoInfo;
|
||||
export let selfDid: string;
|
||||
|
||||
$: project = repo.payloads["xyz.radicle.project"];
|
||||
$: project = repo.payloads["xyz.radicle.project"]!;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -66,17 +63,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="title">Radicle Heartwood Protocol & Stack</div>
|
||||
|
||||
<div class="title">
|
||||
{#if project.data.description}
|
||||
{project.data.description}
|
||||
{:else}
|
||||
No description
|
||||
{/if}
|
||||
</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 class="global-flex" style:gap="4px">
|
||||
<Icon name="issue" />{project.meta.issues.open}
|
||||
</div>
|
||||
<div class="global-flex" style:gap="4px">
|
||||
<Icon name="patch" />{project.meta.patches.open}
|
||||
</div>
|
||||
</div>
|
||||
<span style:color="var(--color-fill-gray)">
|
||||
Updated {formatTimestamp(updatedAt)}
|
||||
Updated {formatTimestamp(project.meta.lastCommit)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { object, string, z } from "zod";
|
||||
|
||||
export type Author = z.infer<typeof authorSchema>;
|
||||
|
||||
export const authorSchema = object({
|
||||
id: string(),
|
||||
alias: string().optional(),
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
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,8 @@
|
||||
import type { RepoInfo } from "@bindings/RepoInfo";
|
||||
import type { Config } from "@bindings/Config";
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
interface BootingRoute {
|
||||
resource: "booting";
|
||||
}
|
||||
@@ -14,6 +19,11 @@ interface HomeRoute {
|
||||
resource: "home";
|
||||
}
|
||||
|
||||
interface LoadedHomeRoute {
|
||||
resource: "home";
|
||||
params: { repos: RepoInfo[]; config: Config };
|
||||
}
|
||||
|
||||
interface DesignSystemRoute {
|
||||
resource: "designSystem";
|
||||
}
|
||||
@@ -27,12 +37,17 @@ export type Route =
|
||||
export type LoadedRoute =
|
||||
| BootingRoute
|
||||
| DesignSystemRoute
|
||||
| HomeRoute
|
||||
| LoadedHomeRoute
|
||||
| AuthenticationErrorRoute;
|
||||
|
||||
export async function loadRoute(
|
||||
route: Route,
|
||||
_previousLoaded: LoadedRoute,
|
||||
): Promise<LoadedRoute> {
|
||||
if (route.resource === "home") {
|
||||
const repos: RepoInfo[] = await invoke("list_repos");
|
||||
const config: Config = await invoke("config");
|
||||
return { resource: "home", params: { repos, config } };
|
||||
}
|
||||
return route;
|
||||
}
|
||||
|
||||
+9
-23
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { Repo } from "@app/lib/api/repo";
|
||||
|
||||
import repoFixture from "@app/lib/api/fixtures/heartwood-repo.json";
|
||||
import type { Config } from "@bindings/Config";
|
||||
import type { RepoInfo } from "@bindings/RepoInfo";
|
||||
|
||||
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";
|
||||
export let repos: RepoInfo[];
|
||||
export let config: Config;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -24,25 +24,11 @@
|
||||
<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} />
|
||||
{#each repos as repo}
|
||||
{#if repo.payloads["xyz.radicle.project"]}
|
||||
<RepoCard {repo} selfDid={`did:key:${config.publicKey}`} />
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div style:margin-top="1rem">
|
||||
|
||||
Reference in New Issue
Block a user