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:
doria 2024-07-29 20:04:04 +09:00 committed by GitHub
commit b5aeac5b1a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
270 changed files with 7269 additions and 56461 deletions

4
.gitignore vendored
View File

@ -15,3 +15,7 @@ wit/
.env .env
kinode/src/bootstrapped_processes.rs kinode/src/bootstrapped_processes.rs
kinode/packages/**/wasi_snapshot_preview1.wasm 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
View File

@ -1117,6 +1117,19 @@ dependencies = [
"generic-array", "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]] [[package]]
name = "blst" name = "blst"
version = "0.3.12" version = "0.3.12"
@ -1719,7 +1732,7 @@ dependencies = [
"futures-core", "futures-core",
"libc", "libc",
"mio", "mio",
"parking_lot 0.12.3", "parking_lot",
"signal-hook", "signal-hook",
"signal-hook-mio", "signal-hook-mio",
"winapi", "winapi",
@ -1843,7 +1856,7 @@ dependencies = [
"hashbrown 0.14.5", "hashbrown 0.14.5",
"lock_api", "lock_api",
"once_cell", "once_cell",
"parking_lot_core 0.9.10", "parking_lot_core",
] ]
[[package]] [[package]]
@ -2517,6 +2530,17 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globe"
version = "0.1.0"
dependencies = [
"kinode_process_lib 0.9.0",
"serde",
"serde_json",
"url",
"wit-bindgen",
]
[[package]] [[package]]
name = "group" name = "group"
version = "0.13.0" version = "0.13.0"
@ -3013,15 +3037,6 @@ dependencies = [
"wit-bindgen", "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]] [[package]]
name = "io-extras" name = "io-extras"
version = "0.18.2" version = "0.18.2"
@ -3233,7 +3248,7 @@ dependencies = [
"hmac", "hmac",
"http 1.1.0", "http 1.1.0",
"jwt", "jwt",
"kit 0.6.7", "kit",
"lazy_static", "lazy_static",
"lib", "lib",
"nohash-hasher", "nohash-hasher",
@ -3298,7 +3313,7 @@ dependencies = [
[[package]] [[package]]
name = "kinode_process_lib" name = "kinode_process_lib"
version = "0.9.0" 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 = [ dependencies = [
"alloy", "alloy",
"alloy-primitives", "alloy-primitives",
@ -3319,45 +3334,8 @@ dependencies = [
[[package]] [[package]]
name = "kit" name = "kit"
version = "0.6.2" version = "0.6.8"
source = "git+https://github.com/kinode-dao/kit?tag=v0.6.2#59ca74d4952998753bf5e64404d09f31a4424830" source = "git+https://github.com/kinode-dao/kit?tag=v0.6.8#7b636305bed2aae6fe36f5ced44181ec7cbdb7a3"
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"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.21.7", "base64 0.21.7",
@ -3425,7 +3403,7 @@ name = "lib"
version = "0.9.0" version = "0.9.0"
dependencies = [ dependencies = [
"alloy", "alloy",
"kit 0.6.2", "kit",
"lazy_static", "lazy_static",
"rand 0.8.5", "rand 0.8.5",
"ring", "ring",
@ -3999,17 +3977,6 @@ dependencies = [
"syn 1.0.109", "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]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.3" version = "0.12.3"
@ -4017,21 +3984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core 0.9.10", "parking_lot_core",
]
[[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",
] ]
[[package]] [[package]]
@ -4042,7 +3995,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall 0.5.3", "redox_syscall",
"smallvec", "smallvec",
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
@ -4559,15 +4512,6 @@ dependencies = [
"rand_core 0.3.1", "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]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.3" version = "0.5.3"
@ -5334,18 +5278,6 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" 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]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.0" version = "1.2.0"
@ -6815,19 +6747,6 @@ dependencies = [
"rustls-pki-types", "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]] [[package]]
name = "wiggle" name = "wiggle"
version = "19.0.2" version = "19.0.2"

View File

@ -18,7 +18,7 @@ members = [
"kinode/packages/app_store/download", "kinode/packages/app_store/install", "kinode/packages/app_store/uninstall", "kinode/packages/app_store/download", "kinode/packages/app_store/install", "kinode/packages/app_store/uninstall",
"kinode/packages/chess/chess", "kinode/packages/chess/chess",
"kinode/packages/homepage/homepage", "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/kns_indexer/kns_indexer", "kinode/packages/kns_indexer/get_block", "kinode/packages/kns_indexer/state",
"kinode/packages/settings/settings", "kinode/packages/settings/settings",
"kinode/packages/terminal/terminal", "kinode/packages/terminal/terminal",

View File

@ -175,7 +175,7 @@
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
Copyright 2024 Unzentrum DAO Copyright 2024 Sybil Technologies AG
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

170
css/kinode.css Normal file
View 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;
}

View File

Before

Width:  |  Height:  |  Size: 644 B

After

Width:  |  Height:  |  Size: 644 B

View File

@ -14,7 +14,7 @@ path = "src/main.rs"
[build-dependencies] [build-dependencies]
anyhow = "1.0.71" 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" tokio = "1.28"
walkdir = "2.4" walkdir = "2.4"
zip = "0.6" zip = "0.6"

View File

@ -106,6 +106,29 @@ fn main() -> anyhow::Result<()> {
let parent_dir = pwd.parent().unwrap(); let parent_dir = pwd.parent().unwrap();
let packages_dir = pwd.join("packages"); 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)? let entries: Vec<_> = fs::read_dir(packages_dir)?
.map(|entry| entry.unwrap().path()) .map(|entry| entry.unwrap().path())
.collect(); .collect();

View File

@ -1,11 +1,11 @@
use crate::state::{PackageListing, PackageState, State}; use crate::state::{PackageListing, State};
use crate::DownloadResponse; use crate::DownloadResponse;
use kinode_process_lib::{ use kinode_process_lib::{
http::{ http::{
bind_http_path, bind_ws_path, send_response, serve_ui, IncomingHttpRequest, Method, bind_http_path, bind_ws_path, send_response, serve_ui, IncomingHttpRequest, Method,
StatusCode, StatusCode,
}, },
Address, NodeId, PackageId, Request, println, Address, NodeId, PackageId, ProcessId, Request,
}; };
use serde_json::json; use serde_json::json;
use std::collections::HashMap; use std::collections::HashMap;
@ -17,9 +17,7 @@ const ICON: &str = include_str!("icon");
pub fn init_frontend(our: &Address) { pub fn init_frontend(our: &Address) {
for path in [ for path in [
"/apps", "/apps",
"/apps/listed",
"/apps/:id", "/apps/:id",
"/apps/listed/:id",
"/apps/:id/caps", "/apps/:id/caps",
"/apps/:id/mirror", "/apps/:id/mirror",
"/apps/:id/auto-update", "/apps/:id/auto-update",
@ -32,7 +30,7 @@ pub fn init_frontend(our: &Address) {
"ui", "ui",
true, true,
false, false,
vec!["/", "/my-apps", "/app-details/:id", "/publish"], vec!["/", "/my-apps", "/app/:id", "/publish"],
) )
.expect("failed to serve static UI"); .expect("failed to serve static UI");
@ -74,7 +72,6 @@ fn make_widget() -> String {
} }
body { body {
color: white;
overflow: hidden; overflow: hidden;
} }
@ -90,8 +87,6 @@ fn make_widget() -> String {
height: 100vh; height: 100vh;
width: 100vw; width: 100vw;
overflow-y: auto; overflow-y: auto;
scrollbar-color: transparent transparent;
scrollbar-width: none;
} }
.app { .app {
@ -101,16 +96,11 @@ fn make_widget() -> String {
align-items: stretch; align-items: stretch;
border-radius: 0.5rem; border-radius: 0.5rem;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
background-color: rgba(255, 255, 255, 0.1);
cursor: pointer; cursor: pointer;
font-family: sans-serif; font-family: sans-serif;
width: 100%; width: 100%;
} }
.app:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.app-image { .app-image {
border-radius: 0.75rem; border-radius: 0.75rem;
margin-right: 0.5rem; margin-right: 0.5rem;
@ -142,11 +132,11 @@ fn make_widget() -> String {
} }
</style> </style>
</head> </head>
<body class="text-white overflow-hidden"> <body>
<div id="latest-apps"></div> <div id="latest-apps"></div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { 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(response => response.json())
.then(data => { .then(data => {
const container = document.getElementById('latest-apps'); const container = document.getElementById('latest-apps');
@ -154,14 +144,14 @@ fn make_widget() -> String {
if (app.metadata) { if (app.metadata) {
const a = document.createElement('a'); const a = document.createElement('a');
a.className = 'app'; 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.target = '_blank';
a.rel = 'noopener noreferrer'; a.rel = 'noopener noreferrer';
const iconLetter = app.metadata_hash.replace('0x', '')[0].toUpperCase(); const iconLetter = app.metadata_hash.replace('0x', '')[0].toUpperCase();
a.innerHTML = `<div a.innerHTML = `<div
class="app-image" class="app-image"
style=" style="
background-image: url('${app.metadata.image || `/icons/${iconLetter}`}'); background-image: url('${app.metadata.image || `/bird-orange.svg`}');
" "
></div> ></div>
<div class="app-info"> <div class="app-info">
@ -181,15 +171,12 @@ fn make_widget() -> String {
} }
/// Actions supported over HTTP: /// Actions supported over HTTP:
/// - get all downloaded apps: GET /apps /// - get all apps: GET /apps
/// - get all listed apps: GET /apps/listed
/// - get some subset of listed apps, via search or filter: ? /// - 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 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, download a listed app: POST /apps/:id
/// - install a downloaded app: POST /apps/:id
/// - uninstall/delete a downloaded app: DELETE /apps/:id /// - uninstall/delete a downloaded app: DELETE /apps/:id
/// - update a downloaded app: PUT /apps/:id /// - update a downloaded app: PUT /apps/:id
/// - approve capabilities for a downloaded app: POST /apps/:id/caps /// - 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) Ok(id)
} }
fn gen_package_info( fn gen_package_info(id: &PackageId, listing: &PackageListing) -> serde_json::Value {
id: &PackageId,
listing: Option<&PackageListing>,
state: Option<&PackageState>,
) -> serde_json::Value {
json!({ json!({
"owner": match &listing { "tba": listing.tba,
Some(listing) => Some(&listing.owner),
None => None,
},
"package": id.package().to_string(), "package": id.package().to_string(),
"publisher": id.publisher(), "publisher": id.publisher(),
"installed": match &state { "installed": match &listing.state {
Some(state) => state.installed, Some(state) => state.installed,
None => false, None => false,
}, },
"metadata_hash": match &listing { "metadata_hash": listing.metadata_hash,
Some(listing) => Some(&listing.metadata_hash), "metadata": listing.metadata,
None => None, "state": match &listing.state {
},
"metadata": match &listing {
Some(listing) => Some(&listing.metadata),
None => match state {
Some(state) => Some(&state.metadata),
None => None,
},
},
"state": match &state {
Some(state) => json!({ Some(state) => json!({
"mirrored_from": state.mirrored_from, "mirrored_from": state.mirrored_from,
"our_version": state.our_version, "our_version": state.our_version_hash,
"caps_approved": state.caps_approved, "caps_approved": state.caps_approved,
"mirroring": state.mirroring, "mirroring": state.mirroring,
"auto_update": state.auto_update, "auto_update": state.auto_update,
@ -275,7 +246,7 @@ fn serve_paths(
let url_params = req.url_params(); let url_params = req.url_params();
match bound_path { match bound_path {
// GET all downloaded apps // GET all apps
"/apps" => { "/apps" => {
if method != Method::GET { if method != Method::GET {
return Ok(( return Ok((
@ -285,39 +256,16 @@ fn serve_paths(
)); ));
} }
let all: Vec<serde_json::Value> = state let all: Vec<serde_json::Value> = state
.downloaded_packages .packages
.iter() .iter()
.map(|(package_id, package_state)| { .map(|(package_id, listing)| gen_package_info(package_id, listing))
let listing = state.get_listing(package_id);
gen_package_info(package_id, listing, Some(package_state))
})
.collect(); .collect();
return Ok((StatusCode::OK, None, serde_json::to_vec(&all)?)); return Ok((StatusCode::OK, None, serde_json::to_vec(&all)?));
} }
// GET all listed apps // GET detail about a specific app
"/apps/listed" => { // install an app: POST
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
// update a downloaded app: PUT // update a downloaded app: PUT
// uninstall/delete a downloaded app: DELETE // uninstall an app: DELETE
"/apps/:id" => { "/apps/:id" => {
let Ok(package_id) = get_package_id(url_params) else { let Ok(package_id) = get_package_id(url_params) else {
return Ok(( return Ok((
@ -329,36 +277,184 @@ fn serve_paths(
match method { match method {
Method::GET => { Method::GET => {
let Some(pkg) = state.downloaded_packages.get(&package_id) else { let Some(listing) = state.packages.get(&package_id) else {
return Ok(( return Ok((
StatusCode::NOT_FOUND, StatusCode::NOT_FOUND,
None, None,
format!("App not found: {package_id}").into_bytes(), format!("App not found: {package_id}").into_bytes(),
)); ));
}; };
let listing = state.get_listing(&package_id);
Ok(( Ok((
StatusCode::OK, StatusCode::OK,
None, None,
gen_package_info(&package_id, listing, Some(pkg)) gen_package_info(&package_id, listing)
.to_string() .to_string()
.into_bytes(), .into_bytes(),
)) ))
} }
Method::POST => { 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)?; crate::handle_install(state, &package_id)?;
Ok((StatusCode::CREATED, None, format!("Installed").into_bytes())) Ok((StatusCode::CREATED, None, format!("Installed").into_bytes()))
} } else {
Method::PUT => { // download a listed app
// update an app let pkg_listing: &PackageListing = state
let _pkg_listing: &PackageListing = state .packages
.get_listing(&package_id)
.ok_or(anyhow::anyhow!("No package"))?;
let pkg_state: &PackageState = state
.downloaded_packages
.get(&package_id) .get(&package_id)
.ok_or(anyhow::anyhow!("No package"))?; .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 let download_from = pkg_state
.mirrored_from .mirrored_from
.as_ref() .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 // GET caps for a specific downloaded app
// approve capabilities for a downloaded app: POST // approve capabilities for a downloaded app: POST
"/apps/:id/caps" => { "/apps/:id/caps" => {

View File

@ -156,6 +156,7 @@ fn handle_message(state: &mut State, message: &Message) -> anyhow::Result<()> {
} else { } else {
// attempt to resubscribe // attempt to resubscribe
state state
.kimap
.provider .provider
.subscribe_loop(1, utils::app_store_filter(state)); .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), }) => (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)); 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 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( return Resp::RemoteResponse(RemoteResponse::DownloadDenied(Reason::HashMismatch(
HashMismatch { HashMismatch {
requested: hash, 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 // get the .zip from VFS and attach as blob to response
let Ok(Ok(_)) = utils::vfs_request( let Ok(Ok(_)) = utils::vfs_request(
format!("/{}/pkg{}", package_id, file_name), format!("/{package_id}/pkg{file_name}"),
vfs::VfsAction::Read, vfs::VfsAction::Read,
) )
.send_and_await_response(VFS_TIMEOUT) else { .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 { pub fn rebuild_index(state: &mut State) -> LocalResponse {
// kill our old subscription and build a new one. // 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); let eth_provider = eth::Provider::new(CHAIN_ID, CHAIN_TIMEOUT);
*state = State::new( *state = State::new(state.our.clone(), eth_provider).expect("state creation failed");
state.our.clone(),
eth_provider,
state.contract_address.clone(),
)
.expect("state creation failed");
fetch_and_subscribe_logs(state); fetch_and_subscribe_logs(state);
LocalResponse::RebuildIndexResponse(RebuildIndexResponse::Success) LocalResponse::RebuildIndexResponse(RebuildIndexResponse::Success)
@ -475,32 +475,21 @@ fn handle_receive_download_package(
return Err(anyhow::anyhow!("received download but found no blob")); return Err(anyhow::anyhow!("received download but found no blob"));
}; };
// check the version hash for this download against requested! // check the version hash for this download against requested!
let download_hash = utils::generate_version_hash(&blob.bytes); let download_hash = utils::sha_256_hash(&blob.bytes);
let (verified, metadata) = match requested_package.desired_version_hash { let verified = match requested_package.desired_version_hash {
Some(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 { if download_hash != hash {
return Err(anyhow::anyhow!( return Err(anyhow::anyhow!(
"downloaded package is not desired version--rejecting download! \ "downloaded package is not desired version--rejecting download! \
download hash: {download_hash}, desired hash: {hash}" 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 => { None => {
println!("downloaded package cannot be found onchain, proceeding with unverified download"); println!("downloaded package cannot be found onchain, proceeding with unverified download");
(true, None) false
} }
Some(package_listing) => { Some(package_listing) => {
if let Some(metadata) = &package_listing.metadata { if let Some(metadata) = &package_listing.metadata {
@ -515,19 +504,20 @@ fn handle_receive_download_package(
proceeding with unverified download" proceeding with unverified download"
); );
} }
(true, Some(metadata.clone())) false
} else { } else {
println!("downloaded package has no metadata to check validity against, proceeding with unverified download"); 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) { let old_manifest_hash = match state.packages.get(package_id) {
Some(package_state) => package_state Some(listing) => listing
.manifest_hash .state
.clone() .as_ref()
.and_then(|state| state.manifest_hash.clone())
.unwrap_or("OLD".to_string()), .unwrap_or("OLD".to_string()),
_ => "OLD".to_string(), _ => "OLD".to_string(),
}; };
@ -536,22 +526,22 @@ fn handle_receive_download_package(
package_id, package_id,
PackageState { PackageState {
mirrored_from: Some(requested_package.from), mirrored_from: Some(requested_package.from),
our_version: download_hash, our_version_hash: download_hash,
installed: false, installed: false,
verified, verified,
caps_approved: false, caps_approved: false,
manifest_hash: None, // generated in the add fn manifest_hash: None, // generated in the add fn
mirroring: requested_package.mirror, mirroring: requested_package.mirror,
auto_update: requested_package.auto_update, auto_update: requested_package.auto_update,
metadata,
}, },
Some(blob.bytes), Some(blob.bytes),
)?; )?;
let new_manifest_hash = match state.downloaded_packages.get(package_id) { let new_manifest_hash = match state.packages.get(package_id) {
Some(package_state) => package_state Some(listing) => listing
.manifest_hash .state
.clone() .as_ref()
.and_then(|state| state.manifest_hash.clone())
.unwrap_or("NEW".to_string()), .unwrap_or("NEW".to_string()),
_ => "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 /// 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<()> { pub fn handle_install(state: &mut State, package_id: &PackageId) -> anyhow::Result<()> {
// wit version will default to the latest if not specified // wit version will default to the latest if not specified
let metadata = state let metadata = &state
.get_downloaded_package(package_id) .packages
.get(package_id)
.ok_or_else(|| anyhow::anyhow!("package not found in manager"))? .ok_or_else(|| anyhow::anyhow!("package not found in manager"))?
.metadata; .metadata;

View File

@ -3,8 +3,7 @@ use crate::{KIMAP_ADDRESS, VFS_TIMEOUT};
use alloy_sol_types::SolEvent; use alloy_sol_types::SolEvent;
use kinode_process_lib::kernel_types::Erc721Metadata; use kinode_process_lib::kernel_types::Erc721Metadata;
use kinode_process_lib::{ use kinode_process_lib::{
eth, kernel_types as kt, kimap, net::get_name, println, vfs, Address, Message, NodeId, eth, kernel_types as kt, kimap, net, println, vfs, Address, NodeId, PackageId, Request,
PackageId, Request,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -41,20 +40,14 @@ impl std::fmt::Display for AppStoreLogError {
impl std::error::Error for AppStoreLogError {} impl std::error::Error for AppStoreLogError {}
pub type PackageHash = String; #[derive(Debug, Deserialize, Serialize)]
pub struct MirroringFile {
/// listing information derived from metadata hash in listing event pub mirroring_from: Option<NodeId>,
#[derive(Debug, Serialize, Deserialize, Clone)] pub mirroring: bool,
pub struct PackageListing { pub auto_update: bool,
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, Serialize, Deserialize)] #[derive(Debug, Deserialize, Serialize)]
pub struct RequestedPackage { pub struct RequestedPackage {
pub from: NodeId, pub from: NodeId,
pub mirror: bool, pub mirror: bool,
@ -63,6 +56,17 @@ pub struct RequestedPackage {
pub desired_version_hash: Option<String>, 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 /// state of an individual package we have downloaded
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PackageState { 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) /// this is "us" if we don't know the source (usually cause it's a local install)
pub mirrored_from: Option<NodeId>, pub mirrored_from: Option<NodeId>,
/// the version of the package we have downloaded /// the version of the package we have downloaded
pub our_version: String, pub our_version_hash: String,
pub installed: bool, pub installed: bool,
pub verified: bool, pub verified: bool,
pub caps_approved: bool, pub caps_approved: bool,
@ -82,33 +86,22 @@ pub struct PackageState {
pub mirroring: bool, pub mirroring: bool,
/// if we get a listing data update, will we try to download it? /// if we get a listing data update, will we try to download it?
pub auto_update: bool, pub auto_update: bool,
pub metadata: Option<kt::Erc721Metadata>,
} }
/// this process's saved state /// this process's saved state
pub struct State { pub struct State {
/// our address, grabbed from init() /// our address, grabbed from init()
pub our: Address, pub our: Address,
/// the eth provider we are using -- not persisted /// the kimap helper we are using
pub provider: eth::Provider,
/// the kimap helper we are using -- not persisted
pub kimap: kimap::Kimap, 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. /// 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 /// when we boot, we can read logs starting from this block and
/// rebuild latest state. /// rebuild latest state.
pub last_saved_block: u64, pub last_saved_block: u64,
pub package_hashes: HashMap<PackageId, PackageHash>,
/// we keep the full state of the package manager here, calculated from /// we keep the full state of the package manager here, calculated from
/// the listings contract logs. in the future, we'll offload this and /// the listings contract logs. in the future, we'll offload this and
/// only track a certain number of packages... /// only track a certain number of packages...
pub listed_packages: HashMap<PackageHash, PackageListing>, pub packages: HashMap<PackageId, 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>,
/// the APIs we have /// the APIs we have
pub downloaded_apis: HashSet<PackageId>, pub downloaded_apis: HashSet<PackageId>,
/// the packages we have outstanding requests to download (not persisted) /// the packages we have outstanding requests to download (not persisted)
@ -119,11 +112,9 @@ pub struct State {
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct SerializedState { pub struct SerializedState {
pub contract_address: String, pub kimap: kimap::Kimap,
pub last_saved_block: u64, pub last_saved_block: u64,
pub package_hashes: HashMap<PackageId, PackageHash>, pub packages: HashMap<PackageId, PackageListing>,
pub listed_packages: HashMap<PackageHash, PackageListing>,
pub downloaded_packages: HashMap<PackageId, PackageState>,
pub downloaded_apis: HashSet<PackageId>, pub downloaded_apis: HashSet<PackageId>,
} }
@ -134,48 +125,35 @@ impl Serialize for State {
{ {
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("State", 6)?; 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("last_saved_block", &self.last_saved_block)?;
state.serialize_field("package_hashes", &self.package_hashes)?; state.serialize_field("packages", &self.packages)?;
state.serialize_field("listed_packages", &self.listed_packages)?;
state.serialize_field("downloaded_packages", &self.downloaded_packages)?;
state.serialize_field("downloaded_apis", &self.downloaded_apis)?; state.serialize_field("downloaded_apis", &self.downloaded_apis)?;
state.end() state.end()
} }
} }
impl State { impl State {
pub fn from_serialized(our: Address, provider: eth::Provider, s: SerializedState) -> Self { pub fn from_serialized(our: Address, s: SerializedState) -> Self {
State { State {
our, our,
provider: provider.clone(), kimap: s.kimap,
kimap: kimap::Kimap::new(provider, eth::Address::from_str(KIMAP_ADDRESS).unwrap()),
contract_address: s.contract_address,
last_saved_block: s.last_saved_block, last_saved_block: s.last_saved_block,
package_hashes: s.package_hashes, packages: s.packages,
listed_packages: s.listed_packages,
downloaded_packages: s.downloaded_packages,
downloaded_apis: s.downloaded_apis, downloaded_apis: s.downloaded_apis,
requested_packages: HashMap::new(), requested_packages: HashMap::new(),
requested_apis: HashMap::new(), requested_apis: HashMap::new(),
} }
} }
/// To create a new state, we populate the downloaded_packages map /// To create a new state, we populate the downloaded_packages map
/// with all packages parseable from our filesystem. /// with all packages parseable from our filesystem.
pub fn new( pub fn new(our: Address, provider: eth::Provider) -> anyhow::Result<Self> {
our: Address,
provider: eth::Provider,
contract_address: String,
) -> anyhow::Result<Self> {
let mut state = State { let mut state = State {
our, our,
provider: provider.clone(),
kimap: kimap::Kimap::new(provider, eth::Address::from_str(KIMAP_ADDRESS).unwrap()), kimap: kimap::Kimap::new(provider, eth::Address::from_str(KIMAP_ADDRESS).unwrap()),
contract_address,
last_saved_block: crate::KIMAP_FIRST_BLOCK, last_saved_block: crate::KIMAP_FIRST_BLOCK,
package_hashes: HashMap::new(), packages: HashMap::new(),
listed_packages: HashMap::new(),
downloaded_packages: HashMap::new(),
downloaded_apis: HashSet::new(), downloaded_apis: HashSet::new(),
requested_packages: HashMap::new(), requested_packages: HashMap::new(),
requested_apis: HashMap::new(), requested_apis: HashMap::new(),
@ -184,30 +162,49 @@ impl State {
Ok(state) Ok(state)
} }
pub fn get_listing(&self, package_id: &PackageId) -> Option<&PackageListing> { pub fn add_listing(&mut self, package_id: &PackageId, metadata: kt::Erc721Metadata) {
self.listed_packages self.packages.insert(
.get(self.package_hashes.get(package_id)?) package_id.clone(),
} PackageListing {
tba: eth::Address::ZERO,
pub fn get_downloaded_package(&self, package_id: &PackageId) -> Option<PackageState> { metadata_uri: "".to_string(),
self.downloaded_packages.get(package_id).cloned() 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( pub fn add_downloaded_package(
&mut self, &mut self,
package_id: &PackageId, package_id: &PackageId,
mut package_state: PackageState, mut package_state: PackageState,
package_bytes: Option<Vec<u8>>, package_zip_bytes: Option<Vec<u8>>,
) -> anyhow::Result<()> { ) -> 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)?; let manifest_hash = utils::create_package_drive(package_id, package_bytes)?;
package_state.manifest_hash = Some(manifest_hash); 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)? { if utils::extract_api(package_id)? {
self.downloaded_apis.insert(package_id.to_owned()); self.downloaded_apis.insert(package_id.to_owned());
} }
self.downloaded_packages listing.state = Some(package_state);
.insert(package_id.to_owned(), package_state);
// kinode_process_lib::set_state(&serde_json::to_vec(self)?); // kinode_process_lib::set_state(&serde_json::to_vec(self)?);
Ok(()) Ok(())
} }
@ -219,11 +216,15 @@ impl State {
fn_: impl FnOnce(&mut PackageState), fn_: impl FnOnce(&mut PackageState),
) -> bool { ) -> bool {
let res = self let res = self
.downloaded_packages .packages
.get_mut(package_id) .get_mut(package_id)
.map(|package_state| { .map(|listing| {
if let Some(package_state) = &mut listing.state {
fn_(package_state); fn_(package_state);
true true
} else {
false
}
}) })
.unwrap_or(false); .unwrap_or(false);
// kinode_process_lib::set_state(&serde_json::to_vec(self).unwrap()); // kinode_process_lib::set_state(&serde_json::to_vec(self).unwrap());
@ -256,24 +257,29 @@ impl State {
/// saves state /// saves state
pub fn populate_packages_from_filesystem(&mut self) -> anyhow::Result<()> { pub fn populate_packages_from_filesystem(&mut self) -> anyhow::Result<()> {
let Message::Response { body, .. } = // call VFS and ask for all directories in our root drive
utils::vfs_request("/".to_string(), vfs::VfsAction::ReadDir) // (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)?? .send_and_await_response(VFS_TIMEOUT)??
.body(),
)?
else { else {
return Err(anyhow::anyhow!("vfs: bad response")); return Err(anyhow::anyhow!("vfs: unexpected response to ReadDir"));
};
let response = serde_json::from_slice::<vfs::VfsResponse>(&body)?;
let vfs::VfsResponse::ReadDir(entries) = response else {
return Err(anyhow::anyhow!("vfs: unexpected response: {:?}", response));
}; };
for entry in entries { for entry in entries {
// ignore non-dirs
if entry.file_type != vfs::FileType::Directory {
continue;
}
// ignore non-package dirs // ignore non-package dirs
let Ok(package_id) = entry.path.parse::<PackageId>() else { let Ok(package_id) = entry.path.parse::<PackageId>() else {
continue; continue;
}; };
if entry.file_type == vfs::FileType::Directory { // grab package .zip if it exists
let zip_file = vfs::File { let zip_file = vfs::File {
path: format!("/{}/pkg/{}.zip", package_id, package_id), path: format!("/{package_id}/pkg/{package_id}.zip"),
timeout: 5, timeout: 5,
}; };
let Ok(zip_file_bytes) = zip_file.read() else { let Ok(zip_file_bytes) = zip_file.read() else {
@ -281,27 +287,47 @@ impl State {
}; };
// generate entry from this data // generate entry from this data
// for the version hash, take the SHA-256 hash of the zip file // 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 { let manifest_file = vfs::File {
path: format!("/{}/pkg/manifest.json", package_id), path: format!("/{package_id}/pkg/manifest.json"),
timeout: 5, timeout: 5,
}; };
let manifest_bytes = manifest_file.read()?; let manifest_bytes = manifest_file.read()?;
// the user will need to turn mirroring and auto-update back on if they // get mirroring data if available
// have to reset the state of their app store for some reason. the apps let mirroring_file = vfs::File {
// themselves will remain on disk unless explicitly deleted. 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( self.add_downloaded_package(
&package_id, &package_id,
PackageState { PackageState {
mirrored_from: None, mirrored_from: mirroring_data.mirroring_from,
our_version, our_version_hash,
installed: true, installed: true,
verified: true, // implicitly verified (TODO re-evaluate) verified: true, // implicitly verified (TODO re-evaluate)
caps_approved: false, // must re-approve if you want to do something caps_approved: false, // must re-approve if you want to do something
manifest_hash: Some(utils::generate_metadata_hash(&manifest_bytes)), manifest_hash: Some(utils::keccak_256_hash(&manifest_bytes)),
mirroring: false, mirroring: mirroring_data.mirroring,
auto_update: false, auto_update: mirroring_data.auto_update,
metadata: None,
}, },
None, None,
)?; )?;
@ -310,8 +336,7 @@ impl State {
utils::vfs_request(format!("/{package_id}/pkg/api"), vfs::VfsAction::Metadata) utils::vfs_request(format!("/{package_id}/pkg/api"), vfs::VfsAction::Metadata)
.send_and_await_response(VFS_TIMEOUT) .send_and_await_response(VFS_TIMEOUT)
{ {
self.downloaded_apis.insert(package_id.to_owned()); self.downloaded_apis.insert(package_id);
}
} }
} }
Ok(()) Ok(())
@ -319,7 +344,7 @@ impl State {
pub fn uninstall(&mut self, package_id: &PackageId) -> anyhow::Result<()> { pub fn uninstall(&mut self, package_id: &PackageId) -> anyhow::Result<()> {
utils::uninstall(package_id)?; 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)?); // kinode_process_lib::set_state(&serde_json::to_vec(self)?);
println!("uninstalled {package_id}"); println!("uninstalled {package_id}");
Ok(()) Ok(())
@ -335,154 +360,81 @@ impl State {
log: eth::Log, log: eth::Log,
update_listings: bool, update_listings: bool,
) -> Result<(), AppStoreLogError> { ) -> Result<(), AppStoreLogError> {
println!("ingesting contract event");
let block_number: u64 = log.block_number.ok_or(AppStoreLogError::NoBlockNumber)?; let block_number: u64 = log.block_number.ok_or(AppStoreLogError::NoBlockNumber)?;
match log.topics()[0] { let kimap::contract::Note::SIGNATURE_HASH = log.topics()[0] else {
kimap::contract::Note::SIGNATURE_HASH => { return Ok(());
let note = };
kimap::contract::Note::decode_log_data(log.data(), false).map_err(|e| {
println!("error decoding note: {e}");
AppStoreLogError::DecodeLogError
})?;
let name = get_name(&note.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(&note.nodehash.to_string(), log.block_number, Some(5))
.ok_or(AppStoreLogError::DecodeLogError)?; .ok_or(AppStoreLogError::DecodeLogError)?;
match std::str::from_utf8(&note.note) { // the app store exclusively looks for ~metadata-uri postings: if one is
Ok("~metadata-uri") => { // observed, we then *query* for ~metadata-hash to verify the content
let metadata_url = String::from_utf8_lossy(&note.data).to_string(); // at the URI.
// generate ~metadata-hash notehash //
let meta_note_name = format!("~metadata-hash.{name}"); // 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(&note.note) else {
return Ok(());
};
let (_tba, _owner, data) = let metadata_uri = String::from_utf8_lossy(&note.data).to_string();
self.kimap.get(&meta_note_name).map_err(|e| {
println!("Error getting metadata hash: {:?}", e); // generate ~metadata-hash notehash
AppStoreLogError::DecodeLogError 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 { if let Some(hash_note) = data {
let metadata_hash = String::from_utf8_lossy(&hash_note).to_string(); 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('.') .split_once('.')
.ok_or(AppStoreLogError::InvalidPublisherName) .ok_or(AppStoreLogError::InvalidPublisherName)
.and_then(|(package, publisher)| { .and_then(|(package, publisher)| {
if package.is_empty() || publisher.is_empty() { if package.is_empty() || publisher.is_empty() {
Err(AppStoreLogError::InvalidPublisherName) Err(AppStoreLogError::InvalidPublisherName)
} else { } 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( let package_id = PackageId::new(&package_name, &publisher_name);
PackageId::new(&package_name, &publisher_name),
package_hash.clone(),
);
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) => { std::collections::hash_map::Entry::Occupied(mut listing) => {
let listing = listing.get_mut(); let listing = listing.get_mut();
listing.name = package_name; listing.tba = tba;
listing.publisher = publisher_name; listing.metadata_uri = metadata_uri;
listing.metadata_url = metadata_url;
listing.metadata_hash = metadata_hash; listing.metadata_hash = metadata_hash;
listing.metadata = Some(metadata); listing.metadata = Some(metadata);
} }
std::collections::hash_map::Entry::Vacant(listing) => { std::collections::hash_map::Entry::Vacant(listing) => {
listing.insert(PackageListing { listing.insert(PackageListing {
owner: "".to_string(), tba,
name: package_name, metadata_uri,
publisher: publisher_name,
metadata_url,
metadata_hash, metadata_hash,
metadata: Some(metadata), metadata: Some(metadata),
state: None,
}); });
} }
}; };
} }
}
Ok("~metadata-hash") => {
let metadata_hash = String::from_utf8_lossy(&note.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; self.last_saved_block = block_number;
if update_listings { if update_listings {
// kinode_process_lib::set_state(&serde_json::to_vec(self).unwrap()); // 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 /// this is done after ingesting a bunch of logs to remove fetches
/// of stale metadata. /// of stale metadata.
pub fn update_listings(&mut self) { 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 listing.metadata.is_none() {
if let Ok(metadata) = if let Ok(metadata) = utils::fetch_metadata_from_url(
utils::fetch_metadata_from_url(&listing.metadata_url, &listing.metadata_hash, 5) &listing.metadata_uri,
{ &listing.metadata_hash,
let package_id = PackageId::new(&listing.name, &listing.publisher); 30,
if let Some(package_state) = self.downloaded_packages.get(&package_id) { ) {
auto_update(&self.our, package_id, &metadata, &package_state); if let Some(package_state) = &listing.state {
auto_update(&self.our, package_id, &metadata, package_state);
} }
listing.metadata = Some(metadata); listing.metadata = Some(metadata);
} }
@ -516,7 +469,7 @@ impl State {
/// and install it if successful. /// and install it if successful.
fn auto_update( fn auto_update(
our: &Address, our: &Address,
package_id: PackageId, package_id: &PackageId,
metadata: &Erc721Metadata, metadata: &Erc721Metadata,
package_state: &PackageState, package_state: &PackageState,
) { ) {
@ -526,22 +479,22 @@ fn auto_update(
.code_hashes .code_hashes
.get(&metadata.properties.current_version); .get(&metadata.properties.current_version);
if let Some(mirrored_from) = &package_state.mirrored_from 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!( println!(
"auto-updating package {package_id} from {} to {} using mirror {mirrored_from}", "auto-updating package {package_id} from {} to {} using mirror {mirrored_from}",
metadata metadata
.properties .properties
.code_hashes .code_hashes
.get(&package_state.our_version) .get(&package_state.our_version_hash)
.unwrap_or(&package_state.our_version), .unwrap_or(&package_state.our_version_hash),
metadata.properties.current_version, metadata.properties.current_version,
); );
Request::to(our) Request::to(our)
.body( .body(
serde_json::to_vec(&LocalRequest::Download(DownloadRequest { serde_json::to_vec(&LocalRequest::Download(DownloadRequest {
package_id: crate::kinode::process::main::PackageId::from_process_lib( package_id: crate::kinode::process::main::PackageId::from_process_lib(
package_id, package_id.clone(),
), ),
download_from: mirrored_from.clone(), download_from: mirrored_from.clone(),
mirror: package_state.mirroring, mirror: package_state.mirroring,

View File

@ -10,7 +10,7 @@ use {
eth, get_blob, get_state, http, kernel_types as kt, kimap, println, vfs, Address, eth, get_blob, get_state, http, kernel_types as kt, kimap, println, vfs, Address,
LazyLoadBlob, PackageId, ProcessId, Request, LazyLoadBlob, PackageId, ProcessId, Request,
}, },
std::{collections::HashSet, str::FromStr}, std::collections::HashSet,
}; };
// quite annoyingly, we must convert from our gen'd version of PackageId // 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() { if let Some(state_bytes) = get_state() {
match serde_json::from_slice::<SerializedState>(&state_bytes) { match serde_json::from_slice::<SerializedState>(&state_bytes) {
Ok(state) => { Ok(state) => {
if state.contract_address == KIMAP_ADDRESS { if state.kimap.address().to_string() == KIMAP_ADDRESS {
return State::from_serialized(our, provider, state); return State::from_serialized(our, state);
} else { } else {
println!( println!(
"state contract address mismatch! expected {}, got {}", "state contract address mismatch. rebuilding state! expected {}, got {}",
KIMAP_ADDRESS, state.contract_address 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 { 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() eth::Filter::new()
.address(eth::Address::from_str(&state.contract_address).unwrap()) .address(*state.kimap.address())
.events([kimap::contract::Note::SIGNATURE]) .events([kimap::contract::Note::SIGNATURE])
.topic3(notes) .topic3(notes)
} }
@ -90,7 +97,7 @@ pub fn fetch_and_subscribe_logs(state: &mut State) {
let filter = app_store_filter(state); let filter = app_store_filter(state);
// get past logs, subscribe to new ones. // get past logs, subscribe to new ones.
for log in fetch_logs( for log in fetch_logs(
&state.provider, &state.kimap.provider,
&filter.clone().from_block(state.last_saved_block), &filter.clone().from_block(state.last_saved_block),
) { ) {
if let Err(e) = state.ingest_contract_event(log, false) { 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.update_listings();
state.provider.subscribe_loop(1, filter); state.kimap.provider.subscribe_loop(1, filter);
} }
/// fetch logs from the chain with a given 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![]) http::send_request_await_response(http::Method::GET, url, None, timeout, vec![])
{ {
if let Some(body) = get_blob() { if let Some(body) = get_blob() {
let hash = generate_metadata_hash(&body.bytes); let hash = keccak_256_hash(&body.bytes);
if &hash == metadata_hash { if &hash == metadata_hash {
return Ok(serde_json::from_slice::<kt::Erc721Metadata>(&body.bytes) return Ok(serde_json::from_slice::<kt::Erc721Metadata>(&body.bytes)
.map_err(|_| AppStoreLogError::MetadataNotFound)?); .map_err(|_| AppStoreLogError::MetadataNotFound)?);
@ -139,11 +146,11 @@ pub fn fetch_metadata_from_url(
Err(AppStoreLogError::MetadataNotFound) Err(AppStoreLogError::MetadataNotFound)
} }
/// generate a Keccak-256 hash of the metadata bytes /// generate a Keccak-256 hash string (with 0x prefix) of the metadata bytes
pub fn generate_metadata_hash(metadata: &[u8]) -> String { pub fn keccak_256_hash(bytes: &[u8]) -> String {
use sha3::{Digest, Keccak256}; use sha3::{Digest, Keccak256};
let mut hasher = Keccak256::new(); let mut hasher = Keccak256::new();
hasher.update(metadata); hasher.update(bytes);
format!("0x{:x}", hasher.finalize()) 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 /// 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}; use sha2::{Digest, Sha256};
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(zip_bytes); hasher.update(bytes);
format!("{:x}", hasher.finalize()) format!("{:x}", hasher.finalize())
} }
@ -187,31 +194,23 @@ pub fn new_package(
mirror: bool, mirror: bool,
bytes: Vec<u8>, bytes: Vec<u8>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
// add to listings
state.add_listing(package_id, metadata);
// set the version hash for this new local package // 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 { let package_state = PackageState {
mirrored_from: Some(state.our.node.clone()), mirrored_from: Some(state.our.node.clone()),
our_version, our_version_hash,
installed: false, installed: false,
verified: true, // sideloaded apps are implicitly verified because there is no "source" to verify against 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 caps_approved: true, // TODO see if we want to auto-approve local installs
manifest_hash: None, // generated in the add fn manifest_hash: None, // generated in the add fn
mirroring: mirror, mirroring: mirror,
auto_update: false, // can't auto-update a local package 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 { state.add_downloaded_package(&package_id, package_state, Some(bytes))
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(())
} }
/// create a new package drive in VFS and add the package zip to it. /// 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, timeout: VFS_TIMEOUT,
}; };
let manifest_bytes = manifest_file.read()?; 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> { pub fn extract_api(package_id: &PackageId) -> anyhow::Result<bool> {

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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};

View File

@ -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

View File

@ -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

View File

@ -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};

View File

@ -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

View File

@ -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};

View File

@ -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};

View File

@ -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

View File

@ -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};

View File

@ -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};

View File

@ -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};

View File

@ -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};

View File

@ -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};

View File

@ -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};

View File

@ -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};

View File

@ -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 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 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

View File

@ -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

View File

@ -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};

View File

@ -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};

View File

@ -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};

View File

@ -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};

View File

@ -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};

View File

@ -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

View File

@ -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};

View File

@ -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};

View File

@ -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};

View File

@ -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};

View File

@ -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};

View File

@ -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>

View File

@ -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 },
],
},
}

View File

@ -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

View File

@ -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"
]
}

View File

@ -5,10 +5,11 @@
<!-- This sets window.our.node --> <!-- This sets window.our.node -->
<script src="/our.js"></script> <script src="/our.js"></script>
<title>Package Store</title> <title>App Store</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="pragma" content="no-cache" /> <meta http-equiv="pragma" content="no-cache" />
<meta http-equiv="cache-control" content="no-cache" /> <meta http-equiv="cache-control" content="no-cache" />
<link rel="stylesheet" href="/kinode.css">
<link rel="icon" <link rel="icon"
href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg=="> href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg==">
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />

File diff suppressed because it is too large Load Diff

View File

@ -8,10 +8,9 @@
"start": "vite --port 3000", "start": "vite --port 3000",
"build": "tsc && vite build", "build": "tsc && vite build",
"copy": "mkdir -p ../pkg/ui && rm -rf ../pkg/ui/* && cp -r dist/* ../pkg/ui/", "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", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview", "preview": "vite preview"
"tc": "typechain --out-dir src/abis/types/ \"./src/abis/**/*.json\""
}, },
"dependencies": { "dependencies": {
"@kinode/client-api": "^0.1.0", "@kinode/client-api": "^0.1.0",
@ -19,15 +18,12 @@
"@rainbow-me/rainbowkit": "^2.1.2", "@rainbow-me/rainbowkit": "^2.1.2",
"@szhsin/react-menu": "^4.1.0", "@szhsin/react-menu": "^4.1.0",
"@tanstack/react-query": "^5.45.1", "@tanstack/react-query": "^5.45.1",
"classnames": "^2.5.1",
"idna-uts46-hx": "^6.0.4", "idna-uts46-hx": "^6.0.4",
"js-sha3": "^0.9.3", "js-sha3": "^0.9.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-icons": "^5.0.1", "react-icons": "^5.0.1",
"react-router-dom": "^6.21.3", "react-router-dom": "^6.21.3",
"tailwindcss": "^3.4.3",
"unocss": "^0.59.0-beta.1",
"viem": "^2.15.1", "viem": "^2.15.1",
"wagmi": "^2.10.3", "wagmi": "^2.10.3",
"zustand": "^4.4.7" "zustand": "^4.4.7"

View File

@ -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

View File

@ -1,45 +1,23 @@
import React, { useState } from "react"; import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import StorePage from "./pages/StorePage"; import StorePage from "./pages/StorePage";
import MyAppsPage from "./pages/MyAppsPage";
import AppPage from "./pages/AppPage"; 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 PublishPage from "./pages/PublishPage";
import Header from "./components/Header";
const BASE_URL = import.meta.env.BASE_URL; const BASE_URL = import.meta.env.BASE_URL;
if (window.our) window.our.process = BASE_URL?.replace("/", ""); 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() { 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 ( 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}> <Router basename={BASE_URL}>
<Header />
<Routes> <Routes>
<Route path={STORE_PATH} element={<StorePage />} /> <Route path={STORE_PATH} element={<StorePage />} />
<Route path={MY_APPS_PATH} element={<MyAppsPage />} />
<Route path={`${APP_DETAILS_PATH}/:id`} element={<AppPage />} /> <Route path={`${APP_DETAILS_PATH}/:id`} element={<AppPage />} />
<Route path={PUBLISH_PATH} element={<PublishPage />} /> <Route path={PUBLISH_PATH} element={<PublishPage />} />
</Routes> </Routes>

View File

@ -39,7 +39,7 @@ export function encodeIntoMintCall(multicalls: `0x${string}`, our_address: `0x${
functionName: 'execute', functionName: 'execute',
args: [ args: [
MULTICALL, MULTICALL,
BigInt(0), // value BigInt(0),
multicalls, multicalls,
1 1
] ]
@ -52,7 +52,7 @@ export function encodeIntoMintCall(multicalls: `0x${string}`, our_address: `0x${
our_address, our_address,
encodePacked(["bytes"], [stringToHex(app_name)]), encodePacked(["bytes"], [stringToHex(app_name)]),
initCall, initCall,
"0x", // erc721 details? <- encode app_store here? actually might be a slick way to do it. "0x",
KINO_ACCOUNT_IMPL, KINO_ACCOUNT_IMPL,
] ]
}) })

View File

@ -2,10 +2,9 @@ import { parseAbi } from "viem";
export { encodeMulticalls, encodeIntoMintCall } from "./helpers"; export { encodeMulticalls, encodeIntoMintCall } from "./helpers";
// move to constants? // also for anvil/optimism
export const KINOMAP: `0x${string}` = "0x0165878A594ca255338adfa4d48449f69242Eb8F"; export const KINOMAP: `0x${string}` = "0x0165878A594ca255338adfa4d48449f69242Eb8F";
export const MULTICALL: `0x${string}` = "0xcA11bde05977b3631167028862bE2a173976CA11"; 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([ export const multicallAbi = parseAbi([

View File

@ -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

View File

@ -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

View File

@ -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>
}
</>
);
}

View File

@ -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>
);
}

View File

@ -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>
}

View File

@ -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

View File

@ -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>
);
}

View File

@ -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

View File

@ -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>
</>
);
}

View File

@ -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>
)
}

View 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;

View File

@ -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;

View File

@ -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>
</>
);
}

View File

@ -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;

View File

@ -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>
</>
);
}

View File

@ -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>
)
}

View File

@ -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;

View File

@ -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;

View File

@ -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