Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yew Subxt WASM examples #968

Merged
merged 6 commits into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
**/*.rs.bk
**/.DS_Store
cargo-timing*
/examples/wasm-example/dist
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
/examples/wasm-example/dist
/examples/wasm-example/dist

11 changes: 11 additions & 0 deletions examples/wasm-example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "wasm-example"
version = "0.1.0"
edition = "2021"

[dependencies]
futures = "0.3.28"
subxt = { path = "../../subxt", default-features = false, features = ["jsonrpsee-web"], target_arch = "wasm32" }
yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }
Copy link
Collaborator

@lexnv lexnv May 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DQ: does yew = {version = "0.20.0", features = ["csr"] } provides enough features here?

web-sys = "0.3.63"
hex = "0.4.3"
8 changes: 8 additions & 0 deletions examples/wasm-example/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link data-trunk rel="scss" href="index.scss" />
<title>Yew App</title>
</head>
</html>
57 changes: 57 additions & 0 deletions examples/wasm-example/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
$primary: #24cc85;
$secondary: #1f624a;
$dark: #242a35;

*{
font-family: monospace;
color: $dark;

}

html{
background-color: $dark;
display: flex;
justify-content: center;
height: 100%;
}

h1{
font-weight: bolder;
color: $dark;
}

body{
width: 800px;
max-width: 100%;
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
height: 100%;
margin: 0px;
padding: 16px;
background-color: $primary;
}


p{
white-space: pre-wrap;
border-radius: 8px;
padding: 8px;
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
background-color: $dark;
color: white;
}
button{
font-size: large;
padding: 8px 16px;
font-weight: bold;
background-color: $dark;
border: none;
border-radius: 8px;
color: white;
cursor: pointer;
display: block;
margin-top: 8px;
}

button:hover{
background-color: $secondary;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}

152 changes: 152 additions & 0 deletions examples/wasm-example/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//! This is a small WASM app using the Yew UI framework showcasing how to use Subxt's features in a WASM environment.
//!
//! To run the app locally use Trunk, a WASM bundler:
//! ```
//! cargo install --locked trunk
//! ```
//! Run the app locally:
//! ```
//! trunk serve --open
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After running this command on the repo trunk serve --open the Cargo.lock will get modified leading to some git uncommitted changes. Could we make sure that the Cargo.lock is up to date before merging this? Thanks!

//! ```
//! You need to have a local polkadot/substrate node with it's JSON-RPC HTTP server running at 127.0.0.1:9933 in order for the examples to be working.
//! Also make sure your browser supports WASM.
use futures::{self, FutureExt};

use yew::prelude::*;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: do we need to import everything here?

mod services;

fn main() {
yew::Renderer::<SubxtExamplesComponent>::new().render();
}

struct SubxtExamplesComponent {
operation_title: Option<AttrValue>,
lines: Vec<AttrValue>,
}

enum Message {
Error(subxt::Error),
Reload,
Line(AttrValue),
Lines(Vec<AttrValue>),
ButtonClick(Button),
}

enum Button {
SubscribeFinalized,
FetchConstant,
FetchEvents,
}

impl Component for SubxtExamplesComponent {
type Message = Message;
type Properties = ();

fn create(_ctx: &Context<Self>) -> Self {
SubxtExamplesComponent {
lines: Vec::new(),
operation_title: None,
}
}

fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Message::Error(err) => {
self.lines.push(err.to_string().into());
}
Message::Reload => {
let window = web_sys::window().expect("Failed to access the window object");
window
.location()
.reload()
.expect("Failed to reload the page");
}
Message::Line(line) => {
self.lines.push(line);
}
Message::Lines(mut lines) => {
self.lines.append(&mut lines);
}
Message::ButtonClick(button) => match button {
Button::SubscribeFinalized => {
self.operation_title = Some("Subscribe to finalized blocks:".into());
let cb: Callback<AttrValue> = ctx.link().callback(Message::Line);
ctx.link()
.send_future(services::subscribe_to_finalized_blocks(cb).map(|result| {
let err = result.unwrap_err();
Message::Error(err)
}));
}
Button::FetchConstant => {
self.operation_title =
Some("Fetch the constant \"block_length\" of \"System\" pallet:".into());
ctx.link()
.send_future(services::fetch_constant_block_length().map(|result| {
match result {
Ok(value) => Message::Line(
format!(
"constant \"block_length\" of \"System\" pallet:\n {value}"
)
.into(),
),
Err(err) => Message::Error(err),
}
}))
}
Button::FetchEvents => {
self.operation_title = Some("Fetch events:".into());
ctx.link()
.send_future(services::fetch_events_dynamically().map(
|result| match result {
Ok(value) => {
Message::Lines(value.into_iter().map(AttrValue::from).collect())
}
Err(err) => Message::Error(err),
},
))
}
},
}
true
}

fn view(&self, ctx: &Context<Self>) -> Html {
let reload: Callback<MouseEvent> = ctx.link().callback(|_| Message::Reload);

let subscribe_finalized = ctx
.link()
.callback(|_| Message::ButtonClick(Button::SubscribeFinalized));

let fetch_constant = ctx
.link()
.callback(|_| Message::ButtonClick(Button::FetchConstant));

let fetch_events = ctx
.link()
.callback(|_| Message::ButtonClick(Button::FetchEvents));

html! {
<div>
if let Some(operation_title) = &self.operation_title{
<button onclick={reload}>{"🡄 Back"}</button>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the prefixed characters do not render properly on my machine, could we change it to something else?
Maybe plain Back or with some other chars?

<h1>{operation_title}</h1>
if self.lines.is_empty(){
<p>{"Loading..."}</p>
}
else{

}
{ for self.lines.iter().map(|line| html! {<p> {line} </p>}) }
}
else{
<>
<h1>{"Subxt Examples"}</h1>
<button onclick={subscribe_finalized}>{"Example: Subscribe to Finalized blocks"}</button>
<button onclick={fetch_constant}>{"Example: Fetch constant value"}</button>
<button onclick={fetch_events}>{"Example: Fetch events"}</button>
</>
}
</div>
}
}
}
73 changes: 73 additions & 0 deletions examples/wasm-example/src/services.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use futures::StreamExt;
use std::fmt::Write;
use subxt::{self, OnlineClient, PolkadotConfig};
use yew::{AttrValue, Callback};

#[subxt::subxt(runtime_metadata_path = "../../artifacts/polkadot_metadata_small.scale")]
mod polkadot {}

pub(crate) async fn fetch_constant_block_length() -> Result<String, subxt::Error> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
let constant_query = polkadot::constants().system().block_length();

let value = api.constants().at(&constant_query)?;
Ok(format!("{value:?}"))
}

pub(crate) async fn fetch_events_dynamically() -> Result<Vec<String>, subxt::Error> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
let events = api.events().at_latest().await?;
let mut event_strings = Vec::<String>::new();
for event in events.iter() {
let event = event?;
let pallet = event.pallet_name();
let variant = event.variant_name();
let field_values = event.field_values()?;
event_strings.push(format!("{pallet}::{variant}: {field_values}"));
}
Ok(event_strings)
}

/// subscribes to finalized blocks. When a block is received, it is formatted as a string and sent via the callback.
pub(crate) async fn subscribe_to_finalized_blocks(
cb: Callback<AttrValue>,
) -> Result<(), subxt::Error> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Subscribe to all finalized blocks:
let mut blocks_sub = api.blocks().subscribe_finalized().await?;
while let Some(block) = blocks_sub.next().await {
let block = block?;
let mut output = String::new();
writeln!(output, "Block #{}:", block.header().number).ok();
writeln!(output, " Hash: {}", block.hash()).ok();
writeln!(output, " Extrinsics:").ok();
let body = block.body().await?;
for ext in body.extrinsics().iter() {
let ext = ext?;
let idx = ext.index();
let events = ext.events().await?;
let bytes_hex = format!("0x{}", hex::encode(ext.bytes()));

// See the API docs for more ways to decode extrinsics:
let decoded_ext = ext.as_root_extrinsic::<polkadot::Call>();

writeln!(output, " Extrinsic #{idx}:").ok();
writeln!(output, " Bytes: {bytes_hex}").ok();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: is there something we could do here to wrap the bytes on the screens width?

I'm seeing the bytes exceeding the green and black boxes on my machine

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly a word-wrap: break-word; in the css

writeln!(output, " Decoded: {decoded_ext:?}").ok();
writeln!(output, " Events:").ok();

for evt in events.iter() {
let evt = evt?;

let pallet_name = evt.pallet_name();
let event_name = evt.variant_name();
let event_values = evt.field_values()?;

writeln!(output, " {pallet_name}_{event_name}").ok();
writeln!(output, " {}", event_values).ok();
}
}
cb.emit(output.into())
}
Ok(())
}