Various small improvements

This commit is contained in:
Pauan 2019-06-20 17:52:52 +02:00
parent 787c117584
commit ec85fef904
6 changed files with 120 additions and 89 deletions

View File

@ -21,7 +21,7 @@ futures-signals = "0.3.5"
wasm-bindgen = "0.2.45"
js-sys = "0.3.22"
# TODO fix this before release
gloo = { git = "https://github.com/rustwasm/gloo", commit = "e8e505fbdbe96164381bd6fe7ef350438d54a84f" }
gloo = { git = "https://github.com/rustwasm/gloo" }
[dependencies.wasm-bindgen-futures]
version = "0.3.22"

View File

@ -25,6 +25,7 @@ struct RafState {
}
// TODO generalize this so it works for any target, not just JS
// TODO move this into gloo
struct Raf {
state: Rc<RefCell<Option<RafState>>>,
}
@ -468,6 +469,9 @@ impl<A, F, S> SignalVec for AnimatedMap<S, F>
pub struct Percentage(f64);
impl Percentage {
pub const START: Percentage = Percentage(0.0);
pub const END: Percentage = Percentage(1.0);
#[inline]
pub fn new(input: f64) -> Self {
debug_assert!(input >= 0.0 && input <= 1.0);

View File

@ -296,26 +296,30 @@ fn set_style<A, B>(style: &CssStyleDeclaration, name: &A, value: B, important: b
let mut names = vec![];
let mut values = vec![];
fn try_set_style(style: &CssStyleDeclaration, names: &mut Vec<String>, values: &mut Vec<String>, name: &str, value: &str, important: bool) -> Result<bool, JsValue> {
assert!(value != "");
// TODO handle browser prefixes ?
style.remove_property(name)?;
style.set_property_with_priority(name, value, if important { "important" } else { "" })?;
// TODO maybe use cfg(debug_assertions) ?
let is_changed = style.get_property_value(name)? != "";
if is_changed {
Ok(true)
} else {
names.push(name.to_string());
values.push(value.to_string());
Ok(false)
}
}
let okay = name.any(|name| {
value.any(|value| {
assert!(value != "");
// TODO handle browser prefixes ?
style.remove_property(name).unwrap_throw();
style.set_property_with_priority(name, value, if important { "important" } else { "" }).unwrap_throw();
// TODO maybe use cfg(debug_assertions) ?
let is_changed = style.get_property_value(name).unwrap_throw() != "";
if is_changed {
true
} else {
names.push(name.to_string());
values.push(value.to_string());
false
}
try_set_style(style, &mut names, &mut values, name, value, important).unwrap_throw()
})
});
@ -527,7 +531,7 @@ impl<A> DomBuilder<A> where A: AsRef<Node> {
#[inline]
pub fn children<'a, B: IntoIterator<Item = &'a mut Dom>>(mut self, children: B) -> Self {
self.check_children();
operations::insert_children_iter(self.element.as_ref(), &mut self.callbacks, children);
operations::insert_children_iter(self.element.as_ref(), &mut self.callbacks, children).unwrap_throw();
self
}
@ -578,7 +582,7 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
#[inline]
pub fn attribute<B>(self, name: B, value: &str) -> Self where B: MultiStr {
name.each(|name| {
dom_operations::set_attribute(self.element.as_ref(), name, value);
dom_operations::set_attribute(self.element.as_ref(), name, value).unwrap_throw();
});
self
}
@ -586,7 +590,7 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
#[inline]
pub fn attribute_namespace<B>(self, namespace: &str, name: B, value: &str) -> Self where B: MultiStr {
name.each(|name| {
dom_operations::set_attribute_ns(self.element.as_ref(), namespace, name, value);
dom_operations::set_attribute_ns(self.element.as_ref(), namespace, name, value).unwrap_throw();
});
self
}
@ -596,7 +600,7 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
let list = self.element.as_ref().class_list();
name.each(|name| {
dom_operations::add_class(&list, name);
dom_operations::add_class(&list, name).unwrap_throw();
});
self
@ -628,12 +632,12 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
let value = value.as_str();
name.each(|name| {
dom_operations::set_attribute(element, &name, value);
dom_operations::set_attribute(element, &name, value).unwrap_throw();
});
},
None => {
name.each(|name| {
dom_operations::remove_attribute(element, &name)
dom_operations::remove_attribute(element, &name).unwrap_throw();
});
},
}
@ -666,12 +670,12 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
let value = value.as_str();
name.each(|name| {
dom_operations::set_attribute_ns(element, &namespace, &name, value);
dom_operations::set_attribute_ns(element, &namespace, &name, value).unwrap_throw();
});
},
None => {
name.each(|name| {
dom_operations::remove_attribute_ns(element, &namespace, &name);
dom_operations::remove_attribute_ns(element, &namespace, &name).unwrap_throw();
});
},
}
@ -704,7 +708,7 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
is_set = true;
name.each(|name| {
dom_operations::add_class(&list, name);
dom_operations::add_class(&list, name).unwrap_throw();
});
}
@ -713,7 +717,7 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
is_set = false;
name.each(|name| {
dom_operations::remove_class(&list, name);
dom_operations::remove_class(&list, name).unwrap_throw();
});
}
}
@ -816,7 +820,7 @@ impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
// This needs to use `after_insert` because calling `.focus()` on an element before it is in the DOM has no effect
self.callbacks.after_insert(move |_| {
// TODO avoid updating if the focused state hasn't changed ?
dom_operations::set_focused(&element, value);
dom_operations::set_focused(&element, value).unwrap_throw();
});
self
@ -833,7 +837,7 @@ impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
// TODO verify that this is correct under all circumstances
callbacks.after_remove(for_each(value, move |value| {
// TODO avoid updating if the focused state hasn't changed ?
dom_operations::set_focused(&element, value);
dom_operations::set_focused(&element, value).unwrap_throw();
}));
});
}
@ -1035,14 +1039,14 @@ mod tests {
builder.style("foo", "bar")
}
a.apply(my_mixin);
let _ = a.apply(my_mixin);
}
#[test]
fn text_signal_types() {
text_signal(always("foo"));
text_signal(always("foo".to_owned()));
text_signal(always("foo".to_owned()).map(|x| RefFn::new(x, |x| x.as_str())));
let _ = text_signal(always("foo"));
let _ = text_signal(always("foo".to_owned()));
let _ = text_signal(always("foo".to_owned()).map(|x| RefFn::new(x, |x| x.as_str())));
//text_signal(always(Arc::new("foo")));
//text_signal(always(Arc::new("foo".to_owned())));
//text_signal(always(Rc::new("foo")));

View File

@ -1,4 +1,4 @@
use wasm_bindgen::UnwrapThrowExt;
use wasm_bindgen::{JsValue, UnwrapThrowExt};
use web_sys::{Node, HtmlElement, Element, DomTokenList};
@ -9,75 +9,88 @@ pub(crate) fn get_at(parent: &Node, index: u32) -> Node {
// TODO make this more efficient
#[inline]
pub(crate) fn move_from_to(parent: &Node, old_index: u32, new_index: u32) {
pub(crate) fn move_from_to(parent: &Node, old_index: u32, new_index: u32) -> Result<(), JsValue> {
let child = get_at(parent, old_index);
parent.remove_child(&child).unwrap_throw();
parent.remove_child(&child)?;
insert_at(parent, new_index, &child);
insert_at(parent, new_index, &child)?;
Ok(())
}
// TODO make this more efficient
#[inline]
pub(crate) fn insert_at(parent: &Node, index: u32, child: &Node) {
parent.insert_before(child, Some(&get_at(parent, index))).unwrap_throw();
pub(crate) fn insert_at(parent: &Node, index: u32, child: &Node) -> Result<(), JsValue> {
parent.insert_before(child, Some(&get_at(parent, index)))?;
Ok(())
}
// TODO make this more efficient
#[inline]
pub(crate) fn update_at(parent: &Node, index: u32, child: &Node) {
parent.replace_child(child, &get_at(parent, index)).unwrap_throw();
pub(crate) fn update_at(parent: &Node, index: u32, child: &Node) -> Result<(), JsValue> {
parent.replace_child(child, &get_at(parent, index))?;
Ok(())
}
// TODO make this more efficient
#[inline]
pub(crate) fn remove_at(parent: &Node, index: u32) {
parent.remove_child(&get_at(parent, index)).unwrap_throw();
pub(crate) fn remove_at(parent: &Node, index: u32) -> Result<(), JsValue> {
parent.remove_child(&get_at(parent, index))?;
Ok(())
}
#[inline]
pub(crate) fn set_focused(element: &HtmlElement, focused: bool) {
pub(crate) fn set_focused(element: &HtmlElement, focused: bool) -> Result<(), JsValue> {
if focused {
element.focus().unwrap_throw();
element.focus()?;
} else {
element.blur().unwrap_throw();
element.blur()?;
}
Ok(())
}
#[inline]
pub(crate) fn add_class(list: &DomTokenList, name: &str) {
list.add_1(name).unwrap_throw();
pub(crate) fn add_class(list: &DomTokenList, name: &str) -> Result<(), JsValue> {
list.add_1(name)?;
Ok(())
}
#[inline]
pub(crate) fn remove_class(list: &DomTokenList, name: &str) {
list.remove_1(name).unwrap_throw();
pub(crate) fn remove_class(list: &DomTokenList, name: &str) -> Result<(), JsValue> {
list.remove_1(name)?;
Ok(())
}
// TODO check that the attribute *actually* was changed
#[inline]
pub(crate) fn set_attribute(element: &Element, name: &str, value: &str) {
element.set_attribute(name, value).unwrap_throw();
pub(crate) fn set_attribute(element: &Element, name: &str, value: &str) -> Result<(), JsValue> {
element.set_attribute(name, value)?;
Ok(())
}
// TODO check that the attribute *actually* was changed
#[inline]
pub(crate) fn set_attribute_ns(element: &Element, namespace: &str, name: &str, value: &str) {
element.set_attribute_ns(Some(namespace), name, value).unwrap_throw();
pub(crate) fn set_attribute_ns(element: &Element, namespace: &str, name: &str, value: &str) -> Result<(), JsValue> {
element.set_attribute_ns(Some(namespace), name, value)?;
Ok(())
}
#[inline]
pub(crate) fn remove_attribute_ns(element: &Element, namespace: &str, name: &str) {
element.remove_attribute_ns(Some(namespace), name).unwrap_throw();
pub(crate) fn remove_attribute_ns(element: &Element, namespace: &str, name: &str) -> Result<(), JsValue> {
element.remove_attribute_ns(Some(namespace), name)?;
Ok(())
}
#[inline]
pub(crate) fn remove_attribute(element: &Element, name: &str) {
element.remove_attribute(name).unwrap_throw();
pub(crate) fn remove_attribute(element: &Element, name: &str) -> Result<(), JsValue> {
element.remove_attribute(name)?;
Ok(())
}

View File

@ -8,7 +8,7 @@ use futures_signals::{cancelable_future, CancelableFutureHandle};
use futures_signals::signal::{Signal, SignalExt};
use futures_signals::signal_vec::{VecDiff, SignalVec, SignalVecExt};
use web_sys::Node;
use wasm_bindgen::UnwrapThrowExt;
use wasm_bindgen::{JsValue, UnwrapThrowExt};
use wasm_bindgen_futures::futures_0_3::spawn_local;
use crate::dom_operations;
@ -82,14 +82,16 @@ pub fn insert_children_signal<A, B, C>(element: &A, callbacks: &mut Callbacks, s
}*/
#[inline]
pub(crate) fn insert_children_iter<'a, A: IntoIterator<Item = &'a mut Dom>>(element: &Node, callbacks: &mut Callbacks, value: A) {
pub(crate) fn insert_children_iter<'a, A: IntoIterator<Item = &'a mut Dom>>(element: &Node, callbacks: &mut Callbacks, value: A) -> Result<(), JsValue> {
for dom in value.into_iter() {
// TODO can this be made more efficient ?
callbacks.after_insert.append(&mut dom.callbacks.after_insert);
callbacks.after_remove.append(&mut dom.callbacks.after_remove);
element.append_child(&dom.element).unwrap_throw();
element.append_child(&dom.element)?;
}
Ok(())
}
@ -124,15 +126,12 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
});
}
// TODO verify that this will drop `children`
callbacks.after_remove(for_each_vec(signal, move |change| {
let mut state = state.lock().unwrap_throw();
fn process_change(state: &mut State, element: &Node, change: VecDiff<Dom>) -> Result<(), JsValue> {
match change {
VecDiff::Replace { values } => {
// TODO is this correct ?
if state.children.len() > 0 {
dom_operations::remove_all_children(&element);
dom_operations::remove_all_children(element);
for dom in state.children.drain(..) {
dom.callbacks.discard();
@ -151,14 +150,14 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
for dom in state.children.iter_mut() {
dom.callbacks.leak();
fragment.append_child(&dom.element).unwrap_throw();
fragment.append_child(&dom.element)?;
if !dom.callbacks.after_insert.is_empty() {
has_inserts = true;
}
}
element.append_child(&fragment).unwrap_throw();
element.append_child(&fragment)?;
if is_inserted && has_inserts {
for dom in state.children.iter_mut() {
@ -169,7 +168,7 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
VecDiff::InsertAt { index, mut value } => {
// TODO better usize -> u32 conversion
dom_operations::insert_at(&element, index as u32, &value.element);
dom_operations::insert_at(element, index as u32, &value.element)?;
value.callbacks.leak();
@ -182,7 +181,7 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
},
VecDiff::Push { mut value } => {
element.append_child(&value.element).unwrap_throw();
element.append_child(&value.element)?;
value.callbacks.leak();
@ -196,7 +195,7 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
VecDiff::UpdateAt { index, mut value } => {
// TODO better usize -> u32 conversion
dom_operations::update_at(&element, index as u32, &value.element);
dom_operations::update_at(element, index as u32, &value.element)?;
value.callbacks.leak();
@ -217,12 +216,12 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
state.children.insert(new_index, value);
// TODO better usize -> u32 conversion
dom_operations::move_from_to(&element, old_index as u32, new_index as u32);
dom_operations::move_from_to(element, old_index as u32, new_index as u32)?;
},
VecDiff::RemoveAt { index } => {
// TODO better usize -> u32 conversion
dom_operations::remove_at(&element, index as u32);
dom_operations::remove_at(element, index as u32)?;
state.children.remove(index).callbacks.discard();
},
@ -232,7 +231,7 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
// TODO create remove_last_child function ?
// TODO better usize -> u32 conversion
dom_operations::remove_at(&element, index as u32);
dom_operations::remove_at(element, index as u32)?;
state.children.pop().unwrap_throw().callbacks.discard();
},
@ -241,7 +240,7 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
// TODO is this correct ?
// TODO is this needed, or is it guaranteed by VecDiff ?
if state.children.len() > 0 {
dom_operations::remove_all_children(&element);
dom_operations::remove_all_children(element);
for dom in state.children.drain(..) {
dom.callbacks.discard();
@ -249,5 +248,14 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
}
},
}
Ok(())
}
// TODO verify that this will drop `children`
callbacks.after_remove(for_each_vec(signal, move |change| {
let mut state = state.lock().unwrap_throw();
process_change(&mut state, &element, change).unwrap_throw();
}));
}

View File

@ -42,22 +42,24 @@ impl<A> State<A> {
}*/
fn current_url_string() -> String {
window().unwrap_throw().location().href().unwrap_throw()
fn current_url_string() -> Result<String, JsValue> {
Ok(window().unwrap_throw().location().href()?)
}
// TODO inline ?
fn change_url(mutable: &Mutable<Url>) {
fn change_url(mutable: &Mutable<Url>) -> Result<(), JsValue> {
let mut lock = mutable.lock_mut();
let new_url = current_url_string();
let new_url = current_url_string()?;
// TODO test that this doesn't notify if the URLs are the same
// TODO helper method for this
// TODO can this be made more efficient ?
if lock.href() != new_url {
*lock = Url::new(&new_url).unwrap_throw();
*lock = Url::new(&new_url)?;
}
Ok(())
}
@ -67,25 +69,25 @@ struct CurrentUrl {
}
impl CurrentUrl {
fn new() -> Self {
fn new() -> Result<Self, JsValue> {
// TODO can this be made more efficient ?
let value = Mutable::new(Url::new(&current_url_string()).unwrap_throw());
let value = Mutable::new(Url::new(&current_url_string()?)?);
Self {
Ok(Self {
_listener: EventListener::new(&window().unwrap_throw(), "popstate", {
let value = value.clone();
move |_| {
change_url(&value);
change_url(&value).unwrap_throw();
}
}),
value,
}
})
}
}
// TODO somehow share this safely between threads ?
thread_local! {
static URL: CurrentUrl = CurrentUrl::new();
static URL: CurrentUrl = CurrentUrl::new().unwrap_throw();
}
@ -113,7 +115,7 @@ pub fn go_to_url(new_url: &str) {
.unwrap_throw();
URL.with(|url| {
change_url(&url.value);
change_url(&url.value).unwrap_throw();
});
}