mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-12-23 08:32:23 +03:00
Merge pull request #462 from kinode-dao/dr/app-store-kimap-rewrite
Deduplicate frontend stuff: app store, homepage, register-ui
This commit is contained in:
commit
b5aeac5b1a
4
.gitignore
vendored
4
.gitignore
vendored
@ -15,3 +15,7 @@ wit/
|
||||
.env
|
||||
kinode/src/bootstrapped_processes.rs
|
||||
kinode/packages/**/wasi_snapshot_preview1.wasm
|
||||
|
||||
kinode/packages/app_store/pkg/ui/*
|
||||
kinode/packages/homepage/pkg/ui/*
|
||||
kinode/src/register-ui/build/*
|
||||
|
147
Cargo.lock
generated
147
Cargo.lock
generated
@ -1117,6 +1117,19 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blog"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"kinode_process_lib 0.9.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blst"
|
||||
version = "0.3.12"
|
||||
@ -1719,7 +1732,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot 0.12.3",
|
||||
"parking_lot",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
@ -1843,7 +1856,7 @@ dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core 0.9.10",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2517,6 +2530,17 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "globe"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"kinode_process_lib 0.9.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.13.0"
|
||||
@ -3013,15 +3037,6 @@ dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-extras"
|
||||
version = "0.18.2"
|
||||
@ -3233,7 +3248,7 @@ dependencies = [
|
||||
"hmac",
|
||||
"http 1.1.0",
|
||||
"jwt",
|
||||
"kit 0.6.7",
|
||||
"kit",
|
||||
"lazy_static",
|
||||
"lib",
|
||||
"nohash-hasher",
|
||||
@ -3298,7 +3313,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "kinode_process_lib"
|
||||
version = "0.9.0"
|
||||
source = "git+https://github.com/kinode-dao/process_lib?branch=develop#290866f1f742b7179dcde30e6ed2249562a4a818"
|
||||
source = "git+https://github.com/kinode-dao/process_lib?branch=develop#9094586e6c816d1c44879f26ac6cced9e31dc839"
|
||||
dependencies = [
|
||||
"alloy",
|
||||
"alloy-primitives",
|
||||
@ -3319,45 +3334,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kit"
|
||||
version = "0.6.2"
|
||||
source = "git+https://github.com/kinode-dao/kit?tag=v0.6.2#59ca74d4952998753bf5e64404d09f31a4424830"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.7",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"dirs 5.0.1",
|
||||
"fs-err",
|
||||
"futures-util",
|
||||
"git2",
|
||||
"hex",
|
||||
"kinode_process_lib 0.8.0",
|
||||
"nix",
|
||||
"regex",
|
||||
"reqwest 0.11.27",
|
||||
"rmp-serde",
|
||||
"semver 1.0.23",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"ssh2",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.23.1",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-error",
|
||||
"tracing-subscriber",
|
||||
"walkdir",
|
||||
"wit-bindgen",
|
||||
"zip 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kit"
|
||||
version = "0.6.7"
|
||||
source = "git+https://github.com/kinode-dao/kit?rev=4a8999f#4a8999f90b69381e94d11fb5aa1b62215a9db95b"
|
||||
version = "0.6.8"
|
||||
source = "git+https://github.com/kinode-dao/kit?tag=v0.6.8#7b636305bed2aae6fe36f5ced44181ec7cbdb7a3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.7",
|
||||
@ -3425,7 +3403,7 @@ name = "lib"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"alloy",
|
||||
"kit 0.6.2",
|
||||
"kit",
|
||||
"lazy_static",
|
||||
"rand 0.8.5",
|
||||
"ring",
|
||||
@ -3999,17 +3977,6 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
@ -4017,21 +3984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core 0.9.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall 0.2.16",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4042,7 +3995,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.5.3",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
@ -4559,15 +4512,6 @@ dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.3"
|
||||
@ -5334,18 +5278,6 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a"
|
||||
|
||||
[[package]]
|
||||
name = "ssh2"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7fe461910559f6d5604c3731d00d2aafc4a83d1665922e280f42f9a168d5455"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
"libssh2-sys",
|
||||
"parking_lot 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
@ -6815,19 +6747,6 @@ dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widget"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"kinode_process_lib 0.9.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wiggle"
|
||||
version = "19.0.2"
|
||||
|
@ -18,7 +18,7 @@ members = [
|
||||
"kinode/packages/app_store/download", "kinode/packages/app_store/install", "kinode/packages/app_store/uninstall",
|
||||
"kinode/packages/chess/chess",
|
||||
"kinode/packages/homepage/homepage",
|
||||
"kinode/packages/kino_updates/widget",
|
||||
"kinode/packages/kino_updates/blog", "kinode/packages/kino_updates/globe",
|
||||
"kinode/packages/kns_indexer/kns_indexer", "kinode/packages/kns_indexer/get_block", "kinode/packages/kns_indexer/state",
|
||||
"kinode/packages/settings/settings",
|
||||
"kinode/packages/terminal/terminal",
|
||||
|
2
LICENSE
2
LICENSE
@ -175,7 +175,7 @@
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2024 Unzentrum DAO
|
||||
Copyright 2024 Sybil Technologies AG
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
170
css/kinode.css
Normal file
170
css/kinode.css
Normal file
@ -0,0 +1,170 @@
|
||||
/* CSS Reset and Base Styles */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
img,
|
||||
picture,
|
||||
video,
|
||||
canvas,
|
||||
svg {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
/* Variables */
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--orange: #FF4F00;
|
||||
--dark-orange: #cc4100;
|
||||
--blue: #2B88D9;
|
||||
--off-white: #fdfdfd;
|
||||
--white: #ffffff;
|
||||
--off-black: #0C090A;
|
||||
--black: #000000;
|
||||
--tan: #fdf6e3;
|
||||
--ansi-red: #dc322f;
|
||||
--maroon: #4f0000;
|
||||
--gray: #657b83;
|
||||
--tasteful-dark: #1f1f1f;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
label,
|
||||
li {
|
||||
color: light-dark(var(--off-black), var(--off-white));
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: light-dark(var(--blue), var(--orange));
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: light-dark(var(--orange), var(--dark-orange));
|
||||
text-decoration: underline wavy;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
body {
|
||||
font-family: var(--font-family-main, sans-serif);
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background-color: light-dark(var(--tan), var(--tasteful-dark));
|
||||
}
|
||||
|
||||
/* Sections */
|
||||
section {
|
||||
background-color: light-dark(var(--white), var(--maroon));
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
section:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
form label {
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
form input {
|
||||
padding: 0.75rem;
|
||||
border: 2px solid var(--orange);
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
form input:focus {
|
||||
outline: none;
|
||||
border-color: var(--dark-orange);
|
||||
box-shadow: 0 0 0 3px rgba(255, 79, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Button styles */
|
||||
button {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
background-color: var(--orange);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: var(--dark-orange);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
background-color: white;
|
||||
color: var(--orange);
|
||||
border: 2px solid var(--orange);
|
||||
}
|
||||
|
||||
button.secondary:hover {
|
||||
background-color: var(--orange);
|
||||
color: white;
|
||||
}
|
Before Width: | Height: | Size: 644 B After Width: | Height: | Size: 644 B |
@ -14,7 +14,7 @@ path = "src/main.rs"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.71"
|
||||
kit = { git = "https://github.com/kinode-dao/kit", rev = "4a8999f" }
|
||||
kit = { git = "https://github.com/kinode-dao/kit", tag = "v0.6.8" }
|
||||
tokio = "1.28"
|
||||
walkdir = "2.4"
|
||||
zip = "0.6"
|
||||
|
@ -106,6 +106,29 @@ fn main() -> anyhow::Result<()> {
|
||||
let parent_dir = pwd.parent().unwrap();
|
||||
let packages_dir = pwd.join("packages");
|
||||
|
||||
if std::env::var("SKIP_BUILD_FRONTEND").is_ok() {
|
||||
println!("Skipping build frontend");
|
||||
} else {
|
||||
// build core frontends
|
||||
let core_frontends = vec![
|
||||
"src/register-ui",
|
||||
"packages/app_store/ui",
|
||||
"packages/homepage/ui",
|
||||
// chess when brought in
|
||||
];
|
||||
|
||||
// for each frontend, execute build.sh
|
||||
for frontend in core_frontends {
|
||||
let status = std::process::Command::new("sh")
|
||||
.current_dir(pwd.join(frontend))
|
||||
.arg("./build.sh")
|
||||
.status()?;
|
||||
if !status.success() {
|
||||
return Err(anyhow::anyhow!("Failed to build frontend: {}", frontend));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let entries: Vec<_> = fs::read_dir(packages_dir)?
|
||||
.map(|entry| entry.unwrap().path())
|
||||
.collect();
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::state::{PackageListing, PackageState, State};
|
||||
use crate::state::{PackageListing, State};
|
||||
use crate::DownloadResponse;
|
||||
use kinode_process_lib::{
|
||||
http::{
|
||||
bind_http_path, bind_ws_path, send_response, serve_ui, IncomingHttpRequest, Method,
|
||||
StatusCode,
|
||||
},
|
||||
Address, NodeId, PackageId, Request,
|
||||
println, Address, NodeId, PackageId, ProcessId, Request,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
@ -17,9 +17,7 @@ const ICON: &str = include_str!("icon");
|
||||
pub fn init_frontend(our: &Address) {
|
||||
for path in [
|
||||
"/apps",
|
||||
"/apps/listed",
|
||||
"/apps/:id",
|
||||
"/apps/listed/:id",
|
||||
"/apps/:id/caps",
|
||||
"/apps/:id/mirror",
|
||||
"/apps/:id/auto-update",
|
||||
@ -32,7 +30,7 @@ pub fn init_frontend(our: &Address) {
|
||||
"ui",
|
||||
true,
|
||||
false,
|
||||
vec!["/", "/my-apps", "/app-details/:id", "/publish"],
|
||||
vec!["/", "/my-apps", "/app/:id", "/publish"],
|
||||
)
|
||||
.expect("failed to serve static UI");
|
||||
|
||||
@ -74,7 +72,6 @@ fn make_widget() -> String {
|
||||
}
|
||||
|
||||
body {
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@ -90,8 +87,6 @@ fn make_widget() -> String {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow-y: auto;
|
||||
scrollbar-color: transparent transparent;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.app {
|
||||
@ -101,16 +96,11 @@ fn make_widget() -> String {
|
||||
align-items: stretch;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
cursor: pointer;
|
||||
font-family: sans-serif;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.app:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.app-image {
|
||||
border-radius: 0.75rem;
|
||||
margin-right: 0.5rem;
|
||||
@ -142,11 +132,11 @@ fn make_widget() -> String {
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-white overflow-hidden">
|
||||
<body>
|
||||
<div id="latest-apps"></div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
fetch('/main:app_store:sys/apps/listed', { credentials: 'include' })
|
||||
fetch('/main:app_store:sys/apps', { credentials: 'include' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const container = document.getElementById('latest-apps');
|
||||
@ -154,14 +144,14 @@ fn make_widget() -> String {
|
||||
if (app.metadata) {
|
||||
const a = document.createElement('a');
|
||||
a.className = 'app';
|
||||
a.href = `/main:app_store:sys/app-details/${app.package}:${app.publisher}`
|
||||
a.href = `/main:app_store:sys/app/${app.package}:${app.publisher}`
|
||||
a.target = '_blank';
|
||||
a.rel = 'noopener noreferrer';
|
||||
const iconLetter = app.metadata_hash.replace('0x', '')[0].toUpperCase();
|
||||
a.innerHTML = `<div
|
||||
class="app-image"
|
||||
style="
|
||||
background-image: url('${app.metadata.image || `/icons/${iconLetter}`}');
|
||||
background-image: url('${app.metadata.image || `/bird-orange.svg`}');
|
||||
"
|
||||
></div>
|
||||
<div class="app-info">
|
||||
@ -181,15 +171,12 @@ fn make_widget() -> String {
|
||||
}
|
||||
|
||||
/// Actions supported over HTTP:
|
||||
/// - get all downloaded apps: GET /apps
|
||||
/// - get all listed apps: GET /apps/listed
|
||||
/// - get all apps: GET /apps
|
||||
/// - get some subset of listed apps, via search or filter: ?
|
||||
/// - get detail about a specific downloaded app: GET /apps/:id
|
||||
/// - get detail about a specific app: GET /apps/:id
|
||||
/// - get capabilities for a specific downloaded app: GET /apps/:id/caps
|
||||
/// - get detail about a specific listed app: GET /apps/listed/:id
|
||||
///
|
||||
/// - download a listed app: POST /apps/listed/:id
|
||||
/// - install a downloaded app: POST /apps/:id
|
||||
/// - install a downloaded app, download a listed app: POST /apps/:id
|
||||
/// - uninstall/delete a downloaded app: DELETE /apps/:id
|
||||
/// - update a downloaded app: PUT /apps/:id
|
||||
/// - approve capabilities for a downloaded app: POST /apps/:id/caps
|
||||
@ -224,37 +211,21 @@ fn get_package_id(url_params: &HashMap<String, String>) -> anyhow::Result<Packag
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
fn gen_package_info(
|
||||
id: &PackageId,
|
||||
listing: Option<&PackageListing>,
|
||||
state: Option<&PackageState>,
|
||||
) -> serde_json::Value {
|
||||
fn gen_package_info(id: &PackageId, listing: &PackageListing) -> serde_json::Value {
|
||||
json!({
|
||||
"owner": match &listing {
|
||||
Some(listing) => Some(&listing.owner),
|
||||
None => None,
|
||||
},
|
||||
"tba": listing.tba,
|
||||
"package": id.package().to_string(),
|
||||
"publisher": id.publisher(),
|
||||
"installed": match &state {
|
||||
"installed": match &listing.state {
|
||||
Some(state) => state.installed,
|
||||
None => false,
|
||||
},
|
||||
"metadata_hash": match &listing {
|
||||
Some(listing) => Some(&listing.metadata_hash),
|
||||
None => None,
|
||||
},
|
||||
"metadata": match &listing {
|
||||
Some(listing) => Some(&listing.metadata),
|
||||
None => match state {
|
||||
Some(state) => Some(&state.metadata),
|
||||
None => None,
|
||||
},
|
||||
},
|
||||
"state": match &state {
|
||||
"metadata_hash": listing.metadata_hash,
|
||||
"metadata": listing.metadata,
|
||||
"state": match &listing.state {
|
||||
Some(state) => json!({
|
||||
"mirrored_from": state.mirrored_from,
|
||||
"our_version": state.our_version,
|
||||
"our_version": state.our_version_hash,
|
||||
"caps_approved": state.caps_approved,
|
||||
"mirroring": state.mirroring,
|
||||
"auto_update": state.auto_update,
|
||||
@ -275,7 +246,7 @@ fn serve_paths(
|
||||
let url_params = req.url_params();
|
||||
|
||||
match bound_path {
|
||||
// GET all downloaded apps
|
||||
// GET all apps
|
||||
"/apps" => {
|
||||
if method != Method::GET {
|
||||
return Ok((
|
||||
@ -285,39 +256,16 @@ fn serve_paths(
|
||||
));
|
||||
}
|
||||
let all: Vec<serde_json::Value> = state
|
||||
.downloaded_packages
|
||||
.packages
|
||||
.iter()
|
||||
.map(|(package_id, package_state)| {
|
||||
let listing = state.get_listing(package_id);
|
||||
gen_package_info(package_id, listing, Some(package_state))
|
||||
})
|
||||
.map(|(package_id, listing)| gen_package_info(package_id, listing))
|
||||
.collect();
|
||||
return Ok((StatusCode::OK, None, serde_json::to_vec(&all)?));
|
||||
}
|
||||
// GET all listed apps
|
||||
"/apps/listed" => {
|
||||
if method != Method::GET {
|
||||
return Ok((
|
||||
StatusCode::METHOD_NOT_ALLOWED,
|
||||
None,
|
||||
format!("Invalid method {method} for {bound_path}").into_bytes(),
|
||||
));
|
||||
}
|
||||
let all: Vec<serde_json::Value> = state
|
||||
.listed_packages
|
||||
.iter()
|
||||
.map(|(_hash, listing)| {
|
||||
let package_id = PackageId::new(&listing.name, &listing.publisher);
|
||||
let state = state.downloaded_packages.get(&package_id);
|
||||
gen_package_info(&package_id, Some(listing), state)
|
||||
})
|
||||
.collect();
|
||||
return Ok((StatusCode::OK, None, serde_json::to_vec(&all)?));
|
||||
}
|
||||
// GET detail about a specific downloaded app
|
||||
// install a downloaded app: POST
|
||||
// GET detail about a specific app
|
||||
// install an app: POST
|
||||
// update a downloaded app: PUT
|
||||
// uninstall/delete a downloaded app: DELETE
|
||||
// uninstall an app: DELETE
|
||||
"/apps/:id" => {
|
||||
let Ok(package_id) = get_package_id(url_params) else {
|
||||
return Ok((
|
||||
@ -329,36 +277,184 @@ fn serve_paths(
|
||||
|
||||
match method {
|
||||
Method::GET => {
|
||||
let Some(pkg) = state.downloaded_packages.get(&package_id) else {
|
||||
let Some(listing) = state.packages.get(&package_id) else {
|
||||
return Ok((
|
||||
StatusCode::NOT_FOUND,
|
||||
None,
|
||||
format!("App not found: {package_id}").into_bytes(),
|
||||
));
|
||||
};
|
||||
let listing = state.get_listing(&package_id);
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
None,
|
||||
gen_package_info(&package_id, listing, Some(pkg))
|
||||
gen_package_info(&package_id, listing)
|
||||
.to_string()
|
||||
.into_bytes(),
|
||||
))
|
||||
}
|
||||
Method::POST => {
|
||||
// install an app
|
||||
let Some(listing) = state.packages.get(&package_id) else {
|
||||
return Ok((
|
||||
StatusCode::NOT_FOUND,
|
||||
None,
|
||||
format!("App not found: {package_id}").into_bytes(),
|
||||
));
|
||||
};
|
||||
if listing.state.is_some() {
|
||||
// install a downloaded app
|
||||
crate::handle_install(state, &package_id)?;
|
||||
Ok((StatusCode::CREATED, None, format!("Installed").into_bytes()))
|
||||
}
|
||||
Method::PUT => {
|
||||
// update an app
|
||||
let _pkg_listing: &PackageListing = state
|
||||
.get_listing(&package_id)
|
||||
.ok_or(anyhow::anyhow!("No package"))?;
|
||||
let pkg_state: &PackageState = state
|
||||
.downloaded_packages
|
||||
} else {
|
||||
// download a listed app
|
||||
let pkg_listing: &PackageListing = state
|
||||
.packages
|
||||
.get(&package_id)
|
||||
.ok_or(anyhow::anyhow!("No package"))?;
|
||||
// from POST body, look for download_from field and use that as the mirror
|
||||
let body = crate::get_blob()
|
||||
.ok_or(anyhow::anyhow!("missing blob"))?
|
||||
.bytes;
|
||||
let body_json: serde_json::Value =
|
||||
serde_json::from_slice(&body).unwrap_or_default();
|
||||
let mirrors: &Vec<NodeId> = pkg_listing
|
||||
.metadata
|
||||
.as_ref()
|
||||
.expect("Package does not have metadata")
|
||||
.properties
|
||||
.mirrors
|
||||
.as_ref();
|
||||
|
||||
let download_from = body_json
|
||||
.get("download_from")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| mirrors.first().map(|mirror| mirror.to_string()));
|
||||
|
||||
// if no specific mirror specified, loop through and ping them.
|
||||
if let Some(download_from) = download_from {
|
||||
// TODO choose more on frontend.
|
||||
let mirror = false;
|
||||
let auto_update = false;
|
||||
let desired_version_hash = None;
|
||||
match crate::start_download(
|
||||
state,
|
||||
package_id,
|
||||
download_from,
|
||||
mirror,
|
||||
auto_update,
|
||||
desired_version_hash,
|
||||
) {
|
||||
DownloadResponse::Started => Ok((
|
||||
StatusCode::CREATED,
|
||||
None,
|
||||
format!("Downloading").into_bytes(),
|
||||
)),
|
||||
other => Ok((
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
None,
|
||||
format!("Failed to download: {other:?}").into_bytes(),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
let online_mirrors: Vec<NodeId> = mirrors
|
||||
.iter()
|
||||
.filter_map(|mirror| {
|
||||
let target = Address::new(
|
||||
mirror,
|
||||
ProcessId::new(Some("net"), "distro", "sys"),
|
||||
);
|
||||
let request = Request::new().target(target).body(vec![]).send();
|
||||
|
||||
match request {
|
||||
Ok(_) => Some(mirror.clone()),
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
println!("all mirrors: {:?}", mirrors);
|
||||
println!("online mirrors: {:?}", online_mirrors);
|
||||
|
||||
let mut failed_mirrors = Vec::new();
|
||||
for online_mirror in &online_mirrors {
|
||||
let mirror = true;
|
||||
let auto_update = false;
|
||||
let desired_version_hash = None;
|
||||
match crate::start_download(
|
||||
state,
|
||||
package_id.clone(),
|
||||
online_mirror.to_string(),
|
||||
mirror,
|
||||
auto_update,
|
||||
desired_version_hash,
|
||||
) {
|
||||
DownloadResponse::Started => {
|
||||
return Ok((
|
||||
StatusCode::CREATED,
|
||||
None,
|
||||
format!(
|
||||
"Download started from mirror: {}",
|
||||
online_mirror
|
||||
)
|
||||
.into_bytes(),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
failed_mirrors.push(online_mirror.to_string());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut failed_mirrors = Vec::new();
|
||||
for online_mirror in &online_mirrors {
|
||||
let mirror = true;
|
||||
let auto_update = false;
|
||||
let desired_version_hash = None;
|
||||
match crate::start_download(
|
||||
state,
|
||||
package_id.clone(),
|
||||
online_mirror.to_string(),
|
||||
mirror,
|
||||
auto_update,
|
||||
desired_version_hash,
|
||||
) {
|
||||
DownloadResponse::Started => {
|
||||
return Ok((
|
||||
StatusCode::CREATED,
|
||||
None,
|
||||
format!(
|
||||
"Download started from mirror: {}",
|
||||
online_mirror
|
||||
)
|
||||
.into_bytes(),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
failed_mirrors.push(online_mirror.to_string());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
None,
|
||||
format!(
|
||||
"Failed to start download from any mirrors. Failed mirrors: {:?}",
|
||||
failed_mirrors
|
||||
).into_bytes(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Method::PUT => {
|
||||
// update a downloaded app
|
||||
let listing: &PackageListing = state
|
||||
.packages
|
||||
.get(&package_id)
|
||||
.ok_or(anyhow::anyhow!("No package listing"))?;
|
||||
let Some(ref pkg_state) = listing.state else {
|
||||
return Err(anyhow::anyhow!("No package state"));
|
||||
};
|
||||
let download_from = pkg_state
|
||||
.mirrored_from
|
||||
.as_ref()
|
||||
@ -400,92 +496,6 @@ fn serve_paths(
|
||||
)),
|
||||
}
|
||||
}
|
||||
// GET detail about a specific listed app
|
||||
// download a listed app: POST
|
||||
"/apps/listed/:id" => {
|
||||
let Ok(package_id) = get_package_id(url_params) else {
|
||||
return Ok((
|
||||
StatusCode::BAD_REQUEST,
|
||||
None,
|
||||
format!("Missing id").into_bytes(),
|
||||
));
|
||||
};
|
||||
|
||||
match method {
|
||||
Method::GET => {
|
||||
let Some(listing) = state.get_listing(&package_id) else {
|
||||
return Ok((
|
||||
StatusCode::NOT_FOUND,
|
||||
None,
|
||||
format!("App not found: {package_id}").into_bytes(),
|
||||
));
|
||||
};
|
||||
let downloaded = state.downloaded_packages.get(&package_id);
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
None,
|
||||
gen_package_info(&package_id, Some(listing), downloaded)
|
||||
.to_string()
|
||||
.into_bytes(),
|
||||
))
|
||||
}
|
||||
Method::POST => {
|
||||
// download an app
|
||||
let pkg_listing: &PackageListing = state
|
||||
.get_listing(&package_id)
|
||||
.ok_or(anyhow::anyhow!("No package"))?;
|
||||
// from POST body, look for download_from field and use that as the mirror
|
||||
let body = crate::get_blob()
|
||||
.ok_or(anyhow::anyhow!("missing blob"))?
|
||||
.bytes;
|
||||
let body_json: serde_json::Value =
|
||||
serde_json::from_slice(&body).unwrap_or_default();
|
||||
let mirrors: &Vec<NodeId> = pkg_listing
|
||||
.metadata
|
||||
.as_ref()
|
||||
.expect("Package does not have metadata")
|
||||
.properties
|
||||
.mirrors
|
||||
.as_ref();
|
||||
let download_from = body_json
|
||||
.get("download_from")
|
||||
.unwrap_or(&json!(mirrors
|
||||
.first()
|
||||
.ok_or(anyhow::anyhow!("No mirrors for package {package_id}"))?))
|
||||
.as_str()
|
||||
.ok_or(anyhow::anyhow!("download_from not a string"))?
|
||||
.to_string();
|
||||
// TODO select on FE? or after download but before install?
|
||||
let mirror = false;
|
||||
let auto_update = false;
|
||||
let desired_version_hash = None;
|
||||
match crate::start_download(
|
||||
state,
|
||||
package_id,
|
||||
download_from,
|
||||
mirror,
|
||||
auto_update,
|
||||
desired_version_hash,
|
||||
) {
|
||||
DownloadResponse::Started => Ok((
|
||||
StatusCode::CREATED,
|
||||
None,
|
||||
format!("Downloading").into_bytes(),
|
||||
)),
|
||||
other => Ok((
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
None,
|
||||
format!("Failed to download: {other:?}").into_bytes(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => Ok((
|
||||
StatusCode::METHOD_NOT_ALLOWED,
|
||||
None,
|
||||
format!("Invalid method {method} for {bound_path}").into_bytes(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
// GET caps for a specific downloaded app
|
||||
// approve capabilities for a downloaded app: POST
|
||||
"/apps/:id/caps" => {
|
||||
|
@ -156,6 +156,7 @@ fn handle_message(state: &mut State, message: &Message) -> anyhow::Result<()> {
|
||||
} else {
|
||||
// attempt to resubscribe
|
||||
state
|
||||
.kimap
|
||||
.provider
|
||||
.subscribe_loop(1, utils::app_store_filter(state));
|
||||
}
|
||||
@ -207,7 +208,11 @@ fn handle_remote_request(state: &mut State, source: &Address, request: RemoteReq
|
||||
}) => (package_id.to_process_lib(), desired_version_hash),
|
||||
};
|
||||
|
||||
let Some(package_state) = state.get_downloaded_package(&package_id) else {
|
||||
let Some(listing) = state.packages.get(&package_id) else {
|
||||
return Resp::RemoteResponse(RemoteResponse::DownloadDenied(Reason::NoPackage));
|
||||
};
|
||||
|
||||
let Some(ref package_state) = listing.state else {
|
||||
return Resp::RemoteResponse(RemoteResponse::DownloadDenied(Reason::NoPackage));
|
||||
};
|
||||
|
||||
@ -216,11 +221,11 @@ fn handle_remote_request(state: &mut State, source: &Address, request: RemoteReq
|
||||
}
|
||||
|
||||
if let Some(hash) = desired_version_hash {
|
||||
if package_state.our_version != hash {
|
||||
if package_state.our_version_hash != hash {
|
||||
return Resp::RemoteResponse(RemoteResponse::DownloadDenied(Reason::HashMismatch(
|
||||
HashMismatch {
|
||||
requested: hash,
|
||||
have: package_state.our_version,
|
||||
have: package_state.our_version_hash.clone(),
|
||||
},
|
||||
)));
|
||||
}
|
||||
@ -230,7 +235,7 @@ fn handle_remote_request(state: &mut State, source: &Address, request: RemoteReq
|
||||
|
||||
// get the .zip from VFS and attach as blob to response
|
||||
let Ok(Ok(_)) = utils::vfs_request(
|
||||
format!("/{}/pkg{}", package_id, file_name),
|
||||
format!("/{package_id}/pkg{file_name}"),
|
||||
vfs::VfsAction::Read,
|
||||
)
|
||||
.send_and_await_response(VFS_TIMEOUT) else {
|
||||
@ -377,15 +382,10 @@ pub fn list_apis(state: &mut State) -> LocalResponse {
|
||||
|
||||
pub fn rebuild_index(state: &mut State) -> LocalResponse {
|
||||
// kill our old subscription and build a new one.
|
||||
let _ = state.provider.unsubscribe(1);
|
||||
let _ = state.kimap.provider.unsubscribe(1);
|
||||
|
||||
let eth_provider = eth::Provider::new(CHAIN_ID, CHAIN_TIMEOUT);
|
||||
*state = State::new(
|
||||
state.our.clone(),
|
||||
eth_provider,
|
||||
state.contract_address.clone(),
|
||||
)
|
||||
.expect("state creation failed");
|
||||
*state = State::new(state.our.clone(), eth_provider).expect("state creation failed");
|
||||
|
||||
fetch_and_subscribe_logs(state);
|
||||
LocalResponse::RebuildIndexResponse(RebuildIndexResponse::Success)
|
||||
@ -475,32 +475,21 @@ fn handle_receive_download_package(
|
||||
return Err(anyhow::anyhow!("received download but found no blob"));
|
||||
};
|
||||
// check the version hash for this download against requested!
|
||||
let download_hash = utils::generate_version_hash(&blob.bytes);
|
||||
let (verified, metadata) = match requested_package.desired_version_hash {
|
||||
let download_hash = utils::sha_256_hash(&blob.bytes);
|
||||
let verified = match requested_package.desired_version_hash {
|
||||
Some(hash) => {
|
||||
let Some(package_listing) = state.get_listing(package_id) else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"downloaded package cannot be found in manager--rejecting download!"
|
||||
));
|
||||
};
|
||||
let Some(metadata) = &package_listing.metadata else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"downloaded package has no metadata to check validity against!"
|
||||
));
|
||||
};
|
||||
if download_hash != hash {
|
||||
return Err(anyhow::anyhow!(
|
||||
"downloaded package is not desired version--rejecting download! \
|
||||
download hash: {download_hash}, desired hash: {hash}"
|
||||
));
|
||||
} else {
|
||||
(true, Some(metadata.clone()))
|
||||
}
|
||||
true
|
||||
}
|
||||
None => match state.get_listing(package_id) {
|
||||
None => match state.packages.get(package_id) {
|
||||
None => {
|
||||
println!("downloaded package cannot be found onchain, proceeding with unverified download");
|
||||
(true, None)
|
||||
false
|
||||
}
|
||||
Some(package_listing) => {
|
||||
if let Some(metadata) = &package_listing.metadata {
|
||||
@ -515,19 +504,20 @@ fn handle_receive_download_package(
|
||||
proceeding with unverified download"
|
||||
);
|
||||
}
|
||||
(true, Some(metadata.clone()))
|
||||
false
|
||||
} else {
|
||||
println!("downloaded package has no metadata to check validity against, proceeding with unverified download");
|
||||
(true, None)
|
||||
false
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let old_manifest_hash = match state.downloaded_packages.get(package_id) {
|
||||
Some(package_state) => package_state
|
||||
.manifest_hash
|
||||
.clone()
|
||||
let old_manifest_hash = match state.packages.get(package_id) {
|
||||
Some(listing) => listing
|
||||
.state
|
||||
.as_ref()
|
||||
.and_then(|state| state.manifest_hash.clone())
|
||||
.unwrap_or("OLD".to_string()),
|
||||
_ => "OLD".to_string(),
|
||||
};
|
||||
@ -536,22 +526,22 @@ fn handle_receive_download_package(
|
||||
package_id,
|
||||
PackageState {
|
||||
mirrored_from: Some(requested_package.from),
|
||||
our_version: download_hash,
|
||||
our_version_hash: download_hash,
|
||||
installed: false,
|
||||
verified,
|
||||
caps_approved: false,
|
||||
manifest_hash: None, // generated in the add fn
|
||||
mirroring: requested_package.mirror,
|
||||
auto_update: requested_package.auto_update,
|
||||
metadata,
|
||||
},
|
||||
Some(blob.bytes),
|
||||
)?;
|
||||
|
||||
let new_manifest_hash = match state.downloaded_packages.get(package_id) {
|
||||
Some(package_state) => package_state
|
||||
.manifest_hash
|
||||
.clone()
|
||||
let new_manifest_hash = match state.packages.get(package_id) {
|
||||
Some(listing) => listing
|
||||
.state
|
||||
.as_ref()
|
||||
.and_then(|state| state.manifest_hash.clone())
|
||||
.unwrap_or("NEW".to_string()),
|
||||
_ => "NEW".to_string(),
|
||||
};
|
||||
@ -595,8 +585,9 @@ fn handle_eth_sub_event(
|
||||
/// make sure you have reviewed and approved caps in manifest before calling this
|
||||
pub fn handle_install(state: &mut State, package_id: &PackageId) -> anyhow::Result<()> {
|
||||
// wit version will default to the latest if not specified
|
||||
let metadata = state
|
||||
.get_downloaded_package(package_id)
|
||||
let metadata = &state
|
||||
.packages
|
||||
.get(package_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("package not found in manager"))?
|
||||
.metadata;
|
||||
|
||||
|
@ -3,8 +3,7 @@ use crate::{KIMAP_ADDRESS, VFS_TIMEOUT};
|
||||
use alloy_sol_types::SolEvent;
|
||||
use kinode_process_lib::kernel_types::Erc721Metadata;
|
||||
use kinode_process_lib::{
|
||||
eth, kernel_types as kt, kimap, net::get_name, println, vfs, Address, Message, NodeId,
|
||||
PackageId, Request,
|
||||
eth, kernel_types as kt, kimap, net, println, vfs, Address, NodeId, PackageId, Request,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@ -41,20 +40,14 @@ impl std::fmt::Display for AppStoreLogError {
|
||||
|
||||
impl std::error::Error for AppStoreLogError {}
|
||||
|
||||
pub type PackageHash = String;
|
||||
|
||||
/// listing information derived from metadata hash in listing event
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PackageListing {
|
||||
pub owner: String, // eth address,
|
||||
pub name: String,
|
||||
pub publisher: NodeId, // this should be moved to metadata...
|
||||
pub metadata_url: String,
|
||||
pub metadata_hash: String,
|
||||
pub metadata: Option<kt::Erc721Metadata>,
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct MirroringFile {
|
||||
pub mirroring_from: Option<NodeId>,
|
||||
pub mirroring: bool,
|
||||
pub auto_update: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct RequestedPackage {
|
||||
pub from: NodeId,
|
||||
pub mirror: bool,
|
||||
@ -63,6 +56,17 @@ pub struct RequestedPackage {
|
||||
pub desired_version_hash: Option<String>,
|
||||
}
|
||||
|
||||
/// listing information derived from metadata hash in listing event
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct PackageListing {
|
||||
pub tba: eth::Address,
|
||||
pub metadata_uri: String,
|
||||
pub metadata_hash: String,
|
||||
pub metadata: Option<kt::Erc721Metadata>,
|
||||
/// if we have downloaded the package, this is populated
|
||||
pub state: Option<PackageState>,
|
||||
}
|
||||
|
||||
/// state of an individual package we have downloaded
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PackageState {
|
||||
@ -70,7 +74,7 @@ pub struct PackageState {
|
||||
/// this is "us" if we don't know the source (usually cause it's a local install)
|
||||
pub mirrored_from: Option<NodeId>,
|
||||
/// the version of the package we have downloaded
|
||||
pub our_version: String,
|
||||
pub our_version_hash: String,
|
||||
pub installed: bool,
|
||||
pub verified: bool,
|
||||
pub caps_approved: bool,
|
||||
@ -82,33 +86,22 @@ pub struct PackageState {
|
||||
pub mirroring: bool,
|
||||
/// if we get a listing data update, will we try to download it?
|
||||
pub auto_update: bool,
|
||||
pub metadata: Option<kt::Erc721Metadata>,
|
||||
}
|
||||
|
||||
/// this process's saved state
|
||||
pub struct State {
|
||||
/// our address, grabbed from init()
|
||||
pub our: Address,
|
||||
/// the eth provider we are using -- not persisted
|
||||
pub provider: eth::Provider,
|
||||
/// the kimap helper we are using -- not persisted
|
||||
/// the kimap helper we are using
|
||||
pub kimap: kimap::Kimap,
|
||||
/// the address of the contract we are using to read package listings
|
||||
pub contract_address: String,
|
||||
/// the last block at which we saved the state of the listings to disk.
|
||||
/// when we boot, we can read logs starting from this block and
|
||||
/// rebuild latest state.
|
||||
pub last_saved_block: u64,
|
||||
pub package_hashes: HashMap<PackageId, PackageHash>,
|
||||
/// we keep the full state of the package manager here, calculated from
|
||||
/// the listings contract logs. in the future, we'll offload this and
|
||||
/// only track a certain number of packages...
|
||||
pub listed_packages: HashMap<PackageHash, PackageListing>,
|
||||
/// we keep the full state of the packages we have downloaded here.
|
||||
/// in order to keep this synchronized with our filesystem, we will
|
||||
/// ingest apps on disk if we have to rebuild our state. this is also
|
||||
/// updated every time we download, create, or uninstall a package.
|
||||
pub downloaded_packages: HashMap<PackageId, PackageState>,
|
||||
pub packages: HashMap<PackageId, PackageListing>,
|
||||
/// the APIs we have
|
||||
pub downloaded_apis: HashSet<PackageId>,
|
||||
/// the packages we have outstanding requests to download (not persisted)
|
||||
@ -119,11 +112,9 @@ pub struct State {
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SerializedState {
|
||||
pub contract_address: String,
|
||||
pub kimap: kimap::Kimap,
|
||||
pub last_saved_block: u64,
|
||||
pub package_hashes: HashMap<PackageId, PackageHash>,
|
||||
pub listed_packages: HashMap<PackageHash, PackageListing>,
|
||||
pub downloaded_packages: HashMap<PackageId, PackageState>,
|
||||
pub packages: HashMap<PackageId, PackageListing>,
|
||||
pub downloaded_apis: HashSet<PackageId>,
|
||||
}
|
||||
|
||||
@ -134,48 +125,35 @@ impl Serialize for State {
|
||||
{
|
||||
use serde::ser::SerializeStruct;
|
||||
let mut state = serializer.serialize_struct("State", 6)?;
|
||||
state.serialize_field("contract_address", &self.contract_address)?;
|
||||
state.serialize_field("kimap", &self.kimap)?;
|
||||
state.serialize_field("last_saved_block", &self.last_saved_block)?;
|
||||
state.serialize_field("package_hashes", &self.package_hashes)?;
|
||||
state.serialize_field("listed_packages", &self.listed_packages)?;
|
||||
state.serialize_field("downloaded_packages", &self.downloaded_packages)?;
|
||||
state.serialize_field("packages", &self.packages)?;
|
||||
state.serialize_field("downloaded_apis", &self.downloaded_apis)?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn from_serialized(our: Address, provider: eth::Provider, s: SerializedState) -> Self {
|
||||
pub fn from_serialized(our: Address, s: SerializedState) -> Self {
|
||||
State {
|
||||
our,
|
||||
provider: provider.clone(),
|
||||
kimap: kimap::Kimap::new(provider, eth::Address::from_str(KIMAP_ADDRESS).unwrap()),
|
||||
contract_address: s.contract_address,
|
||||
kimap: s.kimap,
|
||||
last_saved_block: s.last_saved_block,
|
||||
package_hashes: s.package_hashes,
|
||||
listed_packages: s.listed_packages,
|
||||
downloaded_packages: s.downloaded_packages,
|
||||
packages: s.packages,
|
||||
downloaded_apis: s.downloaded_apis,
|
||||
requested_packages: HashMap::new(),
|
||||
requested_apis: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// To create a new state, we populate the downloaded_packages map
|
||||
/// with all packages parseable from our filesystem.
|
||||
pub fn new(
|
||||
our: Address,
|
||||
provider: eth::Provider,
|
||||
contract_address: String,
|
||||
) -> anyhow::Result<Self> {
|
||||
pub fn new(our: Address, provider: eth::Provider) -> anyhow::Result<Self> {
|
||||
let mut state = State {
|
||||
our,
|
||||
provider: provider.clone(),
|
||||
kimap: kimap::Kimap::new(provider, eth::Address::from_str(KIMAP_ADDRESS).unwrap()),
|
||||
contract_address,
|
||||
last_saved_block: crate::KIMAP_FIRST_BLOCK,
|
||||
package_hashes: HashMap::new(),
|
||||
listed_packages: HashMap::new(),
|
||||
downloaded_packages: HashMap::new(),
|
||||
packages: HashMap::new(),
|
||||
downloaded_apis: HashSet::new(),
|
||||
requested_packages: HashMap::new(),
|
||||
requested_apis: HashMap::new(),
|
||||
@ -184,30 +162,49 @@ impl State {
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
pub fn get_listing(&self, package_id: &PackageId) -> Option<&PackageListing> {
|
||||
self.listed_packages
|
||||
.get(self.package_hashes.get(package_id)?)
|
||||
}
|
||||
|
||||
pub fn get_downloaded_package(&self, package_id: &PackageId) -> Option<PackageState> {
|
||||
self.downloaded_packages.get(package_id).cloned()
|
||||
pub fn add_listing(&mut self, package_id: &PackageId, metadata: kt::Erc721Metadata) {
|
||||
self.packages.insert(
|
||||
package_id.clone(),
|
||||
PackageListing {
|
||||
tba: eth::Address::ZERO,
|
||||
metadata_uri: "".to_string(),
|
||||
metadata_hash: utils::sha_256_hash(&serde_json::to_vec(&metadata).unwrap()),
|
||||
metadata: Some(metadata),
|
||||
state: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// if package_bytes is None, we already have the package downloaded
|
||||
/// in VFS and this is being called to rebuild our process state
|
||||
pub fn add_downloaded_package(
|
||||
&mut self,
|
||||
package_id: &PackageId,
|
||||
mut package_state: PackageState,
|
||||
package_bytes: Option<Vec<u8>>,
|
||||
package_zip_bytes: Option<Vec<u8>>,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Some(package_bytes) = package_bytes {
|
||||
let Some(listing) = self.packages.get_mut(package_id) else {
|
||||
return Err(anyhow::anyhow!("package not found"));
|
||||
};
|
||||
// if passed zip bytes, make drive
|
||||
if let Some(package_bytes) = package_zip_bytes {
|
||||
let manifest_hash = utils::create_package_drive(package_id, package_bytes)?;
|
||||
package_state.manifest_hash = Some(manifest_hash);
|
||||
}
|
||||
// persist mirroring status
|
||||
let mirroring_file = vfs::File {
|
||||
path: format!("/{package_id}/pkg/.mirroring"),
|
||||
timeout: 5,
|
||||
};
|
||||
mirroring_file.write(&serde_json::to_vec(&MirroringFile {
|
||||
mirroring_from: package_state.mirrored_from.clone(),
|
||||
mirroring: package_state.mirroring,
|
||||
auto_update: package_state.auto_update,
|
||||
})?)?;
|
||||
if utils::extract_api(package_id)? {
|
||||
self.downloaded_apis.insert(package_id.to_owned());
|
||||
}
|
||||
self.downloaded_packages
|
||||
.insert(package_id.to_owned(), package_state);
|
||||
listing.state = Some(package_state);
|
||||
// kinode_process_lib::set_state(&serde_json::to_vec(self)?);
|
||||
Ok(())
|
||||
}
|
||||
@ -219,11 +216,15 @@ impl State {
|
||||
fn_: impl FnOnce(&mut PackageState),
|
||||
) -> bool {
|
||||
let res = self
|
||||
.downloaded_packages
|
||||
.packages
|
||||
.get_mut(package_id)
|
||||
.map(|package_state| {
|
||||
.map(|listing| {
|
||||
if let Some(package_state) = &mut listing.state {
|
||||
fn_(package_state);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false);
|
||||
// kinode_process_lib::set_state(&serde_json::to_vec(self).unwrap());
|
||||
@ -256,24 +257,29 @@ impl State {
|
||||
|
||||
/// saves state
|
||||
pub fn populate_packages_from_filesystem(&mut self) -> anyhow::Result<()> {
|
||||
let Message::Response { body, .. } =
|
||||
utils::vfs_request("/".to_string(), vfs::VfsAction::ReadDir)
|
||||
// call VFS and ask for all directories in our root drive
|
||||
// (we have root VFS capability so this is allowed)
|
||||
// we will interpret any that are package dirs and ingest them
|
||||
let vfs::VfsResponse::ReadDir(entries) = serde_json::from_slice::<vfs::VfsResponse>(
|
||||
utils::vfs_request("/", vfs::VfsAction::ReadDir)
|
||||
.send_and_await_response(VFS_TIMEOUT)??
|
||||
.body(),
|
||||
)?
|
||||
else {
|
||||
return Err(anyhow::anyhow!("vfs: bad response"));
|
||||
};
|
||||
let response = serde_json::from_slice::<vfs::VfsResponse>(&body)?;
|
||||
let vfs::VfsResponse::ReadDir(entries) = response else {
|
||||
return Err(anyhow::anyhow!("vfs: unexpected response: {:?}", response));
|
||||
return Err(anyhow::anyhow!("vfs: unexpected response to ReadDir"));
|
||||
};
|
||||
for entry in entries {
|
||||
// ignore non-dirs
|
||||
if entry.file_type != vfs::FileType::Directory {
|
||||
continue;
|
||||
}
|
||||
// ignore non-package dirs
|
||||
let Ok(package_id) = entry.path.parse::<PackageId>() else {
|
||||
continue;
|
||||
};
|
||||
if entry.file_type == vfs::FileType::Directory {
|
||||
// grab package .zip if it exists
|
||||
let zip_file = vfs::File {
|
||||
path: format!("/{}/pkg/{}.zip", package_id, package_id),
|
||||
path: format!("/{package_id}/pkg/{package_id}.zip"),
|
||||
timeout: 5,
|
||||
};
|
||||
let Ok(zip_file_bytes) = zip_file.read() else {
|
||||
@ -281,27 +287,47 @@ impl State {
|
||||
};
|
||||
// generate entry from this data
|
||||
// for the version hash, take the SHA-256 hash of the zip file
|
||||
let our_version = utils::generate_version_hash(&zip_file_bytes);
|
||||
let our_version_hash = utils::sha_256_hash(&zip_file_bytes);
|
||||
let manifest_file = vfs::File {
|
||||
path: format!("/{}/pkg/manifest.json", package_id),
|
||||
path: format!("/{package_id}/pkg/manifest.json"),
|
||||
timeout: 5,
|
||||
};
|
||||
let manifest_bytes = manifest_file.read()?;
|
||||
// the user will need to turn mirroring and auto-update back on if they
|
||||
// have to reset the state of their app store for some reason. the apps
|
||||
// themselves will remain on disk unless explicitly deleted.
|
||||
// get mirroring data if available
|
||||
let mirroring_file = vfs::File {
|
||||
path: format!("/{package_id}/pkg/.mirroring"),
|
||||
timeout: 5,
|
||||
};
|
||||
let mirroring_data = if let Ok(bytes) = mirroring_file.read() {
|
||||
serde_json::from_slice::<MirroringFile>(&bytes)?
|
||||
} else {
|
||||
MirroringFile {
|
||||
mirroring_from: None,
|
||||
mirroring: false,
|
||||
auto_update: false,
|
||||
}
|
||||
};
|
||||
self.packages.insert(
|
||||
package_id.clone(),
|
||||
PackageListing {
|
||||
tba: eth::Address::ZERO,
|
||||
metadata_uri: "".to_string(),
|
||||
metadata_hash: "".to_string(),
|
||||
metadata: None,
|
||||
state: None,
|
||||
},
|
||||
);
|
||||
self.add_downloaded_package(
|
||||
&package_id,
|
||||
PackageState {
|
||||
mirrored_from: None,
|
||||
our_version,
|
||||
mirrored_from: mirroring_data.mirroring_from,
|
||||
our_version_hash,
|
||||
installed: true,
|
||||
verified: true, // implicitly verified (TODO re-evaluate)
|
||||
caps_approved: false, // must re-approve if you want to do something
|
||||
manifest_hash: Some(utils::generate_metadata_hash(&manifest_bytes)),
|
||||
mirroring: false,
|
||||
auto_update: false,
|
||||
metadata: None,
|
||||
manifest_hash: Some(utils::keccak_256_hash(&manifest_bytes)),
|
||||
mirroring: mirroring_data.mirroring,
|
||||
auto_update: mirroring_data.auto_update,
|
||||
},
|
||||
None,
|
||||
)?;
|
||||
@ -310,8 +336,7 @@ impl State {
|
||||
utils::vfs_request(format!("/{package_id}/pkg/api"), vfs::VfsAction::Metadata)
|
||||
.send_and_await_response(VFS_TIMEOUT)
|
||||
{
|
||||
self.downloaded_apis.insert(package_id.to_owned());
|
||||
}
|
||||
self.downloaded_apis.insert(package_id);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@ -319,7 +344,7 @@ impl State {
|
||||
|
||||
pub fn uninstall(&mut self, package_id: &PackageId) -> anyhow::Result<()> {
|
||||
utils::uninstall(package_id)?;
|
||||
self.downloaded_packages.remove(package_id);
|
||||
self.packages.remove(package_id);
|
||||
// kinode_process_lib::set_state(&serde_json::to_vec(self)?);
|
||||
println!("uninstalled {package_id}");
|
||||
Ok(())
|
||||
@ -335,154 +360,81 @@ impl State {
|
||||
log: eth::Log,
|
||||
update_listings: bool,
|
||||
) -> Result<(), AppStoreLogError> {
|
||||
println!("ingesting contract event");
|
||||
|
||||
let block_number: u64 = log.block_number.ok_or(AppStoreLogError::NoBlockNumber)?;
|
||||
|
||||
match log.topics()[0] {
|
||||
kimap::contract::Note::SIGNATURE_HASH => {
|
||||
let note =
|
||||
kimap::contract::Note::decode_log_data(log.data(), false).map_err(|e| {
|
||||
println!("error decoding note: {e}");
|
||||
AppStoreLogError::DecodeLogError
|
||||
})?;
|
||||
let kimap::contract::Note::SIGNATURE_HASH = log.topics()[0] else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let name = get_name(¬e.nodehash.to_string(), log.block_number, Some(5))
|
||||
let note = kimap::contract::Note::decode_log_data(log.data(), false)
|
||||
.map_err(|_| AppStoreLogError::DecodeLogError)?;
|
||||
|
||||
// use kns_indexer to convert nodehash to a kimap name
|
||||
let package_full_path =
|
||||
net::get_name(¬e.nodehash.to_string(), log.block_number, Some(5))
|
||||
.ok_or(AppStoreLogError::DecodeLogError)?;
|
||||
|
||||
match std::str::from_utf8(¬e.note) {
|
||||
Ok("~metadata-uri") => {
|
||||
let metadata_url = String::from_utf8_lossy(¬e.data).to_string();
|
||||
// generate ~metadata-hash notehash
|
||||
let meta_note_name = format!("~metadata-hash.{name}");
|
||||
// the app store exclusively looks for ~metadata-uri postings: if one is
|
||||
// observed, we then *query* for ~metadata-hash to verify the content
|
||||
// at the URI.
|
||||
//
|
||||
// this means that ~metadata-hash should be *posted before or at the same time* as ~metadata-uri!
|
||||
let Ok("~metadata-uri") = std::str::from_utf8(¬e.note) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let (_tba, _owner, data) =
|
||||
self.kimap.get(&meta_note_name).map_err(|e| {
|
||||
println!("Error getting metadata hash: {:?}", e);
|
||||
AppStoreLogError::DecodeLogError
|
||||
let metadata_uri = String::from_utf8_lossy(¬e.data).to_string();
|
||||
|
||||
// generate ~metadata-hash notehash
|
||||
let hash_note = format!("~metadata-hash.{package_full_path}");
|
||||
|
||||
// owner can change which we don't track (yet?) so don't save, need to get when desired
|
||||
let (tba, _owner, data) = self.kimap.get(&hash_note).map_err(|e| {
|
||||
println!("Couldn't find {hash_note}: {e:?}");
|
||||
AppStoreLogError::MetadataHashMismatch
|
||||
})?;
|
||||
|
||||
if let Some(hash_note) = data {
|
||||
let metadata_hash = String::from_utf8_lossy(&hash_note).to_string();
|
||||
let metadata =
|
||||
utils::fetch_metadata_from_url(&metadata_url, &metadata_hash, 5)?;
|
||||
|
||||
// if this fails and doesn't check out, do nothing
|
||||
// fetch metadata from the URI (currently only handling HTTP(S) URLs!)
|
||||
// assert that the metadata hash matches the fetched data
|
||||
let metadata = utils::fetch_metadata_from_url(&metadata_uri, &metadata_hash, 30)?;
|
||||
|
||||
let (package_name, publisher_name) = name
|
||||
let (package_name, publisher_name) = package_full_path
|
||||
.split_once('.')
|
||||
.ok_or(AppStoreLogError::InvalidPublisherName)
|
||||
.and_then(|(package, publisher)| {
|
||||
if package.is_empty() || publisher.is_empty() {
|
||||
Err(AppStoreLogError::InvalidPublisherName)
|
||||
} else {
|
||||
Ok((package.to_string(), publisher.to_string()))
|
||||
Ok((package, publisher))
|
||||
}
|
||||
})?;
|
||||
println!(
|
||||
"pkg_name and publisher_name: {package_name} {publisher_name}"
|
||||
);
|
||||
// do we need package hashes anymore? seems kinda unnecessary, use nodehashes instead?
|
||||
// not removing for now for state compatibility
|
||||
let package_hash = utils::generate_package_hash(
|
||||
&package_name,
|
||||
publisher_name.as_bytes(),
|
||||
);
|
||||
|
||||
self.package_hashes.insert(
|
||||
PackageId::new(&package_name, &publisher_name),
|
||||
package_hash.clone(),
|
||||
);
|
||||
let package_id = PackageId::new(&package_name, &publisher_name);
|
||||
|
||||
match self.listed_packages.entry(package_hash) {
|
||||
println!("got new app with valid metadata: {package_id}");
|
||||
|
||||
match self.packages.entry(package_id) {
|
||||
std::collections::hash_map::Entry::Occupied(mut listing) => {
|
||||
let listing = listing.get_mut();
|
||||
listing.name = package_name;
|
||||
listing.publisher = publisher_name;
|
||||
listing.metadata_url = metadata_url;
|
||||
listing.tba = tba;
|
||||
listing.metadata_uri = metadata_uri;
|
||||
listing.metadata_hash = metadata_hash;
|
||||
listing.metadata = Some(metadata);
|
||||
}
|
||||
std::collections::hash_map::Entry::Vacant(listing) => {
|
||||
listing.insert(PackageListing {
|
||||
owner: "".to_string(),
|
||||
name: package_name,
|
||||
publisher: publisher_name,
|
||||
metadata_url,
|
||||
tba,
|
||||
metadata_uri,
|
||||
metadata_hash,
|
||||
metadata: Some(metadata),
|
||||
state: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok("~metadata-hash") => {
|
||||
let metadata_hash = String::from_utf8_lossy(¬e.data).to_string();
|
||||
// generate ~metadata-uri notehash
|
||||
let meta_note_name = format!("~metadata-uri.{name}");
|
||||
let (_tba, _owner, data) =
|
||||
self.kimap.get(&meta_note_name).map_err(|e| {
|
||||
println!("Error getting metadata uri: {:?}", e);
|
||||
AppStoreLogError::DecodeLogError
|
||||
})?;
|
||||
|
||||
if let Some(uri_note) = data {
|
||||
let metadata_url = String::from_utf8_lossy(&uri_note).to_string();
|
||||
let metadata =
|
||||
utils::fetch_metadata_from_url(&metadata_url, &metadata_hash, 5)?;
|
||||
|
||||
let (package_name, publisher_name) = name
|
||||
.split_once('.')
|
||||
.ok_or(AppStoreLogError::InvalidPublisherName)
|
||||
.and_then(|(package, publisher)| {
|
||||
if package.is_empty() || publisher.is_empty() {
|
||||
Err(AppStoreLogError::InvalidPublisherName)
|
||||
} else {
|
||||
Ok((package.to_string(), publisher.to_string()))
|
||||
}
|
||||
})?;
|
||||
println!(
|
||||
"pkg_name and publisher_name: {package_name} {publisher_name}"
|
||||
);
|
||||
// do we need package hashes anymore? seems kinda unnecessary, use nodehashes instead?
|
||||
// not removing for now for state compatibility
|
||||
let package_hash = utils::generate_package_hash(
|
||||
&package_name,
|
||||
publisher_name.as_bytes(),
|
||||
);
|
||||
|
||||
self.package_hashes.insert(
|
||||
PackageId::new(&package_name, &publisher_name),
|
||||
package_hash.clone(),
|
||||
);
|
||||
|
||||
match self.listed_packages.entry(package_hash) {
|
||||
std::collections::hash_map::Entry::Occupied(mut listing) => {
|
||||
let listing = listing.get_mut();
|
||||
listing.name = package_name;
|
||||
listing.publisher = publisher_name;
|
||||
listing.metadata_url = metadata_url;
|
||||
listing.metadata_hash = metadata_hash;
|
||||
listing.metadata = Some(metadata);
|
||||
}
|
||||
std::collections::hash_map::Entry::Vacant(listing) => {
|
||||
listing.insert(PackageListing {
|
||||
owner: "".to_string(),
|
||||
name: package_name,
|
||||
publisher: publisher_name,
|
||||
metadata_url,
|
||||
metadata_hash,
|
||||
metadata: Some(metadata),
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.last_saved_block = block_number;
|
||||
if update_listings {
|
||||
// kinode_process_lib::set_state(&serde_json::to_vec(self).unwrap());
|
||||
@ -494,14 +446,15 @@ impl State {
|
||||
/// this is done after ingesting a bunch of logs to remove fetches
|
||||
/// of stale metadata.
|
||||
pub fn update_listings(&mut self) {
|
||||
for (_package_hash, listing) in self.listed_packages.iter_mut() {
|
||||
for (package_id, listing) in self.packages.iter_mut() {
|
||||
if listing.metadata.is_none() {
|
||||
if let Ok(metadata) =
|
||||
utils::fetch_metadata_from_url(&listing.metadata_url, &listing.metadata_hash, 5)
|
||||
{
|
||||
let package_id = PackageId::new(&listing.name, &listing.publisher);
|
||||
if let Some(package_state) = self.downloaded_packages.get(&package_id) {
|
||||
auto_update(&self.our, package_id, &metadata, &package_state);
|
||||
if let Ok(metadata) = utils::fetch_metadata_from_url(
|
||||
&listing.metadata_uri,
|
||||
&listing.metadata_hash,
|
||||
30,
|
||||
) {
|
||||
if let Some(package_state) = &listing.state {
|
||||
auto_update(&self.our, package_id, &metadata, package_state);
|
||||
}
|
||||
listing.metadata = Some(metadata);
|
||||
}
|
||||
@ -516,7 +469,7 @@ impl State {
|
||||
/// and install it if successful.
|
||||
fn auto_update(
|
||||
our: &Address,
|
||||
package_id: PackageId,
|
||||
package_id: &PackageId,
|
||||
metadata: &Erc721Metadata,
|
||||
package_state: &PackageState,
|
||||
) {
|
||||
@ -526,22 +479,22 @@ fn auto_update(
|
||||
.code_hashes
|
||||
.get(&metadata.properties.current_version);
|
||||
if let Some(mirrored_from) = &package_state.mirrored_from
|
||||
&& Some(&package_state.our_version) != latest_version_hash
|
||||
&& Some(&package_state.our_version_hash) != latest_version_hash
|
||||
{
|
||||
println!(
|
||||
"auto-updating package {package_id} from {} to {} using mirror {mirrored_from}",
|
||||
metadata
|
||||
.properties
|
||||
.code_hashes
|
||||
.get(&package_state.our_version)
|
||||
.unwrap_or(&package_state.our_version),
|
||||
.get(&package_state.our_version_hash)
|
||||
.unwrap_or(&package_state.our_version_hash),
|
||||
metadata.properties.current_version,
|
||||
);
|
||||
Request::to(our)
|
||||
.body(
|
||||
serde_json::to_vec(&LocalRequest::Download(DownloadRequest {
|
||||
package_id: crate::kinode::process::main::PackageId::from_process_lib(
|
||||
package_id,
|
||||
package_id.clone(),
|
||||
),
|
||||
download_from: mirrored_from.clone(),
|
||||
mirror: package_state.mirroring,
|
||||
|
@ -10,7 +10,7 @@ use {
|
||||
eth, get_blob, get_state, http, kernel_types as kt, kimap, println, vfs, Address,
|
||||
LazyLoadBlob, PackageId, ProcessId, Request,
|
||||
},
|
||||
std::{collections::HashSet, str::FromStr},
|
||||
std::collections::HashSet,
|
||||
};
|
||||
|
||||
// quite annoyingly, we must convert from our gen'd version of PackageId
|
||||
@ -61,26 +61,33 @@ pub fn fetch_state(our: Address, provider: eth::Provider) -> State {
|
||||
if let Some(state_bytes) = get_state() {
|
||||
match serde_json::from_slice::<SerializedState>(&state_bytes) {
|
||||
Ok(state) => {
|
||||
if state.contract_address == KIMAP_ADDRESS {
|
||||
return State::from_serialized(our, provider, state);
|
||||
if state.kimap.address().to_string() == KIMAP_ADDRESS {
|
||||
return State::from_serialized(our, state);
|
||||
} else {
|
||||
println!(
|
||||
"state contract address mismatch! expected {}, got {}",
|
||||
KIMAP_ADDRESS, state.contract_address
|
||||
"state contract address mismatch. rebuilding state! expected {}, got {}",
|
||||
KIMAP_ADDRESS,
|
||||
state.kimap.address().to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => println!("failed to deserialize saved state: {e}"),
|
||||
Err(e) => println!("failed to deserialize saved state, rebuilding: {e}"),
|
||||
}
|
||||
}
|
||||
State::new(our, provider, KIMAP_ADDRESS.to_string()).expect("state creation failed")
|
||||
State::new(our, provider).expect("state creation failed")
|
||||
}
|
||||
|
||||
/// create the filter used for app store getLogs and subscription.
|
||||
/// the app store exclusively looks for ~metadata-uri postings: if one is
|
||||
/// observed, we then *query* for ~metadata-hash to verify the content
|
||||
/// at the URI.
|
||||
///
|
||||
/// this means that ~metadata-hash should be *posted before or at the same time* as ~metadata-uri!
|
||||
pub fn app_store_filter(state: &State) -> eth::Filter {
|
||||
let notes = vec![keccak256("~metadata-uri"), keccak256("~metadata-hash")];
|
||||
let notes = vec![keccak256("~metadata-uri")];
|
||||
|
||||
eth::Filter::new()
|
||||
.address(eth::Address::from_str(&state.contract_address).unwrap())
|
||||
.address(*state.kimap.address())
|
||||
.events([kimap::contract::Note::SIGNATURE])
|
||||
.topic3(notes)
|
||||
}
|
||||
@ -90,7 +97,7 @@ pub fn fetch_and_subscribe_logs(state: &mut State) {
|
||||
let filter = app_store_filter(state);
|
||||
// get past logs, subscribe to new ones.
|
||||
for log in fetch_logs(
|
||||
&state.provider,
|
||||
&state.kimap.provider,
|
||||
&filter.clone().from_block(state.last_saved_block),
|
||||
) {
|
||||
if let Err(e) = state.ingest_contract_event(log, false) {
|
||||
@ -98,7 +105,7 @@ pub fn fetch_and_subscribe_logs(state: &mut State) {
|
||||
};
|
||||
}
|
||||
state.update_listings();
|
||||
state.provider.subscribe_loop(1, filter);
|
||||
state.kimap.provider.subscribe_loop(1, filter);
|
||||
}
|
||||
|
||||
/// fetch logs from the chain with a given filter
|
||||
@ -126,7 +133,7 @@ pub fn fetch_metadata_from_url(
|
||||
http::send_request_await_response(http::Method::GET, url, None, timeout, vec![])
|
||||
{
|
||||
if let Some(body) = get_blob() {
|
||||
let hash = generate_metadata_hash(&body.bytes);
|
||||
let hash = keccak_256_hash(&body.bytes);
|
||||
if &hash == metadata_hash {
|
||||
return Ok(serde_json::from_slice::<kt::Erc721Metadata>(&body.bytes)
|
||||
.map_err(|_| AppStoreLogError::MetadataNotFound)?);
|
||||
@ -139,11 +146,11 @@ pub fn fetch_metadata_from_url(
|
||||
Err(AppStoreLogError::MetadataNotFound)
|
||||
}
|
||||
|
||||
/// generate a Keccak-256 hash of the metadata bytes
|
||||
pub fn generate_metadata_hash(metadata: &[u8]) -> String {
|
||||
/// generate a Keccak-256 hash string (with 0x prefix) of the metadata bytes
|
||||
pub fn keccak_256_hash(bytes: &[u8]) -> String {
|
||||
use sha3::{Digest, Keccak256};
|
||||
let mut hasher = Keccak256::new();
|
||||
hasher.update(metadata);
|
||||
hasher.update(bytes);
|
||||
format!("0x{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
@ -157,10 +164,10 @@ pub fn generate_package_hash(name: &str, publisher_dnswire: &[u8]) -> String {
|
||||
}
|
||||
|
||||
/// generate a SHA-256 hash of the zip bytes to act as a version hash
|
||||
pub fn generate_version_hash(zip_bytes: &[u8]) -> String {
|
||||
pub fn sha_256_hash(bytes: &[u8]) -> String {
|
||||
use sha2::{Digest, Sha256};
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(zip_bytes);
|
||||
hasher.update(bytes);
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
@ -187,31 +194,23 @@ pub fn new_package(
|
||||
mirror: bool,
|
||||
bytes: Vec<u8>,
|
||||
) -> anyhow::Result<()> {
|
||||
// add to listings
|
||||
state.add_listing(package_id, metadata);
|
||||
|
||||
// set the version hash for this new local package
|
||||
let our_version = generate_version_hash(&bytes);
|
||||
let our_version_hash = sha_256_hash(&bytes);
|
||||
|
||||
let package_state = PackageState {
|
||||
mirrored_from: Some(state.our.node.clone()),
|
||||
our_version,
|
||||
our_version_hash,
|
||||
installed: false,
|
||||
verified: true, // sideloaded apps are implicitly verified because there is no "source" to verify against
|
||||
caps_approved: true, // TODO see if we want to auto-approve local installs
|
||||
manifest_hash: None, // generated in the add fn
|
||||
mirroring: mirror,
|
||||
auto_update: false, // can't auto-update a local package
|
||||
metadata: Some(metadata),
|
||||
};
|
||||
let Ok(()) = state.add_downloaded_package(&package_id, package_state, Some(bytes)) else {
|
||||
return Err(anyhow::anyhow!("failed to add package"));
|
||||
};
|
||||
|
||||
let drive_path = format!("/{package_id}/pkg");
|
||||
if let Ok(Ok(_)) = vfs_request(format!("{}/api", drive_path), vfs::VfsAction::Metadata)
|
||||
.send_and_await_response(VFS_TIMEOUT)
|
||||
{
|
||||
state.downloaded_apis.insert(package_id.to_owned());
|
||||
};
|
||||
Ok(())
|
||||
state.add_downloaded_package(&package_id, package_state, Some(bytes))
|
||||
}
|
||||
|
||||
/// create a new package drive in VFS and add the package zip to it.
|
||||
@ -263,7 +262,7 @@ pub fn create_package_drive(
|
||||
timeout: VFS_TIMEOUT,
|
||||
};
|
||||
let manifest_bytes = manifest_file.read()?;
|
||||
Ok(generate_metadata_hash(&manifest_bytes))
|
||||
Ok(keccak_256_hash(&manifest_bytes))
|
||||
}
|
||||
|
||||
pub fn extract_api(package_id: &PackageId) -> anyhow::Result<bool> {
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
var s='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 78 78"><path fill="url(%23a)" fill-rule="evenodd" d="m67.81 19.54 1.69-4.2s-2.14-2.35-4.75-5c-2.6-2.65-8.1-1.09-8.1-1.09L50.37 2H28.34l-6.28 7.25s-5.5-1.56-8.1 1.1c-2.6 2.64-4.75 4.98-4.75 4.98l1.69 4.21-2.15 6.24s6.3 24.3 7.04 27.28c1.46 5.84 2.45 8.1 6.58 11.06a513.66 513.66 0 0 0 12.85 8.89c1.23.78 2.76 2.1 4.13 2.1 1.38 0 2.91-1.32 4.14-2.1 1.22-.78 8.72-5.92 12.85-8.89 4.13-2.96 5.12-5.22 6.58-11.06.74-2.97 7.04-27.28 7.04-27.28l-2.15-6.24Z" clip-rule="evenodd"/><path fill="%23fff" fill-rule="evenodd" d="M39.35 47.5c.42 0 3.11.97 5.27 2.1 2.15 1.14 3.72 1.95 4.22 2.26.5.32.2.92-.26 1.25-.46.33-6.57 5.15-7.17 5.69-.6.53-1.46 1.41-2.06 1.41-.59 0-1.46-.88-2.05-1.41-.6-.54-6.71-5.36-7.17-5.69-.45-.33-.76-.93-.26-1.25.5-.31 2.07-1.12 4.22-2.25 2.16-1.14 4.85-2.1 5.26-2.1Zm.04-34.36c.2.01 1.36.07 3.02.63 1.84.63 3.83 1.4 4.75 1.4.92 0 7.73-1.32 7.73-1.32s8.06 9.94 8.06 12.06c0 2.13-1.01 2.69-2.03 3.8l-6.05 6.54c-.57.62-1.77 1.55-1.06 3.24.7 1.7 1.73 3.84.58 6.03-1.15 2.18-3.12 3.63-4.38 3.4-1.26-.25-4.23-1.83-5.32-2.55-1.09-.72-4.54-3.62-4.54-4.73 0-1.11 3.57-3.1 4.23-3.56.66-.45 3.67-2.2 3.73-2.9.06-.69.04-.89-.85-2.59-.89-1.7-2.49-3.97-2.22-5.48.26-1.51 2.84-2.3 4.68-3 1.84-.71 5.39-2.05 5.83-2.26.44-.2.33-.4-1.01-.53s-5.15-.65-6.86-.16c-1.72.49-4.65 1.23-4.89 1.62-.24.4-.45.4-.2 1.76.24 1.36 1.5 7.87 1.62 9.02.12 1.16.36 1.92-.87 2.2-1.22.3-3.28.79-3.99.79-.7 0-2.76-.5-3.99-.78-1.22-.29-.98-1.05-.86-2.2.12-1.16 1.38-7.67 1.62-9.03.25-1.35.04-1.37-.2-1.76-.24-.4-3.17-1.13-4.89-1.62-1.71-.49-5.52.03-6.86.16-1.34.13-1.46.32-1.01.53.44.21 3.98 1.55 5.83 2.26 1.84.7 4.42 1.49 4.68 3 .27 1.51-1.33 3.78-2.22 5.48-.89 1.7-.91 1.9-.85 2.6.06.68 3.07 2.44 3.73 2.89.66.45 4.23 2.45 4.23 3.56 0 1.1-3.45 4.01-4.54 4.73-1.1.72-4.06 2.3-5.32 2.54-1.26.24-3.23-1.21-4.38-3.4-1.15-2.18-.12-4.33.58-6.02.7-1.69-.49-2.62-1.06-3.24L17.8 29.7c-1.02-1.1-2.04-1.66-2.04-3.79 0-2.12 8.07-12.06 8.07-12.06s6.81 1.33 7.73 1.33c.92 0 2.9-.78 4.74-1.4a11.3 11.3 0 0 1 3.03-.64h.07Z" clip-rule="evenodd"/><mask id="b" width="52" height="14" x="13" y="2" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="%23fff" d="M56.64 9.25 50.37 2H28.34l-6.28 7.25s-5.5-1.56-8.1 1.1c0 0 7.34-.68 9.86 3.5 0 0 6.81 1.33 7.73 1.33.92 0 2.9-.78 4.74-1.4 1.84-.63 3.06-.64 3.06-.64s1.23 0 3.06.63c1.84.63 3.83 1.4 4.75 1.4.92 0 7.73-1.32 7.73-1.32 2.52-4.18 9.86-3.5 9.86-3.5-2.6-2.66-8.1-1.1-8.1-1.1Z"/></mask><g mask="url(%23b)"><path fill="url(%23c)" d="M56.64 9.25 50.37 2H28.34l-6.28 7.25s-5.5-1.56-8.1 1.1c0 0 7.34-.68 9.86 3.5 0 0 6.81 1.33 7.73 1.33.92 0 2.9-.78 4.74-1.4 1.84-.63 3.06-.64 3.06-.64s1.23 0 3.06.63c1.84.63 3.83 1.4 4.75 1.4.92 0 7.73-1.32 7.73-1.32 2.52-4.18 9.86-3.5 9.86-3.5-2.6-2.66-8.1-1.1-8.1-1.1Z"/></g><defs><linearGradient id="a" x1="8.75" x2="69.96" y1="39.07" y2="39.07" gradientUnits="userSpaceOnUse"><stop stop-color="%23F50"/><stop offset=".41" stop-color="%23F50"/><stop offset=".58" stop-color="%23FF2000"/><stop offset="1" stop-color="%23FF2000"/></linearGradient><linearGradient id="c" x1="15.05" x2="64.75" y1="8.68" y2="8.68" gradientUnits="userSpaceOnUse"><stop stop-color="%23FF452A"/><stop offset="1" stop-color="%23FF2000"/></linearGradient></defs></svg>';export{s as default};
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
var t='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 78 78"><path fill="url(%23a)" d="M71.034 20.5a37.001 37.001 0 0 0-64.084 0l2.22 39.96L71.034 20.5Z"/><path fill="url(%23b)" d="M22.979 48.25 6.958 20.5A37 37 0 0 0 39 76l36.26-37-52.281 9.25Z"/><path fill="url(%23c)" d="M55.021 48.25 39 76a37.001 37.001 0 0 0 32.035-55.5H39l16.021 27.75Z"/><path fill="%23fff" d="M39 57.5a18.5 18.5 0 1 0 0-37 18.5 18.5 0 0 0 0 37Z"/><path fill="%231A73E8" d="M39 53.652a14.65 14.65 0 0 0 13.536-20.26A14.653 14.653 0 1 0 39 53.653Z"/><defs><linearGradient id="a" x1="6.958" x2="71.034" y1="25.125" y2="25.125" gradientUnits="userSpaceOnUse"><stop stop-color="%23D93025"/><stop offset="1" stop-color="%23EA4335"/></linearGradient><linearGradient id="b" x1="43.003" x2="10.961" y1="73.684" y2="18.184" gradientUnits="userSpaceOnUse"><stop stop-color="%231E8E3E"/><stop offset="1" stop-color="%2334A853"/></linearGradient><linearGradient id="c" x1="33.598" x2="65.64" y1="76" y2="20.596" gradientUnits="userSpaceOnUse"><stop stop-color="%23FCC934"/><stop offset="1" stop-color="%23FBBC04"/></linearGradient></defs></svg>';export{t as default};
|
@ -1 +0,0 @@
|
||||
var t='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 78 78"><g clip-path="url(%23a)"><path fill="url(%23b)" d="M68.802 57.066c-.992.516-2.016.97-3.064 1.359a29.456 29.456 0 0 1-10.377 1.85c-13.673 0-25.582-9.395-25.582-21.477a9.106 9.106 0 0 1 4.74-7.892c-12.371.52-15.551 13.413-15.551 20.957 0 21.39 19.685 23.53 23.934 23.53 2.284 0 5.724-.665 7.805-1.33l.376-.115a37.088 37.088 0 0 0 19.251-15.263 1.156 1.156 0 0 0-1.532-1.619Z"/><path fill="url(%23c)" d="M68.802 57.066c-.992.516-2.016.97-3.064 1.359a29.456 29.456 0 0 1-10.377 1.85c-13.673 0-25.582-9.395-25.582-21.477a9.106 9.106 0 0 1 4.74-7.892c-12.371.52-15.551 13.413-15.551 20.957 0 21.39 19.685 23.53 23.934 23.53 2.284 0 5.724-.665 7.805-1.33l.376-.115a37.088 37.088 0 0 0 19.251-15.263 1.156 1.156 0 0 0-1.532-1.619Z" opacity=".35"/><path fill="url(%23d)" d="M32.554 71.751a22.894 22.894 0 0 1-6.562-6.157 23.329 23.329 0 0 1 8.527-34.687c.925-.434 2.458-1.186 4.51-1.157a9.365 9.365 0 0 1 7.429 3.758 9.222 9.222 0 0 1 1.82 5.406c0-.058 7.083-23.01-23.124-23.01-12.69 0-23.125 12.025-23.125 22.605a37.636 37.636 0 0 0 3.498 16.188 37 37 0 0 0 45.209 19.367 21.825 21.825 0 0 1-18.153-2.313h-.03Z"/><path fill="url(%23e)" d="M32.554 71.751a22.894 22.894 0 0 1-6.562-6.157 23.329 23.329 0 0 1 8.527-34.687c.925-.434 2.458-1.186 4.51-1.157a9.365 9.365 0 0 1 7.429 3.758 9.222 9.222 0 0 1 1.82 5.406c0-.058 7.083-23.01-23.124-23.01-12.69 0-23.125 12.025-23.125 22.605a37.636 37.636 0 0 0 3.498 16.188 37 37 0 0 0 45.209 19.367 21.825 21.825 0 0 1-18.153-2.313h-.03Z" opacity=".41"/><path fill="url(%23f)" d="M46.053 45.013c-.26.289-.983.722-.983 1.618 0 .752.492 1.503 1.388 2.11 4.133 2.891 11.967 2.486 11.996 2.486a17.227 17.227 0 0 0 8.759-2.399A17.748 17.748 0 0 0 76 33.537c.087-6.475-2.313-10.782-3.266-12.69C66.577 8.88 53.366 2 39 2A37 37 0 0 0 2 38.48c.145-10.551 10.637-19.078 23.125-19.078 1.012 0 6.793.086 12.14 2.89a20.986 20.986 0 0 1 8.933 8.47c1.763 3.064 2.08 6.966 2.08 8.527 0 1.561-.78 3.845-2.254 5.752l.03-.028Z"/><path fill="url(%23g)" d="M46.053 45.013c-.26.289-.983.722-.983 1.618 0 .752.492 1.503 1.388 2.11 4.133 2.891 11.967 2.486 11.996 2.486a17.227 17.227 0 0 0 8.759-2.399A17.748 17.748 0 0 0 76 33.537c.087-6.475-2.313-10.782-3.266-12.69C66.577 8.88 53.366 2 39 2A37 37 0 0 0 2 38.48c.145-10.551 10.637-19.078 23.125-19.078 1.012 0 6.793.086 12.14 2.89a20.986 20.986 0 0 1 8.933 8.47c1.763 3.064 2.08 6.966 2.08 8.527 0 1.561-.78 3.845-2.254 5.752l.03-.028Z"/></g><defs><radialGradient id="c" cx="0" cy="0" r="1" gradientTransform="matrix(27.5766 0 0 26.1977 47.44 53.553)" gradientUnits="userSpaceOnUse"><stop offset=".7" stop-opacity="0"/><stop offset=".9" stop-opacity=".5"/><stop offset="1"/></radialGradient><radialGradient id="e" cx="0" cy="0" r="1" gradientTransform="matrix(6.20902 -40.9798 33.10754 5.01627 22.395 59.506)" gradientUnits="userSpaceOnUse"><stop offset=".8" stop-opacity="0"/><stop offset=".9" stop-opacity=".5"/><stop offset="1"/></radialGradient><radialGradient id="f" cx="0" cy="0" r="1" gradientTransform="matrix(-2.34024 58.50621 -124.60636 -4.98423 9.484 15.677)" gradientUnits="userSpaceOnUse"><stop stop-color="%2335C1F1"/><stop offset=".1" stop-color="%2334C1ED"/><stop offset=".2" stop-color="%232FC2DF"/><stop offset=".3" stop-color="%232BC3D2"/><stop offset=".7" stop-color="%2336C752"/></radialGradient><radialGradient id="g" cx="0" cy="0" r="1" gradientTransform="rotate(73.74 19.467 59.767) scale(28.1258 22.8719)" gradientUnits="userSpaceOnUse"><stop stop-color="%2366EB6E"/><stop offset="1" stop-color="%2366EB6E" stop-opacity="0"/></radialGradient><linearGradient id="b" x1="18.968" x2="70.537" y1="53.164" y2="53.164" gradientUnits="userSpaceOnUse"><stop stop-color="%230C59A4"/><stop offset="1" stop-color="%23114A8B"/></linearGradient><linearGradient id="d" x1="46.14" x2="13.967" y1="30.791" y2="65.854" gradientUnits="userSpaceOnUse"><stop stop-color="%231B9DE2"/><stop offset=".2" stop-color="%231595DF"/><stop offset=".7" stop-color="%230680D7"/><stop offset="1" stop-color="%230078D4"/></linearGradient><clipPath id="a"><path fill="%23fff" d="M0 0h74v74H0z" transform="translate(2 2)"/></clipPath></defs></svg>';export{t as default};
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
var t='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 78 78"><linearGradient id="a" x2="1" gradientTransform="matrix(0 -54.944 -54.944 0 23.62 79.474)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="%23ff1b2d"/><stop offset=".3" stop-color="%23ff1b2d"/><stop offset=".614" stop-color="%23ff1b2d"/><stop offset="1" stop-color="%23a70014"/></linearGradient><linearGradient id="b" x2="1" gradientTransform="matrix(0 -48.595 -48.595 0 37.854 76.235)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="%239c0000"/><stop offset=".7" stop-color="%23ff4b4b"/><stop offset="1" stop-color="%23ff4b4b"/></linearGradient><path fill="url(%23a)" d="M28.346 80.398C12.691 80.398 0 67.707 0 52.052 0 36.85 11.968 24.443 26.996 23.739a28.244 28.244 0 0 1 20.241 7.18c-3.322-2.203-7.207-3.47-11.359-3.47-6.75 0-12.796 3.348-16.862 8.629-3.134 3.7-5.164 9.169-5.302 15.307v1.335c.138 6.137 2.168 11.608 5.302 15.307 4.066 5.28 10.112 8.63 16.862 8.63 4.152 0 8.038-1.269 11.36-3.474a28.239 28.239 0 0 1-18.785 7.215l-.108.001z" transform="matrix(1.3333 0 0 -1.3333 0 107.2)"/><path fill="url(%23b)" d="M19.016 68.025c2.601 3.07 5.96 4.923 9.631 4.923 8.252 0 14.941-9.356 14.941-20.897s-6.69-20.897-14.941-20.897c-3.67 0-7.03 1.85-9.63 4.922 4.066-5.281 10.11-8.63 16.862-8.63 4.152 0 8.036 1.268 11.359 3.472 5.802 5.19 9.455 12.735 9.455 21.133 0 8.397-3.653 15.94-9.453 21.13-3.324 2.206-7.209 3.473-11.361 3.473-6.75 0-12.796-3.348-16.862-8.63" transform="matrix(1.3333 0 0 -1.3333 0 107.2)"/></svg>';export{t as default};
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
var a='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none"><g clip-path="url(%23a)"><path fill="%230078D4" d="M0 0h22.755v22.745H0V0Zm25.245 0H48v22.745H25.245V0ZM0 25.245h22.755V48H0V25.245Zm25.245 0H48V48H25.245"/></g><defs><clipPath id="a"><path fill="%23fff" d="M0 0h48v48H0z"/></clipPath></defs></svg>';export{a as default};
|
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
||||
var t='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="none"><rect width="26.6" height="26.6" x=".7" y=".7" fill="%232D374B" stroke="%2396BEDC" stroke-width="1.4" rx="13.3"/><mask id="a" width="28" height="28" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><rect width="28" height="28" fill="%23C4C4C4" rx="14"/></mask><g mask="url(%23a)"><path fill="%2328A0F0" d="m14.0861 18.6041 6.5014 10.2239 4.0057-2.3213-7.86-12.3943-2.6471 4.4917Zm13.0744 3.4692-.003-1.8599-7.3064-11.407-2.3087 3.9173 7.091 11.4303 2.172-1.2586a.9628.9628 0 0 0 .3555-.7009l-.0004-.1212Z"/><rect width="25.9" height="25.9" x="1.05" y="1.05" fill="url(%23b)" fill-opacity=".3" stroke="%2396BEDC" stroke-width="2.1" rx="12.95"/><path fill="%23fff" d="m.3634 28.2207-3.07-1.7674-.234-.8333L7.7461 9.0194c.7298-1.1913 2.3197-1.575 3.7957-1.5541l1.7323.0457L.3634 28.2207ZM19.1655 7.511l-4.5653.0166L2.24 27.9533l3.6103 2.0788.9818-1.6652L19.1655 7.511Z"/></g><defs><linearGradient id="b" x1="0" x2="14" y1="0" y2="28" gradientUnits="userSpaceOnUse"><stop stop-color="%23fff"/><stop offset="1" stop-color="%23fff" stop-opacity="0"/></linearGradient></defs></svg>%0A';export{t as default};
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
var l='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="none"><path fill="%23fff" d="M23 5H5v18h18V5Z"/><path fill="%23E84142" fill-rule="evenodd" d="M14 28c-7.513.008-14-6.487-14-14C0 6.196 6.043-.008 14 0c7.95.008 14 6.196 14 14 0 7.505-6.495 13.992-14 14Zm-3.971-7.436H7.315c-.57 0-.851 0-1.023-.11a.69.69 0 0 1-.313-.54c-.01-.202.13-.45.412-.944l6.7-11.809c.285-.501.43-.752.612-.845.195-.1.429-.1.625 0 .182.093.326.344.611.845l1.377 2.404.007.013c.308.538.464.81.533 1.097a2.04 2.04 0 0 1 0 .954c-.07.289-.224.564-.536 1.11l-3.52 6.22-.009.017c-.31.542-.467.817-.684 1.024a2.048 2.048 0 0 1-.835.485c-.285.079-.604.079-1.243.079Zm6.852 0h3.888c.574 0 .862 0 1.034-.113a.687.687 0 0 0 .313-.543c.01-.196-.128-.434-.398-.9a8.198 8.198 0 0 1-.028-.048l-1.948-3.332-.022-.037c-.274-.463-.412-.697-.59-.787a.684.684 0 0 0-.621 0c-.179.093-.323.337-.608.828l-1.94 3.331-.007.012c-.284.49-.426.735-.416.936.014.22.127.423.313.543.168.11.456.11 1.03.11Z" clip-rule="evenodd"/></svg>%0A';export{l as default};
|
@ -1 +0,0 @@
|
||||
var l='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28"><g fill="none" fill-rule="evenodd"><path fill="%230052FF" fill-rule="nonzero" d="M14 28a14 14 0 1 0 0-28 14 14 0 0 0 0 28Z"/><path fill="%23FFF" d="M13.967 23.86c5.445 0 9.86-4.415 9.86-9.86 0-5.445-4.415-9.86-9.86-9.86-5.166 0-9.403 3.974-9.825 9.03h14.63v1.642H4.142c.413 5.065 4.654 9.047 9.826 9.047Z"/></g></svg>';export{l as default};
|
@ -1 +0,0 @@
|
||||
var l='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 28 28"><rect width="28" height="28" fill="%23000" rx="14"/><rect width="28" height="28" fill="url(%23a)" fill-opacity=".1" rx="14"/><path fill="%23FCFC03" d="M7.735 7.836 5.581 9.73c-.163.137-.065.447.163.447h13.773c.163 0 .261.173.229.345l-.686 2.1a.247.247 0 0 1-.228.173h-5.353a.247.247 0 0 0-.228.172l-.555 1.515a.253.253 0 0 0 .228.345h5.19c.163 0 .26.172.228.344l-.816 2.652a.247.247 0 0 1-.228.172H9.563c-.163 0-.261-.172-.229-.31l1.534-5.786c.033-.103-.032-.24-.098-.275l-1.631-.999c-.131-.069-.294 0-.36.138l-2.545 9.16c-.065.173.065.31.228.31h10.346c.033 0 .066 0 .098-.034l2.579-1.309a.262.262 0 0 0 .13-.137l1.012-3.134c.033-.07 0-.207-.065-.242l-1.208-1.274c-.098-.103-.065-.344.065-.413l1.86-.93c.066-.034.099-.069.099-.137l1.11-3.134c.032-.104 0-.241-.066-.276l-1.534-1.377c-.065-.035-.065-.07-.163-.07H7.898a.535.535 0 0 0-.163.07Z"/><defs><linearGradient id="a" x1="0" x2="14" y1="0" y2="28" gradientUnits="userSpaceOnUse"><stop stop-color="%23fff"/><stop offset="1" stop-color="%23fff" stop-opacity="0"/></linearGradient></defs></svg>';export{l as default};
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
var l='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="none"><g clip-path="url(%23a)"><path fill="%23F0B90B" fill-rule="evenodd" d="M14 0c7.733 0 14 6.267 14 14s-6.267 14-14 14S0 21.733 0 14 6.267 0 14 0Z" clip-rule="evenodd"/><path fill="%23fff" d="m7.694 14 .01 3.702 3.146 1.85v2.168l-4.986-2.924v-5.878L7.694 14Zm0-3.702v2.157l-1.832-1.083V9.214l1.832-1.083 1.841 1.083-1.84 1.084Zm4.47-1.084 1.832-1.083 1.84 1.083-1.84 1.084-1.832-1.084Z"/><path fill="%23fff" d="M9.018 16.935v-2.168l1.832 1.084v2.157l-1.832-1.073Zm3.146 3.394 1.832 1.084 1.84-1.084v2.157l-1.84 1.084-1.832-1.084V20.33Zm6.3-11.115 1.832-1.083 1.84 1.083v2.158l-1.84 1.083v-2.157l-1.832-1.084Zm1.832 8.488.01-3.702 1.831-1.084v5.879l-4.986 2.924v-2.167l3.145-1.85Z"/><path fill="%23fff" d="m18.982 16.935-1.832 1.073v-2.157l1.832-1.084v2.168Z"/><path fill="%23fff" d="m18.982 11.065.01 2.168-3.155 1.85v3.712l-1.831 1.073-1.832-1.073v-3.711l-3.155-1.851v-2.168l1.84-1.083 3.135 1.86 3.155-1.86 1.84 1.083h-.007Zm-9.964-3.7 4.977-2.935 4.987 2.935-1.832 1.083-3.154-1.86-3.146 1.86-1.832-1.083Z"/></g><defs><clipPath id="a"><path fill="%23fff" d="M0 0h28v28H0z"/></clipPath></defs></svg>';export{l as default};
|
@ -1 +0,0 @@
|
||||
import{B as p,g as m,s as y,d as w,i as k,e as O,a as E,b as L,H as h,f as x}from"./index-9L6Bkx0q.js";class M extends p{constructor({callbackSelector:s,cause:e,data:n,extraData:c,sender:u,urls:t}){var i;super(e.shortMessage||"An error occurred while fetching for an offchain result.",{cause:e,metaMessages:[...e.metaMessages||[],(i=e.metaMessages)!=null&&i.length?"":[],"Offchain Gateway Call:",t&&[" Gateway URL(s):",...t.map(f=>` ${m(f)}`)],` Sender: ${u}`,` Data: ${n}`,` Callback selector: ${s}`,` Extra data: ${c}`].flat()}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"OffchainLookupError"})}}class R extends p{constructor({result:s,url:e}){super("Offchain gateway response is malformed. Response data must be a hex value.",{metaMessages:[`Gateway URL: ${m(e)}`,`Response: ${y(s)}`]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"OffchainLookupResponseMalformedError"})}}class $ extends p{constructor({sender:s,to:e}){super("Reverted sender address does not match target contract address (`to`).",{metaMessages:[`Contract address: ${e}`,`OffchainLookup sender address: ${s}`]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"OffchainLookupSenderMismatchError"})}}const j="0x556f1830",S={name:"OffchainLookup",type:"error",inputs:[{name:"sender",type:"address"},{name:"urls",type:"string[]"},{name:"callData",type:"bytes"},{name:"callbackFunction",type:"bytes4"},{name:"extraData",type:"bytes"}]};async function v(o,{blockNumber:s,blockTag:e,data:n,to:c}){const{args:u}=w({data:n,abi:[S]}),[t,i,f,a,r]=u,{ccipRead:d}=o,b=d&&typeof(d==null?void 0:d.request)=="function"?d.request:q;try{if(!k(c,t))throw new $({sender:t,to:c});const l=await b({data:f,sender:t,urls:i}),{data:g}=await L(o,{blockNumber:s,blockTag:e,data:E([a,O([{type:"bytes"},{type:"bytes"}],[l,r])]),to:c});return g}catch(l){throw new M({callbackSelector:a,cause:l,data:n,extraData:r,sender:t,urls:i})}}async function q({data:o,sender:s,urls:e}){var c;let n=new Error("An unknown error occurred.");for(let u=0;u<e.length;u++){const t=e[u],i=t.includes("{data}")?"GET":"POST",f=i==="POST"?{data:o,sender:s}:void 0;try{const a=await fetch(t.replace("{sender}",s).replace("{data}",o),{body:JSON.stringify(f),method:i});let r;if((c=a.headers.get("Content-Type"))!=null&&c.startsWith("application/json")?r=(await a.json()).data:r=await a.text(),!a.ok){n=new h({body:f,details:r!=null&&r.error?y(r.error):a.statusText,headers:a.headers,status:a.status,url:t});continue}if(!x(r)){n=new R({result:r,url:t});continue}return r}catch(a){n=new h({body:f,details:a.message,url:t})}}throw n}export{q as ccipRequest,v as offchainLookup,S as offchainLookupAbiItem,j as offchainLookupSignature};
|
@ -1 +0,0 @@
|
||||
var e='data:image/svg+xml,<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">%0A<rect width="28" height="28" fill="%232C5FF6"/>%0A<path fill-rule="evenodd" clip-rule="evenodd" d="M14 23.8C19.4124 23.8 23.8 19.4124 23.8 14C23.8 8.58761 19.4124 4.2 14 4.2C8.58761 4.2 4.2 8.58761 4.2 14C4.2 19.4124 8.58761 23.8 14 23.8ZM11.55 10.8C11.1358 10.8 10.8 11.1358 10.8 11.55V16.45C10.8 16.8642 11.1358 17.2 11.55 17.2H16.45C16.8642 17.2 17.2 16.8642 17.2 16.45V11.55C17.2 11.1358 16.8642 10.8 16.45 10.8H11.55Z" fill="white"/>%0A</svg>%0A';export{e as default};
|
@ -1 +0,0 @@
|
||||
var l='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none"><g clip-path="url(%23a)"><path fill="url(%23b)" d="M0 16c0-5.6 0-8.4 1.09-10.54a10 10 0 0 1 4.37-4.37C7.6 0 10.4 0 16 0h16c5.6 0 8.4 0 10.54 1.09a10 10 0 0 1 4.37 4.37C48 7.6 48 10.4 48 16v16c0 5.6 0 8.4-1.09 10.54a10.001 10.001 0 0 1-4.37 4.37C40.4 48 37.6 48 32 48H16c-5.6 0-8.4 0-10.54-1.09a10 10 0 0 1-4.37-4.37C0 40.4 0 37.6 0 32V16Z"/><path fill="%23000" fill-opacity=".08" fill-rule="evenodd" d="M1.133 9.513C1 11.131 1 13.183 1 16v16c0 2.817 0 4.87.133 6.486.131 1.606.387 2.695.848 3.6a9 9 0 0 0 3.933 3.933c.905.461 1.994.717 3.6.848C11.13 47 13.183 47 16 47h16c2.817 0 4.87 0 6.486-.133 1.606-.131 2.695-.387 3.6-.848a9 9 0 0 0 3.933-3.933c.461-.905.717-1.994.848-3.6C47 36.87 47 34.816 47 32V16c0-2.817 0-4.87-.133-6.487-.131-1.605-.387-2.694-.848-3.599a9 9 0 0 0-3.933-3.933c-.905-.461-1.994-.717-3.6-.848C36.87 1 34.816 1 32 1H16c-2.817 0-4.87 0-6.487.133-1.605.131-2.694.387-3.599.848a9 9 0 0 0-3.933 3.933c-.461.905-.717 1.994-.848 3.6ZM1.09 5.46C0 7.6 0 10.4 0 16v16c0 5.6 0 8.4 1.09 10.54a10 10 0 0 0 4.37 4.37C7.6 48 10.4 48 16 48h16c5.6 0 8.4 0 10.54-1.09a10.001 10.001 0 0 0 4.37-4.37C48 40.4 48 37.6 48 32V16c0-5.6 0-8.4-1.09-10.54a10 10 0 0 0-4.37-4.37C40.4 0 37.6 0 32 0H16C10.4 0 7.6 0 5.46 1.09a10 10 0 0 0-4.37 4.37Z" clip-rule="evenodd"/><g clip-path="url(%23c)"><path fill="%23000" fill-opacity=".12" d="M24.716 35.795c.531 0 .968-.234 1.452-.687l9.989-9.38c.537-.512.679-.939.679-1.33 0-.4-.129-.823-.68-1.34l-9.988-9.318c-.527-.497-.903-.74-1.435-.74-.762 0-1.271.592-1.271 1.32v5.075h-.44C14.403 19.395 10 24.689 10 34.373c0 .903.52 1.422 1.119 1.422.482 0 .944-.15 1.31-.857 2.031-4.108 5.295-5.486 10.594-5.486h.439v5.062c0 .73.509 1.281 1.254 1.281Z"/></g><g clip-path="url(%23d)"><path fill="%23fff" d="M24.716 34.795c.531 0 .968-.234 1.452-.687l9.989-9.38c.537-.512.679-.939.679-1.33 0-.4-.129-.823-.68-1.34l-9.988-9.318c-.527-.497-.903-.74-1.435-.74-.762 0-1.271.592-1.271 1.32v5.075h-.44C14.403 18.395 10 23.689 10 33.373c0 .903.52 1.422 1.119 1.422.482 0 .944-.15 1.31-.857 2.031-4.108 5.295-5.486 10.594-5.486h.439v5.062c0 .73.509 1.281 1.254 1.281Z"/></g></g><defs><clipPath id="a"><path fill="%23fff" d="M0 0h48v48H0z"/></clipPath><clipPath id="c"><path fill="%23fff" d="M10 13h26.836v22.808H10z"/></clipPath><clipPath id="d"><path fill="%23fff" d="M10 12h26.836v22.808H10z"/></clipPath><linearGradient id="b" x1="24" x2="24" y1="0" y2="48" gradientUnits="userSpaceOnUse"><stop stop-color="%2359627A"/><stop offset="1" stop-color="%234A5266"/></linearGradient></defs></svg>';export{l as default};
|
@ -1 +0,0 @@
|
||||
var a='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path fill="url(%23a)" d="M0 16c0-5.6 0-8.4 1.09-10.54a10 10 0 0 1 4.37-4.37C7.6 0 10.4 0 16 0h16c5.6 0 8.4 0 10.54 1.09a10 10 0 0 1 4.37 4.37C48 7.6 48 10.4 48 16v16c0 5.6 0 8.4-1.09 10.54a10.001 10.001 0 0 1-4.37 4.37C40.4 48 37.6 48 32 48H16c-5.6 0-8.4 0-10.54-1.09a10 10 0 0 1-4.37-4.37C0 40.4 0 37.6 0 32V16Z"/><path fill="url(%23b)" fill-opacity=".7" d="M0 16c0-5.6 0-8.4 1.09-10.54a10 10 0 0 1 4.37-4.37C7.6 0 10.4 0 16 0h16c5.6 0 8.4 0 10.54 1.09a10 10 0 0 1 4.37 4.37C48 7.6 48 10.4 48 16v16c0 5.6 0 8.4-1.09 10.54a10.001 10.001 0 0 1-4.37 4.37C40.4 48 37.6 48 32 48H16c-5.6 0-8.4 0-10.54-1.09a10 10 0 0 1-4.37-4.37C0 40.4 0 37.6 0 32V16Z" style="mix-blend-mode:screen"/><path fill="%23000" fill-opacity=".04" fill-rule="evenodd" d="M32 1H16c-2.817 0-4.87 0-6.487.133-1.605.131-2.694.387-3.599.848a9 9 0 0 0-3.933 3.933c-.461.905-.717 1.994-.848 3.6C1 11.13 1 13.183 1 16v16c0 2.817 0 4.87.133 6.486.131 1.606.387 2.695.848 3.6a9 9 0 0 0 3.933 3.933c.905.461 1.994.717 3.6.848C11.13 47 13.183 47 16 47h16c2.817 0 4.87 0 6.486-.133 1.606-.131 2.695-.387 3.6-.848a9 9 0 0 0 3.933-3.933c.461-.905.717-1.994.848-3.6C47 36.87 47 34.816 47 32V16c0-2.817 0-4.87-.133-6.487-.131-1.605-.387-2.694-.848-3.599a9 9 0 0 0-3.933-3.933c-.905-.461-1.994-.717-3.6-.848C36.87 1 34.816 1 32 1ZM1.09 5.46C0 7.6 0 10.4 0 16v16c0 5.6 0 8.4 1.09 10.54a10 10 0 0 0 4.37 4.37C7.6 48 10.4 48 16 48h16c5.6 0 8.4 0 10.54-1.09a10.001 10.001 0 0 0 4.37-4.37C48 40.4 48 37.6 48 32V16c0-5.6 0-8.4-1.09-10.54a10 10 0 0 0-4.37-4.37C40.4 0 37.6 0 32 0H16C10.4 0 7.6 0 5.46 1.09a10 10 0 0 0-4.37 4.37Z" clip-rule="evenodd"/><path fill="%23000" fill-opacity=".12" d="M24 13a1.5 1.5 0 0 0-1.5 1.5v8.2c0 .28 0 .42-.055.527a.5.5 0 0 1-.218.218c-.107.055-.247.055-.527.055h-8.2a1.5 1.5 0 0 0 0 3h8.2c.28 0 .42 0 .527.055a.5.5 0 0 1 .218.218c.055.107.055.247.055.527v8.2a1.5 1.5 0 0 0 3 0v-8.2c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.218c.107-.055.247-.055.527-.055h8.2a1.5 1.5 0 0 0 0-3h-8.2c-.28 0-.42 0-.527-.055a.5.5 0 0 1-.218-.218c-.055-.107-.055-.247-.055-.527v-8.2A1.5 1.5 0 0 0 24 13Z"/><path fill="%23fff" d="M24 12a1.5 1.5 0 0 0-1.5 1.5v8.2c0 .28 0 .42-.055.527a.5.5 0 0 1-.218.218c-.107.055-.247.055-.527.055h-8.2a1.5 1.5 0 0 0 0 3h8.2c.28 0 .42 0 .527.055a.5.5 0 0 1 .218.218c.055.107.055.247.055.527v8.2a1.5 1.5 0 0 0 3 0v-8.2c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.218c.107-.055.247-.055.527-.055h8.2a1.5 1.5 0 0 0 0-3h-8.2c-.28 0-.42 0-.527-.055a.5.5 0 0 1-.218-.218c-.055-.107-.055-.247-.055-.527v-8.2A1.5 1.5 0 0 0 24 12Z"/><defs><radialGradient id="a" cx="0" cy="0" r="1" gradientTransform="rotate(-40.077 73.374 58.603) scale(94.7484)" gradientUnits="userSpaceOnUse"><stop offset=".276" stop-color="%2320FF4D"/><stop offset=".464" stop-color="%231499FF"/><stop offset=".755" stop-color="%23FF6FC5"/><stop offset="1" stop-color="%23BC67FF"/></radialGradient><radialGradient id="b" cx="0" cy="0" r="1" gradientTransform="rotate(45 5.303 -12.803) scale(78.4889)" gradientUnits="userSpaceOnUse"><stop stop-color="red"/><stop offset="1" stop-color="%2300A3FF"/></radialGradient></defs></svg>';export{a as default};
|
@ -1 +0,0 @@
|
||||
var l='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="28" height="28"><defs><linearGradient id="A" x1="-18.275%" x2="84.959%" y1="8.219%" y2="71.393%"><stop offset="0%" stop-color="%23002d74"/><stop offset="100%" stop-color="%23001246"/></linearGradient><circle id="B" cx="14" cy="14" r="14"/></defs><g fill-rule="evenodd"><mask id="C" fill="%23fff"><use xlink:href="%23B"/></mask><g fill-rule="nonzero"><path fill="url(%23A)" d="M-1.326-1.326h30.651v30.651H-1.326z" mask="url(%23C)"/><g fill="%23fff"><path d="M14.187 6L7 10.175v8.35l7.187 4.175 7.175-4.175v-8.35L14.187 6zm5.046 11.286l-5.058 2.936-5.046-2.936v-5.871l5.058-2.936 5.046 2.936v5.871z"/><path d="M14.187 22.7l7.175-4.175v-8.35L14.187 6v2.479l5.046 2.936v5.883l-5.058 2.936V22.7h.012z"/><path d="M14.175 6L7 10.175v8.35l7.175 4.175v-2.479l-5.046-2.936v-5.883l5.046-2.924V6zm3.36 10.299l-3.348 1.949-3.36-1.949v-3.898l3.36-1.949 3.348 1.949-1.399.818-1.961-1.143-1.949 1.143v2.274l1.961 1.143 1.961-1.143 1.387.806z"/></g></g></g></svg>';export{l as default};
|
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
||||
var e='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="none"><path fill="%2325292E" fill-rule="evenodd" d="M14 28a14 14 0 1 0 0-28 14 14 0 0 0 0 28Z" clip-rule="evenodd"/><path fill="url(%23a)" fill-opacity=".3" fill-rule="evenodd" d="M14 28a14 14 0 1 0 0-28 14 14 0 0 0 0 28Z" clip-rule="evenodd"/><path fill="url(%23b)" d="M8.19 14.77 14 18.21l5.8-3.44-5.8 8.19-5.81-8.19Z"/><path fill="%23fff" d="m14 16.93-5.81-3.44L14 4.34l5.81 9.15L14 16.93Z"/><defs><linearGradient id="a" x1="0" x2="14" y1="0" y2="28" gradientUnits="userSpaceOnUse"><stop stop-color="%23fff"/><stop offset="1" stop-color="%23fff" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="14" x2="14" y1="14.77" y2="22.96" gradientUnits="userSpaceOnUse"><stop stop-color="%23fff"/><stop offset="1" stop-color="%23fff" stop-opacity=".9"/></linearGradient></defs></svg>%0A';export{e as default};
|
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
||||
var t='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="none"><rect width="28" height="28" fill="url(%23a)" rx="14"/><g clip-path="url(%23b)"><path fill="%23FFF100" d="M22.458 18.409v-.875c0-.162-.258-.317-.72-.457l.011-1.088c0-1.676-.49-3.312-1.402-4.685a7.833 7.833 0 0 0-3.702-2.994l-.033-.218a.639.639 0 0 0-.138-.315.592.592 0 0 0-.277-.188 7.429 7.429 0 0 0-4.395 0 .592.592 0 0 0-.278.188.64.64 0 0 0-.14.315l-.031.203a7.83 7.83 0 0 0-3.727 2.991 8.474 8.474 0 0 0-1.414 4.703v1.093c-.456.139-.71.292-.71.454v.874a.224.224 0 0 0 .03.147c.227-.178.487-.303.764-.366.704-.181 1.42-.307 2.143-.378.202-.024.407-.003.601.063s.372.174.523.318a2.945 2.945 0 0 0 2.043.836h4.748c.756 0 1.485-.299 2.043-.836a1.42 1.42 0 0 1 .522-.32c.194-.067.4-.09.602-.066a14.2 14.2 0 0 1 2.143.376c.262.053.51.167.724.334.012.013.027.024.037.036a.227.227 0 0 0 .033-.145Z"/><path fill="url(%23c)" d="M9.574 16.569c-.006-.2-.01-.402-.01-.604.003-3.04.677-5.765 1.79-7.668a7.83 7.83 0 0 0-3.728 2.99 8.474 8.474 0 0 0-1.414 4.702v1.093a17.98 17.98 0 0 1 3.362-.513Z"/><path fill="url(%23d)" d="M21.749 15.989a8.409 8.409 0 0 0-1.773-5.199c.498 1.674.746 3.42.735 5.173 0 .296-.008.59-.02.88a9.2 9.2 0 0 1 1.045.234l.013-1.088Z"/><path fill="url(%23e)" d="M21.664 18.187c-.705-.18-1.42-.306-2.143-.377a1.365 1.365 0 0 0-.602.064 1.416 1.416 0 0 0-.523.32 2.943 2.943 0 0 1-2.043.835h-4.745a2.945 2.945 0 0 1-2.043-.835 1.417 1.417 0 0 0-.522-.322 1.366 1.366 0 0 0-.602-.065 14.18 14.18 0 0 0-2.143.377 1.962 1.962 0 0 0-.764.367c.36.58 4.006 1.19 8.448 1.19s8.086-.612 8.447-1.19c-.013-.012-.027-.023-.037-.035a1.8 1.8 0 0 0-.728-.329Z"/><path fill="%230A0A0A" d="m13.98 9.823-1.818 3.258 1.817 1.188V9.824Z"/><path fill="%234B4D4D" d="M13.98 9.824v4.444l1.817-1.186-1.817-3.258Zm0 5.09v1.55c.034-.052 1.817-2.738 1.817-2.74l-1.817 1.19Z"/><path fill="%230A0A0A" d="m13.98 14.914-1.818-1.187 1.818 2.737v-1.55Z"/></g><defs><linearGradient id="a" x1="14" x2="14" y1="0" y2="28" gradientUnits="userSpaceOnUse"><stop stop-color="%23F7F1FD"/><stop offset="1" stop-color="%23FBFCDC"/></linearGradient><linearGradient id="c" x1="8.783" x2="8.783" y1="17.082" y2="8.297" gradientUnits="userSpaceOnUse"><stop stop-color="%23EDCF00"/><stop offset=".33" stop-color="%23F0D500"/><stop offset=".77" stop-color="%23F9E500"/><stop offset="1" stop-color="%23FFF100"/></linearGradient><linearGradient id="d" x1="20.862" x2="20.862" y1="17.146" y2="10.79" gradientUnits="userSpaceOnUse"><stop stop-color="%23EDCF00"/><stop offset=".59" stop-color="%23F7E100"/><stop offset="1" stop-color="%23FFF100"/></linearGradient><radialGradient id="e" cx="0" cy="0" r="1" gradientTransform="matrix(6.30353 0 0 6.64935 6.45 23.084)" gradientUnits="userSpaceOnUse"><stop stop-color="%23FFF100"/><stop offset=".23" stop-color="%23F9E500"/><stop offset=".67" stop-color="%23F0D500"/><stop offset="1" stop-color="%23EDCF00"/></radialGradient><clipPath id="b"><path fill="%23fff" d="M0 0h17v13H0z" transform="translate(5.5 7)"/></clipPath></defs></svg>%0A';export{t as default};
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
var l='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 28 28"><g clip-path="url(%23a)"><path fill="%23F9F7EC" d="M14 28c7.732 0 14-6.268 14-14S21.732 0 14 0 0 6.268 0 14s6.268 14 14 14Z"/><g clip-path="url(%23b)"><mask id="c" width="28" height="28" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:luminance"><path fill="%23fff" d="M28 0H0v28h28V0Z"/></mask><g mask="url(%23c)"><path fill="url(%23d)" fill-rule="evenodd" d="M6.51 24.04A12.508 12.508 0 0 1 1.473 14C1.474 7.082 7.082 1.474 14 1.474c5.71 0 11.945 3.412 13.453 8.637C25.768 4.272 20.383 0 14 0 6.268 0 0 6.268 0 14s6.268 14 14 14 14-6.268 14-14v-.737h-.77l-.102-.001c-.09-.002-.222-.004-.387-.01-.33-.011-.789-.034-1.303-.078-1.05-.092-2.248-.27-3.064-.596-1.313-.524-2.054-1.219-2.9-2.032l-.05-.047c-.848-.815-1.8-1.73-3.452-2.508-1.628-.766-3.427-.643-4.749-.37a12.04 12.04 0 0 0-2.138.656c-.665.28-1.31.611-1.964.919 0 0 1.281.351 1.915.547l.106.034a7.416 7.416 0 0 1 1.605.767c.683.44 1.25.992 1.482 1.671-1.451.19-2.812.828-3.83 1.426a15.679 15.679 0 0 0-1.91 1.33c-.084.068-1.394 1.222-1.394 1.222s1.69.18 2.524.348c.555.112 1.303.292 2.083.564.784.274 1.576.632 2.233 1.09.659.46 1.14.992 1.379 1.6.693 1.771.013 3.497-1.353 4.467-1.35.96-3.4 1.187-5.452-.221Zm4.776 2.192a5.647 5.647 0 0 0 1.529-.769c1.858-1.32 2.836-3.74 1.871-6.205-.379-.969-1.103-1.71-1.907-2.27-.806-.563-1.733-.975-2.591-1.274A16.895 16.895 0 0 0 8.6 15.25c.17-.11.352-.225.546-.339 1.134-.667 2.562-1.28 3.932-1.28 2.064 0 3.602.634 5.07 1.314l.402.188c1.312.615 2.69 1.262 4.291 1.262a7.463 7.463 0 0 0 3.595-.893C25.695 21.712 20.41 26.526 14 26.526c-.932 0-1.84-.101-2.714-.294Zm13.559-11.635a6.172 6.172 0 0 1-2.003.324c-1.256 0-2.335-.503-3.705-1.142l-.368-.171c-1.372-.636-2.959-1.309-5.024-1.43-.277-1.368-1.314-2.303-2.202-2.873a7.855 7.855 0 0 0-.295-.181c.088-.021.18-.041.272-.06 1.193-.246 2.618-.307 3.824.26 1.432.674 2.24 1.45 3.08 2.257l.029.028c.864.83 1.779 1.7 3.374 2.338.887.354 2.034.544 3.018.65Z" clip-rule="evenodd"/></g></g></g><defs><clipPath id="a"><path fill="%23fff" d="M0 0h28v28H0z"/></clipPath><clipPath id="b"><path fill="%23fff" d="M0 0h28v28H0z"/></clipPath><linearGradient id="d" x1="0" x2="28.502" y1="28" y2="27.479" gradientUnits="userSpaceOnUse"><stop stop-color="%2329CCB9"/><stop offset=".495" stop-color="%230091FF"/><stop offset="1" stop-color="%23FF66B7"/></linearGradient></defs></svg>';export{l as default};
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
var t='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="none"><rect width="28" height="28" fill="%23FF3131" rx="14"/><rect width="28" height="28" fill="url(%23a)" fill-opacity=".3" rx="14"/><path fill="%23fff" d="M9.22 18.35c2.7 0 4.86-2.2 4.86-5.38 0-2.19-1.47-3.8-3.98-3.8-2.72 0-4.85 2.2-4.85 5.38 0 2.2 1.5 3.8 3.97 3.8Zm.83-7.35c1.06 0 1.74.81 1.74 2.1 0 1.9-1.11 3.42-2.51 3.42-1.06 0-1.74-.82-1.74-2.1 0-1.89 1.11-3.42 2.5-3.42Zm6.38-1.68-1.88 8.88h2.26l.55-2.6h1.47c2.43 0 4.01-1.38 4.01-3.6 0-1.61-1.17-2.68-3.1-2.68h-3.3Zm1.9 1.74h.94c.83 0 1.3.38 1.3 1.14 0 1-.68 1.7-1.74 1.7h-1.11l.6-2.84Z"/><defs><linearGradient id="a" x1="0" x2="14" y1="0" y2="28" gradientUnits="userSpaceOnUse"><stop stop-color="%23fff"/><stop offset="1" stop-color="%23fff" stop-opacity="0"/></linearGradient></defs></svg>%0A';export{t as default};
|
@ -1 +0,0 @@
|
||||
var l='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="28" height="28"><defs><linearGradient id="A" x1="-18.275%" x2="84.959%" y1="8.219%" y2="71.393%"><stop offset="0%" stop-color="%23a229c5"/><stop offset="100%" stop-color="%237b3fe4"/></linearGradient><circle id="B" cx="14" cy="14" r="14"/></defs><g fill-rule="evenodd"><mask id="C" fill="%23fff"><use xlink:href="%23B"/></mask><g fill-rule="nonzero"><path fill="url(%23A)" d="M-1.326-1.326h30.651v30.651H-1.326z" mask="url(%23C)"/><path fill="%23fff" d="M18.049 17.021l3.96-2.287a.681.681 0 0 0 .34-.589V9.572a.683.683 0 0 0-.34-.59l-3.96-2.286a.682.682 0 0 0-.68 0l-3.96 2.287a.682.682 0 0 0-.34.589v8.173L10.29 19.35l-2.777-1.604v-3.207l2.777-1.604 1.832 1.058V11.84l-1.492-.861a.681.681 0 0 0-.68 0l-3.96 2.287a.681.681 0 0 0-.34.589v4.573c0 .242.13.468.34.59l3.96 2.286a.68.68 0 0 0 .68 0l3.96-2.286a.682.682 0 0 0 .34-.589v-8.174l.05-.028 2.728-1.575 2.777 1.603v3.208l-2.777 1.603-1.83-1.056v2.151l1.49.86a.68.68 0 0 0 .68 0z"/></g></g></svg>';export{l as default};
|
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
||||
var a='data:image/svg+xml,<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">%0A<rect width="120" height="120" fill="url(%23paint0_linear_62_329)"/>%0A<path d="M20 38H26C56.9279 38 82 63.0721 82 94V100H94C97.3137 100 100 97.3137 100 94C100 53.1309 66.8691 20 26 20C22.6863 20 20 22.6863 20 26V38Z" fill="url(%23paint1_radial_62_329)"/>%0A<path d="M84 94H100C100 97.3137 97.3137 100 94 100H84V94Z" fill="url(%23paint2_linear_62_329)"/>%0A<path d="M26 20L26 36H20L20 26C20 22.6863 22.6863 20 26 20Z" fill="url(%23paint3_linear_62_329)"/>%0A<path d="M20 36H26C58.0325 36 84 61.9675 84 94V100H66V94C66 71.9086 48.0914 54 26 54H20V36Z" fill="url(%23paint4_radial_62_329)"/>%0A<path d="M68 94H84V100H68V94Z" fill="url(%23paint5_linear_62_329)"/>%0A<path d="M20 52L20 36L26 36L26 52H20Z" fill="url(%23paint6_linear_62_329)"/>%0A<path d="M20 62C20 65.3137 22.6863 68 26 68C40.3594 68 52 79.6406 52 94C52 97.3137 54.6863 100 58 100H68V94C68 70.804 49.196 52 26 52H20V62Z" fill="url(%23paint7_radial_62_329)"/>%0A<path d="M52 94H68V100H58C54.6863 100 52 97.3137 52 94Z" fill="url(%23paint8_radial_62_329)"/>%0A<path d="M26 68C22.6863 68 20 65.3137 20 62L20 52L26 52L26 68Z" fill="url(%23paint9_radial_62_329)"/>%0A<defs>%0A<linearGradient id="paint0_linear_62_329" x1="60" y1="0" x2="60" y2="120" gradientUnits="userSpaceOnUse">%0A<stop stop-color="%23174299"/>%0A<stop offset="1" stop-color="%23001E59"/>%0A</linearGradient>%0A<radialGradient id="paint1_radial_62_329" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(26 94) rotate(-90) scale(74)">%0A<stop offset="0.770277" stop-color="%23FF4000"/>%0A<stop offset="1" stop-color="%238754C9"/>%0A</radialGradient>%0A<linearGradient id="paint2_linear_62_329" x1="83" y1="97" x2="100" y2="97" gradientUnits="userSpaceOnUse">%0A<stop stop-color="%23FF4000"/>%0A<stop offset="1" stop-color="%238754C9"/>%0A</linearGradient>%0A<linearGradient id="paint3_linear_62_329" x1="23" y1="20" x2="23" y2="37" gradientUnits="userSpaceOnUse">%0A<stop stop-color="%238754C9"/>%0A<stop offset="1" stop-color="%23FF4000"/>%0A</linearGradient>%0A<radialGradient id="paint4_radial_62_329" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(26 94) rotate(-90) scale(58)">%0A<stop offset="0.723929" stop-color="%23FFF700"/>%0A<stop offset="1" stop-color="%23FF9901"/>%0A</radialGradient>%0A<linearGradient id="paint5_linear_62_329" x1="68" y1="97" x2="84" y2="97" gradientUnits="userSpaceOnUse">%0A<stop stop-color="%23FFF700"/>%0A<stop offset="1" stop-color="%23FF9901"/>%0A</linearGradient>%0A<linearGradient id="paint6_linear_62_329" x1="23" y1="52" x2="23" y2="36" gradientUnits="userSpaceOnUse">%0A<stop stop-color="%23FFF700"/>%0A<stop offset="1" stop-color="%23FF9901"/>%0A</linearGradient>%0A<radialGradient id="paint7_radial_62_329" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(26 94) rotate(-90) scale(42)">%0A<stop offset="0.59513" stop-color="%2300AAFF"/>%0A<stop offset="1" stop-color="%2301DA40"/>%0A</radialGradient>%0A<radialGradient id="paint8_radial_62_329" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(51 97) scale(17 45.3333)">%0A<stop stop-color="%2300AAFF"/>%0A<stop offset="1" stop-color="%2301DA40"/>%0A</radialGradient>%0A<radialGradient id="paint9_radial_62_329" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(23 69) rotate(-90) scale(17 322.37)">%0A<stop stop-color="%2300AAFF"/>%0A<stop offset="1" stop-color="%2301DA40"/>%0A</radialGradient>%0A</defs>%0A</svg>%0A';export{a as default};
|
@ -1 +0,0 @@
|
||||
var a='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><g clip-path="url(%23a)"><path fill="url(%23b)" d="M0 16c0-5.6 0-8.4 1.09-10.54a10 10 0 0 1 4.37-4.37C7.6 0 10.4 0 16 0h16c5.6 0 8.4 0 10.54 1.09a10 10 0 0 1 4.37 4.37C48 7.6 48 10.4 48 16v16c0 5.6 0 8.4-1.09 10.54a10.001 10.001 0 0 1-4.37 4.37C40.4 48 37.6 48 32 48H16c-5.6 0-8.4 0-10.54-1.09a10 10 0 0 1-4.37-4.37C0 40.4 0 37.6 0 32V16Z"/><path fill="%23000" fill-opacity=".08" fill-rule="evenodd" d="M1.133 9.513C1 11.131 1 13.183 1 16v16c0 2.817 0 4.87.133 6.486.131 1.606.387 2.695.848 3.6a9 9 0 0 0 3.933 3.933c.905.461 1.994.717 3.6.848C11.13 47 13.183 47 16 47h16c2.817 0 4.87 0 6.486-.133 1.606-.131 2.695-.387 3.6-.848a9 9 0 0 0 3.933-3.933c.461-.905.717-1.994.848-3.6C47 36.87 47 34.816 47 32V16c0-2.817 0-4.87-.133-6.487-.131-1.605-.387-2.694-.848-3.599a9 9 0 0 0-3.933-3.933c-.905-.461-1.994-.717-3.6-.848C36.87 1 34.816 1 32 1H16c-2.817 0-4.87 0-6.487.133-1.605.131-2.694.387-3.599.848a9 9 0 0 0-3.933 3.933c-.461.905-.717 1.994-.848 3.6ZM1.09 5.46C0 7.6 0 10.4 0 16v16c0 5.6 0 8.4 1.09 10.54a10 10 0 0 0 4.37 4.37C7.6 48 10.4 48 16 48h16c5.6 0 8.4 0 10.54-1.09a10.001 10.001 0 0 0 4.37-4.37C48 40.4 48 37.6 48 32V16c0-5.6 0-8.4-1.09-10.54a10 10 0 0 0-4.37-4.37C40.4 0 37.6 0 32 0H16C10.4 0 7.6 0 5.46 1.09a10 10 0 0 0-4.37 4.37Z" clip-rule="evenodd"/><path fill="%23000" fill-opacity=".12" d="M36.345 13.155a1.5 1.5 0 1 0-3 0v2.224c0 .627-.775.937-1.218.494a12.75 12.75 0 1 0 0 18.031 1.5 1.5 0 1 0-2.121-2.12 9.75 9.75 0 1 1 0-13.79c.61.61.172 1.616-.691 1.616H26.89a1.5 1.5 0 0 0 0 3h7.955a1.5 1.5 0 0 0 1.5-1.5v-7.955Z"/><path fill="%23fff" d="M36.345 12.155a1.5 1.5 0 1 0-3 0v2.224c0 .627-.775.937-1.218.494a12.75 12.75 0 1 0 0 18.031 1.5 1.5 0 1 0-2.121-2.12 9.75 9.75 0 1 1 0-13.79c.61.61.172 1.616-.691 1.616H26.89a1.5 1.5 0 0 0 0 3h7.955a1.5 1.5 0 0 0 1.5-1.5v-7.955Z"/></g><defs><linearGradient id="b" x1="24" x2="24" y1="0" y2="48" gradientUnits="userSpaceOnUse"><stop stop-color="%2359627A"/><stop offset="1" stop-color="%234A5266"/></linearGradient><clipPath id="a"><path fill="%23fff" d="M0 0h48v48H0z"/></clipPath></defs></svg>';export{a as default};
|
@ -1 +0,0 @@
|
||||
var a='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 28 28"><g clip-path="url(%23a)"><mask id="b" width="28" height="28" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="%23FDFDFF" d="M14 28c7.732 0 14-6.268 14-14S21.732 0 14 0 0 6.268 0 14s6.268 14 14 14Z"/></mask><g mask="url(%23b)"><path fill="%23FDFDFF" d="M6 3h16v21H6z"/><path fill="%231273EA" d="M0 14v14h28V0H0v14Zm19.468-8.543c.307.072.537.201.747.42.103.108.186.23.253.37.06.126.089.212.115.343l.02.1v11.805l-.02.094c-.062.28-.193.529-.383.722-.05.05-.302.26-.672.556l-1.038.833-.754.605-2.038 1.636c-.569.458-1.06.85-1.093.872-.34.23-.782.246-1.14.04a5.804 5.804 0 0 1-.388-.294L9.94 21.04l-1.199-.962c-.651-.523-.895-.724-.954-.786a1.445 1.445 0 0 1-.369-.739c-.017-.101-.017-.19-.015-6.005l.002-5.902.026-.103a1.445 1.445 0 0 1 1.224-1.105c.017-.002 2.44-.003 5.38-.003l5.35.002.083.02Z"/><path fill="%231273EA" d="M10.873 7.58A1.43 1.43 0 0 0 9.586 8.65c-.045.181-.044-.05-.044 4.489 0 2.905.003 4.25.01 4.302.043.348.188.642.43.875.053.05 2.205 1.784 2.318 1.867.025.02.04.023.095.023.056 0 .07-.004.101-.024a.18.18 0 0 0 .056-.063l.019-.04v-2.957c0-1.89.004-2.968.009-2.988a.186.186 0 0 1 .09-.113l.042-.022h.874c.958 0 .948 0 1.112.055.322.107.572.36.68.684.054.16.051-.007.051 2.765 0 1.81.003 2.552.01 2.577.021.08.089.13.172.13a.175.175 0 0 0 .072-.015c.027-.014 2.194-1.75 2.308-1.848.224-.195.39-.494.45-.809.02-.1.02-.108.023-1.442.004-1.392 0-1.52-.033-1.682a1.375 1.375 0 0 0-.39-.707 1.374 1.374 0 0 0-.847-.41l-.094-.012.063-.007c.18-.02.362-.071.503-.142.397-.2.669-.552.767-.996l.02-.09v-1.61c0-1.533 0-1.613-.017-1.698a1.404 1.404 0 0 0-.426-.778 1.4 1.4 0 0 0-.853-.382c-.093-.008-6.186-.011-6.284-.003Zm4.464 1.445c.021.012.05.037.063.057l.025.036.002 1.233c.002 1.103.001 1.242-.011 1.322-.072.432-.39.77-.83.878-.074.018-.097.018-.967.021-.996.004-.937.007-.998-.06a.213.213 0 0 1-.04-.069c-.014-.048-.014-3.265 0-3.313a.207.207 0 0 1 .11-.118c.031-.01.23-.012 1.322-.01l1.286.001.038.022Z"/></g></g><defs><clipPath id="a"><path fill="%23fff" d="M0 0h28v28H0z"/></clipPath></defs></svg>';export{a as default};
|
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
||||
var a='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path fill="url(%23a)" d="M0 16c0-5.6 0-8.4 1.09-10.54a10 10 0 0 1 4.37-4.37C7.6 0 10.4 0 16 0h16c5.6 0 8.4 0 10.54 1.09a10 10 0 0 1 4.37 4.37C48 7.6 48 10.4 48 16v16c0 5.6 0 8.4-1.09 10.54a10.001 10.001 0 0 1-4.37 4.37C40.4 48 37.6 48 32 48H16c-5.6 0-8.4 0-10.54-1.09a10 10 0 0 1-4.37-4.37C0 40.4 0 37.6 0 32V16Z"/><path fill="%23000" fill-opacity=".08" fill-rule="evenodd" d="M1.133 9.513C1 11.131 1 13.183 1 16v16c0 2.817 0 4.87.133 6.486.131 1.606.387 2.695.848 3.6a9 9 0 0 0 3.933 3.933c.905.461 1.994.717 3.6.848C11.13 47 13.183 47 16 47h16c2.817 0 4.87 0 6.486-.133 1.606-.131 2.695-.387 3.6-.848a9 9 0 0 0 3.933-3.933c.461-.905.717-1.994.848-3.6C47 36.87 47 34.816 47 32V16c0-2.817 0-4.87-.133-6.487-.131-1.605-.387-2.694-.848-3.599a9 9 0 0 0-3.933-3.933c-.905-.461-1.994-.717-3.6-.848C36.87 1 34.816 1 32 1H16c-2.817 0-4.87 0-6.487.133-1.605.131-2.694.387-3.599.848a9 9 0 0 0-3.933 3.933c-.461.905-.717 1.994-.848 3.6ZM1.09 5.46C0 7.6 0 10.4 0 16v16c0 5.6 0 8.4 1.09 10.54a10 10 0 0 0 4.37 4.37C7.6 48 10.4 48 16 48h16c5.6 0 8.4 0 10.54-1.09a10.001 10.001 0 0 0 4.37-4.37C48 40.4 48 37.6 48 32V16c0-5.6 0-8.4-1.09-10.54a10 10 0 0 0-4.37-4.37C40.4 0 37.6 0 32 0H16C10.4 0 7.6 0 5.46 1.09a10 10 0 0 0-4.37 4.37Z" clip-rule="evenodd"/><path fill="%23000" fill-opacity=".12" d="M28 10.5A1.5 1.5 0 0 1 29.5 9h3.955c.506 0 .76 0 .973.015a6 6 0 0 1 5.557 5.557c.015.214.015.467.015.974V19.5a1.5 1.5 0 0 1-3 0v-4.125c0-.348 0-.522-.014-.67a3 3 0 0 0-2.692-2.69c-.147-.015-.32-.015-.669-.015H29.5a1.5 1.5 0 0 1-1.5-1.5Zm0 29a1.5 1.5 0 0 0 1.5 1.5h3.955c.506 0 .76 0 .973-.015a6 6 0 0 0 5.557-5.557c.015-.214.015-.467.015-.973V30.5a1.5 1.5 0 0 0-3 0v4.125c0 .348 0 .522-.014.67a3 3 0 0 1-2.692 2.69c-.147.015-.32.015-.669.015H29.5a1.5 1.5 0 0 0-1.5 1.5ZM18.5 41a1.5 1.5 0 0 0 0-3h-4.125c-.348 0-.522 0-.67-.014a3 3 0 0 1-2.69-2.692c-.015-.147-.015-.32-.015-.669V30.5a1.5 1.5 0 0 0-3 0v3.955c0 .506 0 .76.015.973a6 6 0 0 0 5.557 5.557c.214.015.467.015.974.015H18.5ZM20 10.5A1.5 1.5 0 0 0 18.5 9h-3.954c-.507 0-.76 0-.974.015a6 6 0 0 0-5.557 5.557C8 14.786 8 15.039 8 15.546V19.5a1.5 1.5 0 0 0 3 0v-4.125c0-.348 0-.522.014-.67a3 3 0 0 1 2.692-2.69c.147-.015.32-.015.669-.015H18.5a1.5 1.5 0 0 0 1.5-1.5Z"/><path fill="%23fff" d="M28 9.5A1.5 1.5 0 0 1 29.5 8h3.955c.506 0 .76 0 .973.015a6 6 0 0 1 5.557 5.557c.015.214.015.467.015.974V18.5a1.5 1.5 0 0 1-3 0v-4.125c0-.348 0-.522-.014-.67a3 3 0 0 0-2.692-2.69c-.147-.015-.32-.015-.669-.015H29.5A1.5 1.5 0 0 1 28 9.5Zm0 29a1.5 1.5 0 0 0 1.5 1.5h3.955c.506 0 .76 0 .973-.015a6 6 0 0 0 5.557-5.557c.015-.214.015-.467.015-.973V29.5a1.5 1.5 0 0 0-3 0v4.125c0 .348 0 .522-.014.67a3 3 0 0 1-2.692 2.69c-.147.015-.32.015-.669.015H29.5a1.5 1.5 0 0 0-1.5 1.5ZM18.5 40a1.5 1.5 0 0 0 0-3h-4.125c-.348 0-.522 0-.67-.014a3 3 0 0 1-2.69-2.692c-.015-.147-.015-.32-.015-.669V29.5a1.5 1.5 0 0 0-3 0v3.955c0 .506 0 .76.015.973a6 6 0 0 0 5.557 5.557c.214.015.467.015.974.015H18.5ZM20 9.5A1.5 1.5 0 0 0 18.5 8h-3.954c-.507 0-.76 0-.974.015a6 6 0 0 0-5.557 5.557C8 13.786 8 14.039 8 14.546V18.5a1.5 1.5 0 0 0 3 0v-4.125c0-.348 0-.522.014-.67a3 3 0 0 1 2.692-2.69c.147-.015.32-.015.669-.015H18.5A1.5 1.5 0 0 0 20 9.5Z"/><defs><linearGradient id="a" x1="24" x2="24" y1="0" y2="48" gradientUnits="userSpaceOnUse"><stop stop-color="%2359627A"/><stop offset="1" stop-color="%234A5266"/></linearGradient></defs></svg>';export{a as default};
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
||||
var C='data:image/svg+xml,<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">%0A<rect width="28" height="28" fill="%233B99FC"/>%0A<path d="M8.38969 10.3739C11.4882 7.27538 16.5118 7.27538 19.6103 10.3739L19.9832 10.7468C20.1382 10.9017 20.1382 11.1529 19.9832 11.3078L18.7076 12.5835C18.6301 12.6609 18.5045 12.6609 18.4271 12.5835L17.9139 12.0703C15.7523 9.9087 12.2477 9.9087 10.0861 12.0703L9.53655 12.6198C9.45909 12.6973 9.3335 12.6973 9.25604 12.6198L7.98039 11.3442C7.82547 11.1893 7.82547 10.9381 7.98039 10.7832L8.38969 10.3739ZM22.2485 13.012L23.3838 14.1474C23.5387 14.3023 23.5387 14.5535 23.3838 14.7084L18.2645 19.8277C18.1096 19.9827 17.8584 19.9827 17.7035 19.8277C17.7035 19.8277 17.7035 19.8277 17.7035 19.8277L14.0702 16.1944C14.0314 16.1557 13.9686 16.1557 13.9299 16.1944C13.9299 16.1944 13.9299 16.1944 13.9299 16.1944L10.2966 19.8277C10.1417 19.9827 9.89053 19.9827 9.73561 19.8278C9.7356 19.8278 9.7356 19.8277 9.7356 19.8277L4.61619 14.7083C4.46127 14.5534 4.46127 14.3022 4.61619 14.1473L5.75152 13.012C5.90645 12.857 6.15763 12.857 6.31255 13.012L9.94595 16.6454C9.98468 16.6841 10.0475 16.6841 10.0862 16.6454C10.0862 16.6454 10.0862 16.6454 10.0862 16.6454L13.7194 13.012C13.8743 12.857 14.1255 12.857 14.2805 13.012C14.2805 13.012 14.2805 13.012 14.2805 13.012L17.9139 16.6454C17.9526 16.6841 18.0154 16.6841 18.0541 16.6454L21.6874 13.012C21.8424 12.8571 22.0936 12.8571 22.2485 13.012Z" fill="white"/>%0A</svg>%0A';export{C as default};
|
@ -1 +0,0 @@
|
||||
var l='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 28 28"><g clip-path="url(%23a)"><path fill="%23B7B5B1" d="M8 8h12v12H8z"/><path fill="%23B7B5B1" d="M28 12.667C24.786-5.97.448-2.363.011 12.667c1.4.728 2.285 1.176 2.285 1.176s-.74.448-2.296 1.434c2.8 18.278 26.723 15.624 28-.023-1.523-.93-2.352-1.422-2.352-1.422s.717-.336 2.352-1.165Zm-11.973 6.507-2.285-3.92-2.318 3.92-1.758-.123 3.304-5.566L9.99 8.68l1.792-.157 2.117 3.562 2.117-3.405 1.669.045-2.778 4.726 3.058 5.69-1.96.045.022-.012Z"/><path fill="%23244B81" d="M26.869 11.94C22.512-4.627 2.52-.147 1.154 11.94a249.514 249.514 0 0 1 3.404 1.926l-3.416 2.172c2.98 15.927 24.54 12.858 25.727-.022-2.173-1.366-3.461-2.162-3.461-2.162s2.934-1.635 3.46-1.915Zm-10.842 7.246-2.285-3.92-2.318 3.92-1.747-.124 3.304-5.566L10 8.691l1.793-.157 2.116 3.562 2.117-3.405 1.669.045-2.766 4.726 3.057 5.69-1.96.045v-.011Z"/></g><defs><clipPath id="a"><path fill="%23fff" d="M0 0h28v28H0z"/></clipPath></defs></svg>';export{l as default};
|
@ -1 +0,0 @@
|
||||
var l='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="none" viewBox="0 0 178 178"><circle cx="89" cy="89" r="89" fill="%23005741"/><path fill="%23fff" d="M112.109 108.673v12.02H62.523c.684-7.911 3.236-13.477 12.064-21.304l37.522-32.01v28.09h13.507V43.79H48.813v25.76H62.32V57.297h40.803L65.784 89.163l-.089.085c-15.648 13.854-16.892 25.036-16.892 38.211v6.751h76.818v-25.527h-13.507z"/></svg>';export{l as default};
|
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
||||
var l='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 28 28"><g clip-path="url(%23a)"><path fill="%23F9F7EC" d="M14 28c7.732 0 14-6.268 14-14S21.732 0 14 0 0 6.268 0 14s6.268 14 14 14Z"/><path fill="%23000" fill-rule="evenodd" d="m22.12 13.93-4.669-4.648v3.402l-4.634 3.409h4.634v2.485l4.669-4.648ZM5.67 13.93l4.669 4.648v-3.381l4.634-3.437h-4.634V9.275L5.67 13.93Z" clip-rule="evenodd"/></g><defs><clipPath id="a"><path fill="%23fff" d="M0 0h28v28H0z"/></clipPath></defs></svg>';export{l as default};
|
@ -1 +0,0 @@
|
||||
var o='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="none"><g clip-path="url(%23a)"><path fill="url(%23b)" d="M.943 13.754c0 7.586 5.944 13.755 13.252 13.755 7.308 0 13.252-6.17 13.252-13.755C27.44 6.17 21.497 0 14.195 0 6.887 0 .943 6.17.943 13.754Z"/><path fill="url(%23c)" d="M.943 13.754c0 7.586 5.944 13.755 13.252 13.755 7.308 0 13.252-6.17 13.252-13.755C27.44 6.17 21.497 0 14.195 0 6.887 0 .943 6.17.943 13.754Z"/><path fill="url(%23d)" d="M.943 13.754c0 7.586 5.944 13.755 13.252 13.755 7.308 0 13.252-6.17 13.252-13.755C27.44 6.17 21.497 0 14.195 0 6.887 0 .943 6.17.943 13.754Z"/></g><defs><radialGradient id="b" cx="0" cy="0" r="1" gradientTransform="matrix(19.9547 0 0 20.7113 18.16 6.7)" gradientUnits="userSpaceOnUse"><stop offset=".005" stop-color="%23fff"/><stop offset=".458" stop-color="%23B7D8C8"/><stop offset=".656" stop-color="%236D9487"/><stop offset="1" stop-color="%234B4C3C"/></radialGradient><radialGradient id="c" cx="0" cy="0" r="1" gradientTransform="matrix(19.9547 0 0 20.7113 18.16 6.7)" gradientUnits="userSpaceOnUse"><stop offset=".005" stop-color="%23fff"/><stop offset=".458" stop-color="%23B5B4C6"/><stop offset=".656" stop-color="%239B8F8F"/><stop offset="1" stop-color="%234B4C3C"/></radialGradient><radialGradient id="d" cx="0" cy="0" r="1" gradientTransform="matrix(19.9547 0 0 20.7113 18.16 6.7)" gradientUnits="userSpaceOnUse"><stop offset=".156" stop-color="%23DCC8D0"/><stop offset=".302" stop-color="%2378C8CF"/><stop offset=".427" stop-color="%234D959E"/><stop offset=".557" stop-color="%23305EB9"/><stop offset=".797" stop-color="%23311F12"/><stop offset=".906" stop-color="%23684232"/><stop offset="1" stop-color="%232D1C13"/></radialGradient><clipPath id="a"><path fill="%23fff" d="M0 0h28v28H0z"/></clipPath></defs></svg>';export{o as default};
|
@ -1,25 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<!-- This sets window.our.node -->
|
||||
<script src="/our.js"></script>
|
||||
|
||||
<title>Package Store</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="pragma" content="no-cache" />
|
||||
<meta http-equiv="cache-control" content="no-cache" />
|
||||
<link rel="icon"
|
||||
href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg==">
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
|
||||
<script type="module" crossorigin src="/main:app_store:sys/assets/index-9L6Bkx0q.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-bN28jcF1.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,18 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
@ -1 +1 @@
|
||||
npm run build:copy && cd ~/kinode && cargo +nightly build -p kinode && cd kinode/packages/app_store/ui
|
||||
npm install && npm run build:copy
|
@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "Chat Template",
|
||||
"subtitle": "The chat template from kit",
|
||||
"description": "The kit chat template is the default app when starting a new kit project. This app is the basic version of that, packaged for the app store.",
|
||||
"image": "https://st4.depositphotos.com/7662228/30134/v/450/depositphotos_301343880-stock-illustration-best-chat-speech-bubble-icon.jpg",
|
||||
"version": "0.1.2",
|
||||
"license": "MIT",
|
||||
"website": "https://kinode.org",
|
||||
"screenshots": [
|
||||
"https://pongo-uploads.s3.us-east-2.amazonaws.com/Screenshot+2024-01-30+at+10.01.46+PM.png",
|
||||
"https://pongo-uploads.s3.us-east-2.amazonaws.com/Screenshot+2024-01-30+at+10.01.52+PM.png"
|
||||
],
|
||||
"mirrors": [
|
||||
"odinsbadeye.os"
|
||||
],
|
||||
"versions": [
|
||||
"a2c584bf63a730efdc79ec0a3c93bc97eba4e8745c633e3abe090b4f7e270e92",
|
||||
"c13f7ae39fa7f652164cfc1db305cd864cc1dc5f33827a2d74f7dde70ef36662",
|
||||
"09d24205d8e1f3634448e881db200b88ad691bbdaabbccb885b225147ba4a93e",
|
||||
"733be24324802a35944a73f355595f781de65d9d6e393bdabe879edcb77dfb62"
|
||||
]
|
||||
}
|
@ -5,10 +5,11 @@
|
||||
<!-- This sets window.our.node -->
|
||||
<script src="/our.js"></script>
|
||||
|
||||
<title>Package Store</title>
|
||||
<title>App Store</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="pragma" content="no-cache" />
|
||||
<meta http-equiv="cache-control" content="no-cache" />
|
||||
<link rel="stylesheet" href="/kinode.css">
|
||||
<link rel="icon"
|
||||
href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg==">
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
1314
kinode/packages/app_store/ui/package-lock.json
generated
1314
kinode/packages/app_store/ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,10 +8,9 @@
|
||||
"start": "vite --port 3000",
|
||||
"build": "tsc && vite build",
|
||||
"copy": "mkdir -p ../pkg/ui && rm -rf ../pkg/ui/* && cp -r dist/* ../pkg/ui/",
|
||||
"build:copy": "npm run tc && npm run build && npm run copy",
|
||||
"build:copy": "npm run build && npm run copy",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview",
|
||||
"tc": "typechain --out-dir src/abis/types/ \"./src/abis/**/*.json\""
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kinode/client-api": "^0.1.0",
|
||||
@ -19,15 +18,12 @@
|
||||
"@rainbow-me/rainbowkit": "^2.1.2",
|
||||
"@szhsin/react-menu": "^4.1.0",
|
||||
"@tanstack/react-query": "^5.45.1",
|
||||
"classnames": "^2.5.1",
|
||||
"idna-uts46-hx": "^6.0.4",
|
||||
"js-sha3": "^0.9.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^5.0.1",
|
||||
"react-router-dom": "^6.21.3",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"unocss": "^0.59.0-beta.1",
|
||||
"viem": "^2.15.1",
|
||||
"wagmi": "^2.10.3",
|
||||
"zustand": "^4.4.7"
|
||||
|
@ -1,4 +0,0 @@
|
||||
<svg width="779" height="514" viewBox="0 0 779 514" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M753.092 5.91932C756.557 5.09976 755.962 -0.00012207 752.401 -0.00012207H426.001C424.755 -0.00012207 423.639 0.77027 423.197 1.93535L236.968 492.6C235.729 495.865 240.123 498.255 242.191 495.441L569.357 50.1132C569.778 49.5392 570.391 49.1339 571.084 48.97L753.092 5.91932Z" fill="#FFF5D9"/>
|
||||
<path d="M11.9665 40.2288C9.10949 38.777 10.2135 34.4583 13.4167 34.5557L404.273 46.4367C406.334 46.4993 407.719 48.5749 406.986 50.5023L347.438 206.981C346.804 208.647 344.865 209.396 343.275 208.588L11.9665 40.2288Z" fill="#FFF5D9"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 644 B |
@ -1,45 +1,23 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
||||
|
||||
import StorePage from "./pages/StorePage";
|
||||
import MyAppsPage from "./pages/MyAppsPage";
|
||||
import AppPage from "./pages/AppPage";
|
||||
import { APP_DETAILS_PATH, MY_APPS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
|
||||
import { APP_DETAILS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
|
||||
import PublishPage from "./pages/PublishPage";
|
||||
|
||||
import Header from "./components/Header";
|
||||
|
||||
const BASE_URL = import.meta.env.BASE_URL;
|
||||
if (window.our) window.our.process = BASE_URL?.replace("/", "");
|
||||
|
||||
const PROXY_TARGET = `${import.meta.env.VITE_NODE_URL || "http://localhost:8080"
|
||||
}${BASE_URL}`;
|
||||
|
||||
// This env also has BASE_URL which should match the process + package name
|
||||
const WEBSOCKET_URL = import.meta.env.DEV // eslint-disable-line
|
||||
? `${PROXY_TARGET.replace("http", "ws")}`
|
||||
: undefined;
|
||||
|
||||
function App() {
|
||||
const [nodeConnected, setNodeConnected] = useState(true); // eslint-disable-line
|
||||
|
||||
if (!nodeConnected) {
|
||||
return (
|
||||
<div className="flex flex-col c">
|
||||
<h2 style={{ color: "red" }}>Node not connected</h2>
|
||||
<h4>
|
||||
You need to start a node at {PROXY_TARGET} before you can use this UI
|
||||
in development.
|
||||
</h4>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col c h-screen w-screen max-h-screen max-w-screen overflow-x-hidden special-appstore-background">
|
||||
<div>
|
||||
<Router basename={BASE_URL}>
|
||||
<Header />
|
||||
<Routes>
|
||||
<Route path={STORE_PATH} element={<StorePage />} />
|
||||
<Route path={MY_APPS_PATH} element={<MyAppsPage />} />
|
||||
<Route path={`${APP_DETAILS_PATH}/:id`} element={<AppPage />} />
|
||||
<Route path={PUBLISH_PATH} element={<PublishPage />} />
|
||||
</Routes>
|
||||
|
@ -39,7 +39,7 @@ export function encodeIntoMintCall(multicalls: `0x${string}`, our_address: `0x${
|
||||
functionName: 'execute',
|
||||
args: [
|
||||
MULTICALL,
|
||||
BigInt(0), // value
|
||||
BigInt(0),
|
||||
multicalls,
|
||||
1
|
||||
]
|
||||
@ -52,7 +52,7 @@ export function encodeIntoMintCall(multicalls: `0x${string}`, our_address: `0x${
|
||||
our_address,
|
||||
encodePacked(["bytes"], [stringToHex(app_name)]),
|
||||
initCall,
|
||||
"0x", // erc721 details? <- encode app_store here? actually might be a slick way to do it.
|
||||
"0x",
|
||||
KINO_ACCOUNT_IMPL,
|
||||
]
|
||||
})
|
||||
|
@ -2,10 +2,9 @@ import { parseAbi } from "viem";
|
||||
|
||||
export { encodeMulticalls, encodeIntoMintCall } from "./helpers";
|
||||
|
||||
// move to constants? // also for anvil/optimism
|
||||
export const KINOMAP: `0x${string}` = "0x0165878A594ca255338adfa4d48449f69242Eb8F";
|
||||
export const MULTICALL: `0x${string}` = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
||||
export const KINO_ACCOUNT_IMPL: `0x${string}` = "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9";
|
||||
export const KINO_ACCOUNT_IMPL: `0x${string}` = "0x58790D9957ECE58607A4b58308BBD5FE1a2e4789";
|
||||
|
||||
|
||||
export const multicallAbi = parseAbi([
|
||||
|
@ -1,10 +0,0 @@
|
||||
<svg width="122" height="81" viewBox="0 0 122 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_6_651)">
|
||||
<path d="M89.3665 8.06803L121.5 0.35155L66.5111 0.320312L63.7089 7.69502L0.5 5.7032L54.0253 32.9925L36.1529 80.3203L89.3665 8.06803Z" fill="#FFF5D9"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_6_651">
|
||||
<rect width="121" height="80" fill="white" transform="translate(0.5 0.320312)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 431 B |
@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#FFF5D9" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M6 9l6 6 6-6"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 188 B |
@ -1,65 +0,0 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { AppInfo } from "../types/Apps";
|
||||
import UpdateButton from "./UpdateButton";
|
||||
import DownloadButton from "./DownloadButton";
|
||||
import InstallButton from "./InstallButton";
|
||||
import LaunchButton from "./LaunchButton";
|
||||
import { FaCheck } from "react-icons/fa6";
|
||||
import classNames from "classnames";
|
||||
|
||||
interface ActionButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
app: AppInfo;
|
||||
isIcon?: boolean;
|
||||
permitMultiButton?: boolean;
|
||||
launchPath?: string
|
||||
}
|
||||
|
||||
export default function ActionButton({ app, launchPath = '', isIcon = false, permitMultiButton = false, ...props }: ActionButtonProps) {
|
||||
const { installed, downloaded, updatable } = useMemo(() => {
|
||||
const versions = Object.entries(app?.metadata?.properties?.code_hashes || {});
|
||||
const latestHash = (versions.find(([v]) => v === app.metadata?.properties?.current_version) || [])[1];
|
||||
|
||||
const installed = app.installed;
|
||||
const downloaded = Boolean(app.state);
|
||||
|
||||
const updatable =
|
||||
Boolean(app.state?.our_version && latestHash) &&
|
||||
app.state?.our_version !== latestHash &&
|
||||
app.publisher !== (window as any).our.node;
|
||||
return {
|
||||
installed,
|
||||
downloaded,
|
||||
updatable,
|
||||
};
|
||||
}, [app]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* if it's got a UI and it's updatable, show both buttons if we have space (launch will otherwise push out update) */}
|
||||
{permitMultiButton && installed && updatable && launchPath && <UpdateButton app={app} {...props} isIcon={isIcon} />}
|
||||
{(installed && launchPath)
|
||||
? <LaunchButton app={app} {...props} isIcon={isIcon} launchPath={launchPath} />
|
||||
: (installed && updatable)
|
||||
? <UpdateButton app={app} {...props} isIcon={isIcon} />
|
||||
: !downloaded
|
||||
? <DownloadButton app={app} {...props} isIcon={isIcon} />
|
||||
: !installed
|
||||
? <InstallButton app={app} {...props} isIcon={isIcon} />
|
||||
: isIcon
|
||||
? <button
|
||||
className="pointer-events none icon clear absolute top-0 right-0"
|
||||
>
|
||||
<FaCheck />
|
||||
</button>
|
||||
: <></>
|
||||
// <button
|
||||
// onClick={() => { }}
|
||||
// {...props as any}
|
||||
// className={classNames("clear pointer-events-none", props.className)}
|
||||
// >
|
||||
// Installed
|
||||
// </button>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import AppHeader from "./AppHeader";
|
||||
import ActionButton from "./ActionButton";
|
||||
import { AppInfo } from "../types/Apps";
|
||||
import { appId } from "../utils/app";
|
||||
import { isMobileCheck } from "../utils/dimensions";
|
||||
import classNames from "classnames";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { APP_DETAILS_PATH } from "../constants/path";
|
||||
import MoreActions from "./MoreActions";
|
||||
|
||||
interface AppEntryProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
app: AppInfo;
|
||||
size?: "small" | "medium" | "large";
|
||||
overrideImageSize?: "small" | "medium" | "large";
|
||||
showMoreActions?: boolean;
|
||||
launchPath?: string;
|
||||
}
|
||||
|
||||
export default function AppEntry({ app, size = "medium", overrideImageSize, showMoreActions, launchPath, ...props }: AppEntryProps) {
|
||||
const isMobile = isMobileCheck()
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
key={appId(app)}
|
||||
className={classNames("flex justify-between rounded-lg hover:bg-white/10 card cursor-pointer", props.className, {
|
||||
'flex-wrap gap-2': isMobile,
|
||||
'flex-col relative': size !== 'large'
|
||||
})}
|
||||
onClick={() => {
|
||||
if (!showMoreActions) {
|
||||
navigate(`/${APP_DETAILS_PATH}/${appId(app)}`)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AppHeader app={app} size={size} overrideImageSize={overrideImageSize} />
|
||||
<div className={classNames("flex", {
|
||||
'items-center': size !== 'large',
|
||||
'items-start': size === 'large',
|
||||
'absolute': size !== 'large',
|
||||
'top-2 right-2': size !== 'large' && showMoreActions,
|
||||
'top-0 right-0': size !== 'large' && !showMoreActions,
|
||||
'ml-auto': size === 'large' && isMobile,
|
||||
})}>
|
||||
<ActionButton
|
||||
app={app}
|
||||
launchPath={launchPath}
|
||||
isIcon={!showMoreActions && size !== 'large'}
|
||||
className={classNames({
|
||||
'bg-orange text-lg': size === 'large',
|
||||
'mr-2': showMoreActions,
|
||||
'w-full': size === 'large'
|
||||
})}
|
||||
/>
|
||||
{showMoreActions && <MoreActions
|
||||
app={app}
|
||||
className={classNames("self-stretch", {
|
||||
'self-start': size === 'large',
|
||||
|
||||
})}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
import React from "react";
|
||||
import { AppInfo } from "../types/Apps";
|
||||
import { appId } from "../utils/app";
|
||||
import classNames from "classnames";
|
||||
import ColorDot from "./ColorDot";
|
||||
import { isMobileCheck } from "../utils/dimensions";
|
||||
import AppIconPlaceholder from './AppIconPlaceholder'
|
||||
|
||||
interface AppHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
app: AppInfo;
|
||||
size?: "small" | "medium" | "large";
|
||||
overrideImageSize?: "small" | "medium" | "large"
|
||||
}
|
||||
|
||||
export default function AppHeader({
|
||||
app,
|
||||
size = "medium",
|
||||
overrideImageSize,
|
||||
...props
|
||||
}: AppHeaderProps) {
|
||||
const isMobile = isMobileCheck()
|
||||
|
||||
const appName = <div
|
||||
className={classNames({
|
||||
'text-3xl font-[OpenSans]': !isMobile && size === 'large',
|
||||
'text-xl': !isMobile && size !== 'large',
|
||||
'text-lg': isMobile
|
||||
})}
|
||||
>
|
||||
{app.metadata?.name || appId(app)}
|
||||
</div>
|
||||
|
||||
const imageSize = overrideImageSize || size
|
||||
|
||||
return <div
|
||||
{...props}
|
||||
className={classNames('flex w-full justify-content-start', size, props.className, {
|
||||
'flex-col': size === 'small',
|
||||
'gap-2': isMobile,
|
||||
'gap-4': !isMobile,
|
||||
'gap-6': !isMobile && size === 'large'
|
||||
})}
|
||||
>
|
||||
{size === 'small' && appName}
|
||||
{app.metadata?.image
|
||||
? <img
|
||||
src={app.metadata.image}
|
||||
alt="app icon"
|
||||
className={classNames('object-cover', {
|
||||
'rounded': !imageSize,
|
||||
'rounded-md': imageSize === 'small',
|
||||
'rounded-lg': imageSize === 'medium',
|
||||
'rounded-2xl': imageSize === 'large',
|
||||
'h-32': imageSize === 'large' || imageSize === 'small',
|
||||
'h-20': imageSize === 'medium',
|
||||
})}
|
||||
/>
|
||||
: <AppIconPlaceholder
|
||||
text={app.metadata_hash || app.state?.our_version?.toString() || ''}
|
||||
size={imageSize}
|
||||
/>}
|
||||
<div className={classNames("flex flex-col", {
|
||||
'gap-2': isMobile,
|
||||
'gap-4 max-w-3/4': isMobile && size !== 'small'
|
||||
})}>
|
||||
{size !== 'small' && appName}
|
||||
{app.metadata?.description && (
|
||||
<div
|
||||
style={{
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}
|
||||
className={classNames({
|
||||
'text-2xl': size === 'large'
|
||||
})}
|
||||
>
|
||||
{app.metadata.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { isMobileCheck } from '../utils/dimensions';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const AppIconPlaceholder: React.FC<{ text: string, className?: string, size: 'small' | 'medium' | 'large' }> = ({ text, className, size }) => {
|
||||
const index = text.split('').pop()?.toUpperCase() || '0'
|
||||
const derivedFilename = `/icons/${index}`
|
||||
|
||||
if (!derivedFilename) {
|
||||
return null
|
||||
}
|
||||
|
||||
const isMobile = isMobileCheck()
|
||||
|
||||
return <img
|
||||
src={derivedFilename}
|
||||
className={classNames('m-0 align-self-center rounded-full', {
|
||||
'h-32 w-32': !isMobile && size === 'large',
|
||||
'h-18 w-18': !isMobile && size === 'medium',
|
||||
'h-12 w-12': isMobile || size === 'small',
|
||||
}, className)}
|
||||
/>
|
||||
}
|
||||
|
||||
export default AppIconPlaceholder
|
@ -1,32 +0,0 @@
|
||||
import React from "react";
|
||||
import { FaCheck } from "react-icons/fa6";
|
||||
|
||||
export default function Checkbox({
|
||||
readOnly = false,
|
||||
checked,
|
||||
setChecked,
|
||||
}: {
|
||||
readOnly?: boolean;
|
||||
checked: boolean;
|
||||
setChecked?: (checked: boolean) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="relative">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checked"
|
||||
name="checked"
|
||||
checked={checked}
|
||||
onChange={(e) => setChecked && setChecked(e.target.checked)}
|
||||
autoFocus
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
{checked && (
|
||||
<FaCheck
|
||||
className="absolute left-1 top-1 cursor-pointer"
|
||||
onClick={() => setChecked && setChecked(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
import { hexToRgb, hslToRgb, rgbToHex, rgbToHsl } from '../utils/colors'
|
||||
import { isMobileCheck } from '../utils/dimensions'
|
||||
|
||||
interface ColorDotProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||
num: string,
|
||||
dotSize?: 'small' | 'medium' | 'large'
|
||||
}
|
||||
|
||||
const ColorDot: React.FC<ColorDotProps> = ({
|
||||
num,
|
||||
dotSize,
|
||||
...props
|
||||
}) => {
|
||||
const isMobile = isMobileCheck()
|
||||
|
||||
num = num ? num : '';
|
||||
|
||||
while (num.length < 6) {
|
||||
num = '0' + num
|
||||
}
|
||||
|
||||
const leftHsl = rgbToHsl(hexToRgb(num.slice(0, 6)))
|
||||
const rightHsl = rgbToHsl(hexToRgb(num.length > 6 ? num.slice(num.length - 6) : num))
|
||||
leftHsl.s = rightHsl.s = 1
|
||||
const leftColor = rgbToHex(hslToRgb(leftHsl))
|
||||
const rightColor = rgbToHex(hslToRgb(rightHsl))
|
||||
|
||||
const angle = (parseInt(num, 16) % 360) || -45
|
||||
|
||||
return (
|
||||
<div {...props} className={classNames('flex', props.className)}>
|
||||
<div
|
||||
className={classNames('m-0 align-self-center border rounded-full outline-black', {
|
||||
'h-32 w-32': !isMobile && dotSize === 'large',
|
||||
'h-18 w-18': !isMobile && dotSize === 'medium',
|
||||
'h-12 w-12': isMobile || dotSize === 'small',
|
||||
'border-4': !isMobile,
|
||||
'border-2': isMobile,
|
||||
})}
|
||||
style={{
|
||||
borderTopColor: leftColor,
|
||||
borderRightColor: rightColor,
|
||||
borderBottomColor: rightColor,
|
||||
borderLeftColor: leftColor,
|
||||
background: `linear-gradient(${angle}deg, ${leftColor} 0 50%, ${rightColor} 50% 100%)`,
|
||||
filter: 'saturate(0.25)',
|
||||
opacity: '0.75'
|
||||
}} />
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ColorDot
|
@ -1,122 +0,0 @@
|
||||
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { AppInfo } from "../types/Apps";
|
||||
import useAppsStore from "../store/apps-store";
|
||||
import Modal from "./Modal";
|
||||
import { getAppName } from "../utils/app";
|
||||
import Loader from "./Loader";
|
||||
import classNames from "classnames";
|
||||
import { FaDownload } from "react-icons/fa6";
|
||||
|
||||
interface DownloadButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
app: AppInfo;
|
||||
isIcon?: boolean;
|
||||
}
|
||||
|
||||
export default function DownloadButton({ app, isIcon = false, ...props }: DownloadButtonProps) {
|
||||
const { downloadApp, getCaps, getMyApp, getMyApps } =
|
||||
useAppsStore();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [mirror, setMirror] = useState(app.metadata?.properties?.mirrors?.[0] || "Other");
|
||||
const [customMirror, setCustomMirror] = useState("");
|
||||
const [downloading, setDownloading] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setMirror(app.metadata?.properties?.mirrors?.[0] || "Other");
|
||||
}, [app.metadata?.properties?.mirrors]);
|
||||
|
||||
const onClick = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
setShowModal(true);
|
||||
}, [app, setShowModal, getCaps]);
|
||||
|
||||
const download = useCallback(async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const targetMirror = mirror === "Other" ? customMirror : mirror;
|
||||
|
||||
if (!targetMirror) {
|
||||
window.alert("Please select a mirror");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setDownloading(`Downloading ${getAppName(app)}...`);
|
||||
await downloadApp(app, targetMirror);
|
||||
const interval = setInterval(() => {
|
||||
getMyApp(app)
|
||||
.then(() => {
|
||||
setDownloading("");
|
||||
setShowModal(false);
|
||||
clearInterval(interval);
|
||||
getMyApps();
|
||||
})
|
||||
.catch(console.log);
|
||||
}, 2000);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
window.alert(
|
||||
`Failed to download app from ${targetMirror}, please try a different mirror.`
|
||||
);
|
||||
setDownloading("");
|
||||
}
|
||||
}, [mirror, customMirror, app, downloadApp, getMyApp]);
|
||||
|
||||
const appName = getAppName(app);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
{...props}
|
||||
type="button"
|
||||
className={classNames("text-sm self-start", props.className, {
|
||||
'icon clear': isIcon,
|
||||
'black': !isIcon,
|
||||
})}
|
||||
disabled={!!downloading}
|
||||
onClick={onClick}
|
||||
>
|
||||
{isIcon
|
||||
? <FaDownload />
|
||||
: downloading
|
||||
? 'Downloading...'
|
||||
: 'Download'}
|
||||
</button>
|
||||
<Modal show={showModal} hide={() => setShowModal(false)}>
|
||||
{downloading ? (
|
||||
<div className="flex-col-center gap-4">
|
||||
<Loader msg={downloading} />
|
||||
<div className="text-center">
|
||||
App is downloading in the background. You can safely close this window.
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<form className="flex flex-col items-center gap-2" onSubmit={download}>
|
||||
<h4>Download '{appName}'</h4>
|
||||
<h5>Select Mirror</h5>
|
||||
<select value={mirror} onChange={(e) => setMirror(e.target.value)}>
|
||||
{((app.metadata?.properties?.mirrors || []).concat(["Other"])).map((m) => (
|
||||
<option key={m} value={m}>
|
||||
{m}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{mirror === "Other" && (
|
||||
<input
|
||||
type="text"
|
||||
value={customMirror}
|
||||
onChange={(e) => setCustomMirror(e.target.value)}
|
||||
placeholder="Mirror, i.e. 'template.os'"
|
||||
className="p-1 max-w-[240px] w-full"
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
)}
|
||||
<button type="submit">
|
||||
Download
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FaEllipsisH } from 'react-icons/fa';
|
||||
import { Menu, MenuButton } from '@szhsin/react-menu';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface DropdownProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
}
|
||||
|
||||
export default function Dropdown({ ...props }: DropdownProps) {
|
||||
return (
|
||||
<Menu
|
||||
{...props}
|
||||
unmountOnClose={true}
|
||||
className={classNames("relative", props.className)}
|
||||
direction='left'
|
||||
menuButton={<MenuButton>
|
||||
<FaEllipsisH className='mb-[3px]' />
|
||||
</MenuButton>}
|
||||
>
|
||||
{props.children}
|
||||
</Menu>
|
||||
)
|
||||
}
|
24
kinode/packages/app_store/ui/src/components/Header.tsx
Normal file
24
kinode/packages/app_store/ui/src/components/Header.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { STORE_PATH, PUBLISH_PATH } from '../constants/path';
|
||||
import { ConnectButton } from '@rainbow-me/rainbowkit';
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<header className="app-header">
|
||||
<div className="header-left">
|
||||
<nav>
|
||||
<Link to={STORE_PATH} className={location.pathname === STORE_PATH ? 'active' : ''}>Home</Link>
|
||||
<Link to={PUBLISH_PATH} className={location.pathname === PUBLISH_PATH ? 'active' : ''}>Publish</Link>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="header-right">
|
||||
<ConnectButton />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
@ -1,19 +0,0 @@
|
||||
import classNames from "classnames";
|
||||
import React from "react"
|
||||
import { FaHome } from "react-icons/fa"
|
||||
import { isMobileCheck } from "../utils/dimensions";
|
||||
|
||||
const HomeButton: React.FC = () => {
|
||||
const isMobile = isMobileCheck()
|
||||
return <button
|
||||
className={classNames("clear absolute p-2", {
|
||||
'top-2 left-2': isMobile,
|
||||
'top-8 left-8': !isMobile
|
||||
})}
|
||||
onClick={() => window.location.href = '/'}
|
||||
>
|
||||
<FaHome size={24} />
|
||||
</button>
|
||||
}
|
||||
|
||||
export default HomeButton;
|
@ -1,97 +0,0 @@
|
||||
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { AppInfo } from "../types/Apps";
|
||||
import useAppsStore from "../store/apps-store";
|
||||
import Modal from "./Modal";
|
||||
import { getAppName } from "../utils/app";
|
||||
import Loader from "./Loader";
|
||||
import classNames from "classnames";
|
||||
import { FaI } from "react-icons/fa6";
|
||||
|
||||
interface InstallButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
app: AppInfo;
|
||||
isIcon?: boolean;
|
||||
}
|
||||
|
||||
export default function InstallButton({ app, isIcon = false, ...props }: InstallButtonProps) {
|
||||
const { installApp, getCaps, getMyApp, getMyApps } =
|
||||
useAppsStore();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [caps, setCaps] = useState<any[]>([]);
|
||||
const [installing, setInstalling] = useState("");
|
||||
|
||||
const onClick = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
getCaps(app).then((manifest) => {
|
||||
setCaps(manifest.request_capabilities);
|
||||
});
|
||||
setShowModal(true);
|
||||
}, [app, setShowModal, getCaps]);
|
||||
|
||||
const install = useCallback(async () => {
|
||||
try {
|
||||
setInstalling(`Installing ${getAppName(app)}...`);
|
||||
await installApp(app);
|
||||
|
||||
const interval = setInterval(() => {
|
||||
getMyApp(app)
|
||||
.then((app) => {
|
||||
if (!app.installed) return;
|
||||
setInstalling("");
|
||||
setShowModal(false);
|
||||
clearInterval(interval);
|
||||
getMyApps();
|
||||
})
|
||||
.catch(console.log);
|
||||
}, 2000);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
window.alert(`Failed to install, please try again.`);
|
||||
setInstalling("");
|
||||
}
|
||||
}, [app, installApp, getMyApp]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
{...props}
|
||||
type="button"
|
||||
className={classNames("text-sm self-start", props.className, {
|
||||
'icon clear': isIcon
|
||||
})}
|
||||
onClick={onClick}
|
||||
disabled={!!installing}
|
||||
>
|
||||
{isIcon
|
||||
? <FaI />
|
||||
: installing
|
||||
? 'Installing...'
|
||||
: "Install"}
|
||||
</button>
|
||||
<Modal show={showModal} hide={() => setShowModal(false)}>
|
||||
{installing ? (
|
||||
<div className="flex-col-center gap-4">
|
||||
<Loader msg={installing} />
|
||||
<div className="text-center">
|
||||
App is installing in the background. You can safely close this window.
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-col-center gap-2">
|
||||
<h4>Approve App Permissions</h4>
|
||||
<h5 className="m-0">
|
||||
{getAppName(app)} needs the following permissions:
|
||||
</h5>
|
||||
<ul className="flex flex-col items-start">
|
||||
{caps.map((cap) => (
|
||||
<li>{JSON.stringify(cap)}</li>
|
||||
))}
|
||||
</ul>
|
||||
<button type="button" onClick={install}>
|
||||
Approve & Install
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import jazzicon from '@metamask/jazzicon';
|
||||
|
||||
interface JazziconProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
address: string;
|
||||
diameter?: number;
|
||||
}
|
||||
|
||||
const Jazzicon: React.FC<JazziconProps> = ({ address, diameter = 40, ...props }) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (address && ref.current) {
|
||||
const seed = parseInt(address.slice(2, 10), 16); // Derive a seed from Ethereum address
|
||||
const icon = jazzicon(diameter, seed);
|
||||
|
||||
// Clear the current icon
|
||||
ref.current.innerHTML = '';
|
||||
// Append the new icon
|
||||
ref.current.appendChild(icon);
|
||||
}
|
||||
}, [address, diameter]);
|
||||
|
||||
return <div {...props} ref={ref} />;
|
||||
};
|
||||
|
||||
export default Jazzicon;
|
@ -1,34 +0,0 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { AppInfo } from "../types/Apps";
|
||||
import classNames from "classnames";
|
||||
import { FaPlay } from "react-icons/fa6";
|
||||
|
||||
interface LaunchButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
app: AppInfo;
|
||||
launchPath: string;
|
||||
isIcon?: boolean;
|
||||
}
|
||||
|
||||
export default function LaunchButton({ app, launchPath, isIcon = false, ...props }: LaunchButtonProps) {
|
||||
const onLaunch = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
window.location.href = `/${launchPath.replace('/', '')}`
|
||||
return;
|
||||
}, [app, launchPath]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
{...props}
|
||||
type="button"
|
||||
className={classNames("text-sm self-start", props.className, {
|
||||
'icon clear': isIcon,
|
||||
'alt': !isIcon
|
||||
})}
|
||||
onClick={onLaunch}
|
||||
>
|
||||
{isIcon ? <FaPlay /> : "Launch"}
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import React from 'react'
|
||||
import { FaCircleNotch } from 'react-icons/fa6'
|
||||
|
||||
type LoaderProps = {
|
||||
msg: string
|
||||
}
|
||||
|
||||
export default function Loader({ msg }: LoaderProps) {
|
||||
return (
|
||||
<div id="loading" className="flex-col-center text-center gap-4">
|
||||
<h4>{msg}</h4>
|
||||
<FaCircleNotch className="animate-spin rounded-full h-8 w-8" />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { FaCheck, FaTriangleExclamation, FaCircle, FaX } from 'react-icons/fa6';
|
||||
|
||||
interface MessagePopupProps {
|
||||
type: 'success' | 'error' | 'info';
|
||||
content: string;
|
||||
onClose: () => void;
|
||||
autoCloseDelay?: number;
|
||||
}
|
||||
|
||||
const MessagePopup: React.FC<MessagePopupProps> = ({ type, content, onClose, autoCloseDelay = 5000 }) => {
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setVisible(false);
|
||||
onClose();
|
||||
}, 5000);
|
||||
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [onClose, autoCloseDelay]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
|
||||
const icon = {
|
||||
success: <FaCheck />,
|
||||
error: <FaTriangleExclamation />,
|
||||
info: <FaCircle />
|
||||
}[type];
|
||||
|
||||
return (
|
||||
<div className={classNames(
|
||||
'fixed bottom-4 right-4 p-4 rounded-lg shadow-lg flex items-center gap-4 max-w-md',
|
||||
{
|
||||
'bg-green-600': type === 'success',
|
||||
'bg-red-600': type === 'error',
|
||||
'bg-blue-600': type === 'info'
|
||||
}
|
||||
)}>
|
||||
<div className="text-2xl text-white">{icon}</div>
|
||||
<div className="flex-grow text-white">{content}</div>
|
||||
<button onClick={onClose} className="text-white hover:text-gray-200">
|
||||
<FaX />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessagePopup;
|
@ -1,279 +0,0 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { AppInfo } from "../types/Apps";
|
||||
import { FaX } from "react-icons/fa6";
|
||||
|
||||
interface Props {
|
||||
app?: AppInfo;
|
||||
packageName: string;
|
||||
publisherId: string;
|
||||
goBack: () => void;
|
||||
}
|
||||
|
||||
const VALID_VERSION_REGEX = /^\d+\.\d+\.\d+$/;
|
||||
|
||||
const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
|
||||
const [formData, setFormData] = useState({
|
||||
name: app?.metadata?.name || "",
|
||||
description: app?.metadata?.description || "",
|
||||
image: app?.metadata?.image || "",
|
||||
external_url: app?.metadata?.external_url || "",
|
||||
animation_url: app?.metadata?.animation_url || "",
|
||||
// properties, which can come from the app itself
|
||||
package_name: packageName,
|
||||
current_version: "",
|
||||
publisher: publisherId,
|
||||
mirrors: [publisherId],
|
||||
});
|
||||
|
||||
const [codeHashes, setCodeHashes] = useState<[string, string][]>(
|
||||
Object.entries(app?.metadata?.properties?.code_hashes || {}).concat([
|
||||
["", app?.state?.our_version || ""],
|
||||
])
|
||||
);
|
||||
|
||||
const handleFieldChange = (field, value) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[field]: value,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleFieldChange("package_name", packageName);
|
||||
}, [packageName]);
|
||||
|
||||
useEffect(() => {
|
||||
handleFieldChange("publisher", publisherId);
|
||||
}, [publisherId]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
const code_hashes = codeHashes.reduce((acc, [version, hash]) => {
|
||||
acc[version] = hash;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (!VALID_VERSION_REGEX.test(formData.current_version)) {
|
||||
window.alert("Current version must be in the format x.y.z");
|
||||
return;
|
||||
} else if (!code_hashes[formData.current_version]) {
|
||||
window.alert(
|
||||
`Code hashes must include current version (${formData.current_version})`
|
||||
);
|
||||
return;
|
||||
} else if (
|
||||
!Object.keys(code_hashes).reduce(
|
||||
(valid, version) => valid && VALID_VERSION_REGEX.test(version),
|
||||
true
|
||||
)
|
||||
) {
|
||||
window.alert("Code hashes must be a JSON object with valid version keys");
|
||||
return;
|
||||
}
|
||||
|
||||
const jsonData = JSON.stringify({
|
||||
name: formData.name,
|
||||
description: formData.description,
|
||||
image: formData.image,
|
||||
external_url: formData.external_url,
|
||||
animation_url: formData.animation_url,
|
||||
properties: {
|
||||
package_name: formData.package_name,
|
||||
current_version: formData.current_version,
|
||||
publisher: formData.publisher,
|
||||
mirrors: formData.mirrors,
|
||||
code_hashes,
|
||||
},
|
||||
});
|
||||
|
||||
const blob = new Blob([jsonData], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download =
|
||||
formData.package_name + "_" + formData.publisher + "_metadata.json";
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}, [formData, codeHashes]);
|
||||
|
||||
const handleClearForm = () => {
|
||||
setFormData({
|
||||
name: "",
|
||||
description: "",
|
||||
image: "",
|
||||
external_url: "",
|
||||
animation_url: "",
|
||||
|
||||
package_name: "",
|
||||
current_version: "",
|
||||
publisher: "",
|
||||
mirrors: [],
|
||||
});
|
||||
setCodeHashes([]);
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="flex flex-col card mt-2 gap-2">
|
||||
<h4>Fill out metadata</h4>
|
||||
<div className="flex flex-col w-3/4">
|
||||
<label className="metadata-label">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
value={formData.name}
|
||||
onChange={(e) => handleFieldChange("name", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-3/4">
|
||||
<label className="metadata-label">Description</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Description"
|
||||
value={formData.description}
|
||||
onChange={(e) => handleFieldChange("description", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-3/4">
|
||||
<label className="metadata-label">Image URL</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Image URL"
|
||||
value={formData.image}
|
||||
onChange={(e) => handleFieldChange("image", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-3/4">
|
||||
<label className="metadata-label">External URL</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="External URL"
|
||||
value={formData.external_url}
|
||||
onChange={(e) => handleFieldChange("external_url", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-3/4">
|
||||
<label className="metadata-label">Animation URL</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Animation URL"
|
||||
value={formData.animation_url}
|
||||
onChange={(e) => handleFieldChange("animation_url", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-3/4">
|
||||
<label className="metadata-label">Package Name</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Package Name"
|
||||
value={formData.package_name}
|
||||
onChange={(e) => handleFieldChange("package_name", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-3/4">
|
||||
<label className="metadata-label">Current Version</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Current Version"
|
||||
value={formData.current_version}
|
||||
onChange={(e) => handleFieldChange("current_version", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-3/4">
|
||||
<label className="metadata-label">Publisher</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Publisher"
|
||||
value={formData.publisher}
|
||||
onChange={(e) => handleFieldChange("publisher", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-3/4">
|
||||
<label className="metadata-label">Mirrors (separated by commas)</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Mirrors (separated by commas)"
|
||||
value={formData.mirrors.join(",")}
|
||||
onChange={(e) =>
|
||||
handleFieldChange(
|
||||
"mirrors",
|
||||
e.target.value.split(",").map((m) => m.trim())
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-col w-3/4 gap-2"
|
||||
>
|
||||
<div
|
||||
className="flex gap-2 mt-0 justify-between w-full"
|
||||
>
|
||||
<h5 className="m-0">Code Hashes</h5>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCodeHashes([...codeHashes, ["", ""]])}
|
||||
className="clear"
|
||||
>
|
||||
Add code hash
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{codeHashes.map(([version, hash], ind, arr) => (
|
||||
<div
|
||||
key={ind + "_code_hash"}
|
||||
className="flex gap-2 mt-0 w-full"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Version"
|
||||
value={version}
|
||||
onChange={(e) =>
|
||||
setCodeHashes((prev) => {
|
||||
const newHashes = [...prev];
|
||||
newHashes[ind][0] = e.target.value;
|
||||
return newHashes;
|
||||
})
|
||||
}
|
||||
className="flex-1"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Hash"
|
||||
value={hash}
|
||||
onChange={(e) =>
|
||||
setCodeHashes((prev) => {
|
||||
const newHashes = [...prev];
|
||||
newHashes[ind][1] = e.target.value;
|
||||
return newHashes;
|
||||
})
|
||||
}
|
||||
className="flex-5"
|
||||
/>
|
||||
{arr.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setCodeHashes((prev) => prev.filter((_, i) => i !== ind))
|
||||
}
|
||||
className="icon"
|
||||
>
|
||||
<FaX />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex gap-2 my-4">
|
||||
<button type="button" onClick={handleSubmit} className="alt">
|
||||
Download JSON
|
||||
</button>
|
||||
<button type="button" onClick={handleClearForm} className="clear">
|
||||
Clear Form
|
||||
</button>
|
||||
<button type="button" onClick={goBack}>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetadataForm;
|
@ -1,68 +0,0 @@
|
||||
import classNames from 'classnames'
|
||||
import React, { MouseEvent } from 'react'
|
||||
import { FaX } from 'react-icons/fa6'
|
||||
import { isMobileCheck } from '../utils/dimensions'
|
||||
|
||||
export interface ModalProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
show: boolean
|
||||
hide: () => void
|
||||
hideClose?: boolean
|
||||
children: React.ReactNode,
|
||||
title?: string
|
||||
}
|
||||
|
||||
const Modal: React.FC<ModalProps> = ({
|
||||
show,
|
||||
hide,
|
||||
hideClose = false,
|
||||
title,
|
||||
...props
|
||||
}) => {
|
||||
const dontHide = (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
if (!show) {
|
||||
return null
|
||||
}
|
||||
|
||||
const isMobile = isMobileCheck()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(`bg-black/25 backdrop-blur-lg fixed top-0 bottom-0 left-0 right-0 flex flex-col c z-30 min-h-[10em] isMobile-${isMobile}`,
|
||||
{
|
||||
isMobile,
|
||||
show,
|
||||
'min-w-[30em]': !isMobile,
|
||||
'min-w-[75vw]': isMobile,
|
||||
}
|
||||
)}
|
||||
onClick={hide}
|
||||
>
|
||||
<div
|
||||
{...props}
|
||||
className={`flex flex-col relative bg-black/90 rounded-lg py-6 px-12 ${props.className || ''}`}
|
||||
onClick={dontHide}
|
||||
>
|
||||
{Boolean(title) && <h4 className='mt-0 mb-2'>{title}</h4>}
|
||||
{!hideClose && (
|
||||
<button
|
||||
className='icon absolute top-1 right-1'
|
||||
onClick={hide}
|
||||
>
|
||||
<FaX />
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
className='flex flex-col items-center w-full'
|
||||
onClick={dontHide}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Modal
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user