Skip to content

Commit

Permalink
xmlns RSX, added svg example (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
schell authored Dec 10, 2020
1 parent eae58f2 commit 6a325af
Show file tree
Hide file tree
Showing 18 changed files with 228 additions and 59 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"examples/todomvc",
"examples/multipage",
"examples/spa-routing",
"examples/svg",
"cookbook"
]

Expand Down
2 changes: 1 addition & 1 deletion cookbook/book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ src = "src"
title = "Cooking with Mogwai"

[preprocessor.variables.variables]
mogwai_docs_version = "0.3.5"
mogwai_docs_version = "0.3.6"
cookbookroot = ""

[output.html]
Expand Down
1 change: 1 addition & 0 deletions cookbook/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
- [Nesting components](nest_component.md)
- [List of components](lists_of_components.md)
- [Single page application routing](routing.md)
- [SVG](svg.md)
46 changes: 46 additions & 0 deletions cookbook/src/svg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Creating SVGs with mogwai
*S*calable *V*ector *G*raphics are images that are specified with XML. An SVG's
markup looks a lot like HTML markup and allows developers and artists to quickly
create images that can be resized without degrading the quality of the image.

### Contents
- [Explanation](#explanation)
- [Notes](#notes)
- [Code](#code)
- [Example](#example)

## Explanation

In `mogwai` we create SVG images using the same RSX we use to create any other
[`View`][structviewbuilder]. There's just one extra attribute we need to specify
that lets the browser know that we're drawing an SVG image instead of HTML -
the `xmlns` attribute.

## Notes

Unfortunately we must supply this namespace for each SVG node. It ends up not
being too much of a burden.

## Code

```rust, ignore
{{#include ../../examples/svg/src/lib.rs}}
```

Notice that the `main` of this example takes an optional string. This allows us
to pass the id of an element that we'd like to append our list/parent component
to. This allows us to load the example on the page right here.

## Example

<div id="app_example"></div>
<script type="module">
import init, { main } from '{{cookbookroot}}/examples/svg/pkg/svg.js';
window.addEventListener('load', async () => {
await init();
await main("app_example");
});
</script>


{{#include reflinks.md}}
2 changes: 1 addition & 1 deletion crates/mogwai-html-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mogwai-html-macro"
version = "0.2.1"
version = "0.2.2"
authors = ["Schell Scivally <[email protected]>"]
edition = "2018"
readme = "README.md"
Expand Down
37 changes: 27 additions & 10 deletions crates/mogwai-html-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ fn cast_type_attribute(node: &Node) -> Option<proc_macro2::TokenStream> {
}
}

fn xmlns_attribute(node: &Node) -> Option<proc_macro2::TokenStream> {
let key = node.name_as_string()?;
let expr = node.value.as_ref()?;
if key.starts_with("xmlns") {
Some(quote! {#expr})
} else {
None
}
}

fn attribute_to_token_stream(node: Node) -> Result<proc_macro2::TokenStream, Error> {
let span = node.name_span().unwrap_or(Span::call_site());
if let Some(key) = node.name_as_string() {
Expand Down Expand Up @@ -43,13 +53,12 @@ fn attribute_to_token_stream(node: Node) -> Result<proc_macro2::TokenStream, Err
[attribute_name] => Ok(quote! {
__mogwai_node.attribute(#attribute_name, #expr);
}),
keys => Err(Error::new(
span,
format!(
"expected a valid attribute key/value pair. Got `{}`",
keys.join(":")
),
)),
keys => {
let attribute_name = keys.join(":");
Ok(quote! {
__mogwai_node.attribute(#attribute_name, #expr);
})
}
}
} else {
Ok(quote! {
Expand Down Expand Up @@ -98,10 +107,13 @@ where
NodeType::Element => match node.name_as_string() {
Some(tag) => {
let mut type_is = quote! { web_sys::HtmlElement };
'find_type: for att_node in node.attributes.iter() {
let mut namespace = None;
for att_node in node.attributes.iter() {
if let Some(cast_type) = cast_type_attribute(att_node) {
type_is = cast_type;
break 'find_type;
}
if let Some(ns) = xmlns_attribute(att_node) {
namespace = Some(ns);
}
}

Expand All @@ -121,9 +133,14 @@ where
if let Some(error) = may_error {
Err(error)
} else {
let create = if let Some(ns) = namespace {
quote! {#view_path::element_ns(#tag, #ns)}
} else {
quote! {#view_path::element(#tag)}
};
Ok(quote! {
{
let mut __mogwai_node = (#view_path::element(#tag) as #view_path<#type_is>);
let mut __mogwai_node = #create as #view_path<#type_is>;
#(#attribute_tokens)*
#(#child_tokens)*
__mogwai_node
Expand Down
29 changes: 0 additions & 29 deletions examples/list-of-gizmos/scripts/common.sh

This file was deleted.

17 changes: 0 additions & 17 deletions examples/list-of-gizmos/scripts/test.sh

This file was deleted.

Empty file added examples/svg/.cargo-ok
Empty file.
3 changes: 3 additions & 0 deletions examples/svg/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
**/*.rs.bk
/pkg
49 changes: 49 additions & 0 deletions examples/svg/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
[package]
name = "svg"
version = "0.0.0"
authors = ["Schell Scivally <[email protected]>"]
edition = "2018"

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = ["console_error_panic_hook"]

[dependencies]
console_log = "^0.1"
log = "^0.4"
serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
wasm-bindgen = "^0.2"

# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.6", optional = true }

# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
#
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4.2", optional = true }

[dependencies.mogwai]
version = "^0.3"
path = "../../mogwai"

[dependencies.web-sys]
version = "^0.3"
# Add more web-sys API's as you need them
features = [
"HtmlInputElement",
]

[dev-dependencies]
wasm-bindgen-test = "0.2"

[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
18 changes: 18 additions & 0 deletions examples/svg/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>svg</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app"></div>
<script type="module">
import init, { main } from './pkg/svg.js';
window.addEventListener('load', async () => {
await init();
await main("app");
});
</script>
</body>
</html>
44 changes: 44 additions & 0 deletions examples/svg/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#![allow(unused_braces)]
use log::Level;
use mogwai::prelude::*;
use std::panic;
use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

/// Create an SVG circle using the xmlns attribute and the SVG namespace.
fn my_circle() -> ViewBuilder<HtmlElement> {
let ns = "http://www.w3.org/2000/svg";
builder! {
<svg xmlns=ns width="100" height="100">
<circle xmlns=ns
cx="50"
cy="50"
r="40"
stroke="green"
stroke-width="4"
fill="yellow" />
</svg>
}
}

#[wasm_bindgen]
pub fn main(parent_id: Option<String>) -> Result<(), JsValue> {
panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(Level::Trace).unwrap();

let view = View::from(my_circle());

if let Some(id) = parent_id {
let parent = utils::document()
.get_element_by_id(&id)
.unwrap();
view.run_in_container(&parent)
} else {
view.run()
}
}
Empty file added examples/svg/style.css
Empty file.
2 changes: 1 addition & 1 deletion mogwai/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mogwai"
version = "0.3.5"
version = "0.3.6"
authors = ["Schell Scivally <[email protected]>"]
edition = "2018"
license = "MIT"
Expand Down
7 changes: 7 additions & 0 deletions mogwai/src/txrx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,13 @@ impl<A: 'static> Transmitter<A> {
self.responders.send(a);
}

/// Send a bunch of messages.
pub fn send_many(&self, msgs: &[A]) {
msgs
.iter()
.for_each(|msg| self.send(msg));
}

/// Execute a future that results in a message, then send it. `wasm32` spawns
/// a local execution context to drive the `Future` to completion. Outside of
/// `wasm32` (e.g. during server-side rendering) this is a noop.
Expand Down
17 changes: 17 additions & 0 deletions mogwai/src/view/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,23 @@ where
let len = i.slots.len();
let _ = i.remove_child_at(len - 1);
}
Patch::Keep{ first } => {
let mut i = internals.borrow_mut();
let len = i.slots.len();
if *first < len {
for n in *first..len {
let _ = i.remove_child_at(n);
}
}
}
Patch::Drop{ first } => {
let mut i = internals.borrow_mut();
let end = usize::min(i.slots.len(), *first);
for n in 0..end {
let _ = i.remove_child_at(n);
}
}

});
}
}
12 changes: 12 additions & 0 deletions mogwai/src/view/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ pub enum Patch<T> {
PopFront,
/// Remove the last child node.
PopBack,
/// Keep the first N nodes.
Keep {
/// The number/size of the first nodes to keep.
first: usize,
},
/// Drop the first N nodes.
Drop {
/// the number/size of the first nodes to drop.
first: usize,
}
}

impl<T> Patch<T> {
Expand All @@ -171,6 +181,8 @@ impl<T> Patch<T> {
Patch::PushBack { value } => Patch::PushBack { value: f(value) },
Patch::PopFront => Patch::PopFront,
Patch::PopBack => Patch::PopBack,
Patch::Keep { first } => Patch::Keep{first: *first},
Patch::Drop { first } => Patch::Drop{first: *first},
}
}
}
Expand Down

0 comments on commit 6a325af

Please sign in to comment.