mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-12-22 08:01:47 +03:00
add a new app! kino_updates, serves a widget
This commit is contained in:
parent
8ad86b0183
commit
78aab0004a
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -6528,6 +6528,19 @@ version = "0.25.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||
|
||||
[[package]]
|
||||
name = "widget"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"kinode_process_lib 0.7.0 (git+https://github.com/kinode-dao/process_lib?tag=v0.7.0)",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wiggle"
|
||||
version = "19.0.2"
|
||||
|
@ -18,6 +18,7 @@ members = [
|
||||
"kinode/packages/app_store/download", "kinode/packages/app_store/install", "kinode/packages/app_store/uninstall",
|
||||
"kinode/packages/chess/chess",
|
||||
"kinode/packages/homepage/homepage",
|
||||
"kinode/packages/kino_updates/widget",
|
||||
"kinode/packages/kns_indexer/kns_indexer", "kinode/packages/kns_indexer/get_block", "kinode/packages/kns_indexer/state",
|
||||
"kinode/packages/settings/settings",
|
||||
"kinode/packages/terminal/terminal",
|
||||
|
16
kinode/packages/kino_updates/metadata.json
Normal file
16
kinode/packages/kino_updates/metadata.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "kino_updates",
|
||||
"description": "Updates from Kinode DAO",
|
||||
"image": "https://kinode.org/static/media/kinode.a60d538cb0ae88c56b07.png",
|
||||
"properties": {
|
||||
"package_name": "kino_updates",
|
||||
"current_version": "0.1.0",
|
||||
"publisher": "sys",
|
||||
"mirrors": [],
|
||||
"code_hashes": {
|
||||
"0.1.0": ""
|
||||
}
|
||||
},
|
||||
"external_url": "https://kinode.org",
|
||||
"animation_url": ""
|
||||
}
|
18
kinode/packages/kino_updates/pkg/manifest.json
Normal file
18
kinode/packages/kino_updates/pkg/manifest.json
Normal file
@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"process_name": "widget",
|
||||
"process_wasm_path": "/widget.wasm",
|
||||
"on_exit": "Restart",
|
||||
"request_networking": false,
|
||||
"request_capabilities": [
|
||||
"homepage:homepage:sys",
|
||||
"http_client:distro:sys",
|
||||
"http_server:distro:sys"
|
||||
],
|
||||
"grant_capabilities": [
|
||||
"http_client:distro:sys",
|
||||
"timer:distro:sys"
|
||||
],
|
||||
"public": false
|
||||
}
|
||||
]
|
1
kinode/packages/kino_updates/pkg/scripts.json
Normal file
1
kinode/packages/kino_updates/pkg/scripts.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
19
kinode/packages/kino_updates/widget/Cargo.toml
Normal file
19
kinode/packages/kino_updates/widget/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "widget"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bincode = "1.3.3"
|
||||
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.7.0" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
url = "2.5.0"
|
||||
wit-bindgen = "0.24.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "kinode:process"
|
164
kinode/packages/kino_updates/widget/src/lib.rs
Normal file
164
kinode/packages/kino_updates/widget/src/lib.rs
Normal file
@ -0,0 +1,164 @@
|
||||
use kinode_process_lib::{call_init, http, timer, Address, Request};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
wit_bindgen::generate!({
|
||||
path: "wit",
|
||||
world: "process",
|
||||
});
|
||||
|
||||
/// Fetching OS version from main package.. LMK if there's a better way...
|
||||
const CARGO_TOML: &str = include_str!("../../../../Cargo.toml");
|
||||
/// A static message to display on the homepage.
|
||||
const MOTD: &str = "Welcome to Kinode!";
|
||||
/// 20 minutes
|
||||
const REFRESH_INTERVAL: u64 = 20 * 60 * 1000;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct KinodeBlogPost {
|
||||
slug: String,
|
||||
content: String,
|
||||
title: String,
|
||||
date: u64,
|
||||
#[serde(rename = "thumbnailImage")]
|
||||
thumbnail_image: String,
|
||||
#[serde(flatten)]
|
||||
extra: std::collections::HashMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
call_init!(init);
|
||||
fn init(_our: Address) {
|
||||
// updates: given a static message, the current version of the system,
|
||||
// and the kinode.org website, produce a widget for our homepage which
|
||||
// presents this information in a visually appealing way.
|
||||
loop {
|
||||
// add ourselves to the homepage
|
||||
Request::to(("our", "homepage", "homepage", "sys"))
|
||||
.body(
|
||||
serde_json::json!({
|
||||
"Add": {
|
||||
"label": "KinoUpdates",
|
||||
"widget": create_widget(fetch_three_most_recent_blog_posts()),
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.send()
|
||||
.unwrap();
|
||||
|
||||
timer::set_and_await_timer(REFRESH_INTERVAL).expect("Failed to produce a timer!");
|
||||
}
|
||||
}
|
||||
|
||||
fn create_widget(posts: Vec<KinodeBlogPost>) -> String {
|
||||
return format!(
|
||||
r#"<html>
|
||||
<head>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
.post {{
|
||||
width: 100%;
|
||||
}}
|
||||
|
||||
.post-image {{
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 16px;
|
||||
}}
|
||||
|
||||
.post-info {{
|
||||
max-width: 67%
|
||||
}}
|
||||
|
||||
@media screen and (min-width: 500px) {{
|
||||
.post {{
|
||||
width: 49%;
|
||||
}}
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-white overflow-hidden">
|
||||
<p>Kinode {}: {}</p>
|
||||
<p>Recent posts from kinode.org:</p>
|
||||
<div
|
||||
id="latest-blog-posts"
|
||||
class="flex flex-col p-2 gap-2 backdrop-brightness-125 rounded-xl shadow-lg h-screen w-screen overflow-y-auto"
|
||||
style="
|
||||
scrollbar-color: transparent transparent;
|
||||
scrollbar-width: none;
|
||||
"
|
||||
>
|
||||
{}
|
||||
</div>
|
||||
</body>
|
||||
</html>"#,
|
||||
version_from_cargo_toml(),
|
||||
MOTD,
|
||||
posts
|
||||
.into_iter()
|
||||
.map(post_to_html_string)
|
||||
.collect::<String>()
|
||||
);
|
||||
}
|
||||
|
||||
fn version_from_cargo_toml() -> String {
|
||||
let version = CARGO_TOML
|
||||
.lines()
|
||||
.find(|line| line.starts_with("version = "))
|
||||
.expect("Failed to find version in Cargo.toml");
|
||||
|
||||
version
|
||||
.split('=')
|
||||
.last()
|
||||
.expect("Failed to parse version from Cargo.toml")
|
||||
.trim()
|
||||
.trim_matches('"')
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn fetch_three_most_recent_blog_posts() -> Vec<KinodeBlogPost> {
|
||||
let blog_posts = match http::send_request_await_response(
|
||||
http::Method::GET,
|
||||
url::Url::parse("https://kinode.org/api/blog/posts").unwrap(),
|
||||
None,
|
||||
60,
|
||||
vec![],
|
||||
) {
|
||||
Ok(response) => serde_json::from_slice::<Vec<KinodeBlogPost>>(response.body())
|
||||
.expect("Invalid UTF-8 from kinode.org"),
|
||||
Err(e) => panic!("Failed to fetch blog posts: {:?}", e),
|
||||
};
|
||||
|
||||
blog_posts.into_iter().rev().take(3).collect()
|
||||
}
|
||||
|
||||
/// Take first 100 chars of a blog post and append "..." to the end
|
||||
fn trim_content(content: &str) -> String {
|
||||
if content.len() > 100 {
|
||||
format!("{}...", &content[..100])
|
||||
} else {
|
||||
content.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn post_to_html_string(post: KinodeBlogPost) -> String {
|
||||
format!(
|
||||
r#"<div class="post p-2 grow self-stretch flex items-stretch rounded-lg shadow bg-white/10 font-sans w-full">
|
||||
<div
|
||||
class="post-image rounded mr-2 grow"
|
||||
style="background-image: url('https://kinode.org{}');"
|
||||
></div>
|
||||
<div class="post-info flex flex-col grow">
|
||||
<h2 class="font-bold">{}</h2>
|
||||
<p>{}</p>
|
||||
<a href="https://kinode.org/blog/post/{}" class="text-blue-500" target="_blank" rel="noopener noreferrer">Read more</a>
|
||||
</div>
|
||||
</div>"#,
|
||||
post.thumbnail_image,
|
||||
post.title,
|
||||
trim_content(&post.content),
|
||||
post.slug,
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user