diff --git a/Cargo.toml b/Cargo.toml index 3f530855a..7c545750b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,6 @@ wasm-bindgen-futures = "0.3.6" # Markdown conversion pulldown-cmark = "^0.2.0" -#syntect = "^3.0.2" // todo bugs out -regex = "^1.1.0" [dependencies.web-sys] version = "0.3.4" @@ -46,6 +44,7 @@ features = [ "Node", "NodeList", "Performance", + "PopStateEvent", "Request", "RequestInit", "RequestMode", diff --git a/LICENSE b/LICENSE index 45e05f0df..7560b534d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018 David O'Connor +Copyright (c) 2019 David O'Connor Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/README.md b/README.md index d1ad45bba..9c64b01a7 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ And wasm-bindgen: `cargo install wasm-bindgen-cli` ### The theoretical minimum To start, clone [This quickstart repo](https://github.com/David-OConnor/seed-quickstart), run `build.sh` or `build.ps1` in a terminal, then start a dev server that supports WASM. -For example, with [Python](https://www.python.org/downloads/) installed, run `python server.py`. -(Linux users may need to run `python3 server.py`.) +For example, with [Python](https://www.python.org/downloads/) installed, run `python serve.py`. +(Linux users may need to run `python3 serve.py`.) Once you change your package name, you'll need to tweak the html file and build script, as described below. @@ -170,13 +170,13 @@ fn view(model: Model) -> El { // When passing numerical values to style!, "px" is implied. "border" => "2px solid #004422"; "padding" => 20 }, - // We can use normal Rust code and comments in the view. - h3![ format!("{} {}{} so far", model.count, model.what_we_count, plural) ], - button![ simple_ev("click", Msg::Increment), "+" ], - button![ simple_ev("click", Msg::Decrement), "-" ], + // We can use normal Rust code and comments in the view. + h3![ format!("{} {}{} so far", model.count, model.what_we_count, plural) ], + button![ simple_ev("click", Msg::Increment), "+" ], + button![ simple_ev("click", Msg::Decrement), "-" ], - // Optionally-displaying an element - if model.count >= 10 { h2![ style!{"padding" => 50}, "Nice!" ] } else { seed::empty() } + // Optionally-displaying an element + if model.count >= 10 { h2![ style!{"padding" => 50}, "Nice!" ] } else { seed::empty() } ], success_level(model.count), // Incorporating a separate component @@ -212,8 +212,8 @@ The Quickstart repo includes these, but you'll still need to do the rename. You `./build.sh` or `.\build.ps1` For development, you can view your app using a shimmed Python dev server described above. -(Set up [this mime-type shim](https://github.com/David-OConnor/seed-quickstart/blob/master/server.py) -from the quickstart repo, and run `python server.py`). +(Set up [this mime-type shim](https://github.com/David-OConnor/seed-quickstart/blob/master/serve.py) +from the quickstart repo, and run `python serve.py`). For details, reference [the wasm-bindgen documention](https://rustwasm.github.io/wasm-bindgen/whirlwind-tour/basic-usage.html). In the future, I'd like the build script and commands above to be replaced by [wasm-pack](https://github.com/rustwasm/wasm-pack). @@ -468,7 +468,7 @@ extern crate seed; div![] ``` -These macros accept any combination (0 or 1 per) of the following parameters: +These macros accept any combination of the following parameters: - One [Attrs](https://docs.rs/seed/0.1.6/seed/dom_types/struct.Attrs.html) struct - One [Style](https://docs.rs/seed/0.1.6/seed/dom_types/struct.Style.html) struct - One or more [Listener](https://docs.rs/seed/0.1.6/seed/dom_types/struct.Listener.html) structs, which handle events @@ -525,10 +525,10 @@ the same one more than once: Setting an InputElement's `checked` property is done through normal attributes: ```rust -input![ attrs!{"type" => "checkbox"; "checked" => true ] +input![ attrs!{"type" => "checkbox"; "checked" => true} ] ``` -To edit Attrs or Styles you've created, you can edit their .vals HashMap. To add +To change Attrs or Styles you've created, edit their .vals HashMap. To add a new part to them, use their .add method: ```rust let mut attributes = attrs!{}; @@ -559,6 +559,19 @@ fn view(model: Model) -> El { } ``` +We can combine Attrs and Style instances using their `merge` methods, which take +an &Attrs and &Style respectively. This can be used to compose styles from reusable parts. +Example: +```rust +let base_style = !style{"color" => "lavender"}; + +div![ + h1![ &base_style.merge(&style!{"grid-row" => "1 / 2"}) "First row" ], + h1![ &base_style.merge(&style!{"grid-row" => "2 / 3"}) "Second row" ], +] +``` + + Overall: we leverage of Rust's strict type system to flexibly-create the view using normal Rust code. @@ -885,10 +898,6 @@ function run() { ``` Note that you don't need to pass your Msg enum; it's inferred from the update function. -### Comments in the view -The Element-creation macros used to create views are normal Rust code, you can -use comments in them normally: either on their own line, or in line. - ### Logging in the web browser To output to the web browser's console (ie `console.log()` in JS), use `web_sys::console_log1`, @@ -942,11 +951,12 @@ let data = serde_json::from_str(&loaded_serialized).unwrap(); ``` -### Display markdown +### Display markdown and raw HTML Seed supports creating elements from markdown text, using [pulldown-cmark](https://github.com/raphlinus/pulldown-cmark) internally. Use the [El::from_markdown()](https://docs.rs/seed/0.1.6/seed/dom_types/struct.El.html#method.from_markdown) method to create an element that accepts a markdown &str as its only parameter, and displays -it normally as html. +it normally as html. Note that it does not support syntax highlighting. You can render raw HTML with `El::from_html(html)`, where `html` is a +&str of HTML. Example: ```rust @@ -958,13 +968,26 @@ fn view(model: Model) -> El { Let's set the existence-of-God issue aside for a later volume, and just [learn to code](https://play.rust-lang.org/). -"; +" +; + + let html = +" +
+

It is a truth universally acknowledged, that a single man in + possession of a good fortune, must be in want of a good time./p> +

+" +; div![ El::from_markdown(markdown) + El::from_html(html) ] } + + ``` ### Building a release version @@ -1016,8 +1039,8 @@ of your familiarity with Rust. - Complete documentation that always matches the current version. Getting examples working, and starting a project should be painless, and require nothing beyond this guide. - -- An API that's easy to read, write, and understand. + +- Concise, flexibilty vew syntax that's easy to read and write. ### A note on view syntax @@ -1081,10 +1104,10 @@ You may choose this approach over Elm if you're already comfortable with Rust, want the performance benefits, or don't want to code business logic in a purely-functional langauge. -Compared to React, for example, you may appreciate the consistency of how to write apps: +Compared with React, you may appreciate the consistency of how to write apps: There's no distinction between logic and display code; no restrictions on comments; no distinction between components and normal functions. The API is -flexible, and avoids the OOP boilerplate. +flexible, and avoids OOP boilerplate. I also hope that config, building, and dependency-management is cleaner with Cargo and wasm-bindgen than with npm. diff --git a/examples/counter/pkg/counter.js b/examples/counter/pkg/counter.js index 0493ffce3..a258f3e11 100644 --- a/examples/counter/pkg/counter.js +++ b/examples/counter/pkg/counter.js @@ -141,6 +141,15 @@ function GetOwnOrInheritedPropertyDescriptor(obj, id) { return {} } +const __widl_f_set_inner_html_Element_target = GetOwnOrInheritedPropertyDescriptor(typeof Element === 'undefined' ? null : Element.prototype, 'innerHTML').set || function() { + throw new Error(`wasm-bindgen: Element.innerHTML does not exist`); +}; + +__exports.__widl_f_set_inner_html_Element = function(arg0, arg1, arg2) { + let varg1 = getStringFromWasm(arg1, arg2); + __widl_f_set_inner_html_Element_target.call(getObject(arg0), varg1); +}; + const __widl_f_target_Event_target = GetOwnOrInheritedPropertyDescriptor(typeof Event === 'undefined' ? null : Event.prototype, 'target').get || function() { throw new Error(`wasm-bindgen: Event.target does not exist`); }; @@ -259,6 +268,21 @@ __exports.__widl_f_value_HTMLTextAreaElement = function(ret, arg0) { }; +const __widl_f_back_History_target = typeof History === 'undefined' ? null : History.prototype.back || function() { + throw new Error(`wasm-bindgen: History.back does not exist`); +}; + +__exports.__widl_f_back_History = function(arg0, exnptr) { + try { + __widl_f_back_History_target.call(getObject(arg0)); + } catch (e) { + const view = getUint32Memory(); + view[exnptr / 4] = 1; + view[exnptr / 4 + 1] = addHeapObject(e); + + } +}; + const __widl_f_append_child_Node_target = typeof Node === 'undefined' ? null : Node.prototype.appendChild || function() { throw new Error(`wasm-bindgen: Node.appendChild does not exist`); }; @@ -333,6 +357,18 @@ __exports.__widl_f_length_NodeList = function(arg0) { return __widl_f_length_NodeList_target.call(getObject(arg0)); }; +__exports.__widl_instanceof_PopStateEvent = function(idx) { + return getObject(idx) instanceof PopStateEvent ? 1 : 0; +}; + +const __widl_f_state_PopStateEvent_target = GetOwnOrInheritedPropertyDescriptor(typeof PopStateEvent === 'undefined' ? null : PopStateEvent.prototype, 'state').get || function() { + throw new Error(`wasm-bindgen: PopStateEvent.state does not exist`); +}; + +__exports.__widl_f_state_PopStateEvent = function(arg0) { + return addHeapObject(__widl_f_state_PopStateEvent_target.call(getObject(arg0))); +}; + __exports.__widl_instanceof_Window = function(idx) { return getObject(idx) instanceof Window ? 1 : 0; }; @@ -344,6 +380,17 @@ __exports.__widl_f_document_Window = function(arg0) { }; +__exports.__widl_f_history_Window = function(arg0, exnptr) { + try { + return addHeapObject(getObject(arg0).history); + } catch (e) { + const view = getUint32Memory(); + view[exnptr / 4] = 1; + view[exnptr / 4 + 1] = addHeapObject(e); + + } +}; + const __widl_f_log_1__target = console.log; __exports.__widl_f_log_1_ = function(arg0) { @@ -428,9 +475,11 @@ __exports.__wbindgen_cb_drop = function(i) { return 0; }; -__exports.__wbindgen_closure_wrapper765 = function(a, b, _ignored) { - const f = wasm.__wbg_function_table.get(2); - const d = wasm.__wbg_function_table.get(3); +__exports.__wbindgen_cb_forget = dropObject; + +__exports.__wbindgen_closure_wrapper790 = function(a, b, _ignored) { + const f = wasm.__wbg_function_table.get(6); + const d = wasm.__wbg_function_table.get(7); const cb = function(arg0) { this.cnt++; let a = this.a; diff --git a/examples/counter/pkg/counter_bg.wasm b/examples/counter/pkg/counter_bg.wasm index 429cd83eb..d51ad480b 100644 Binary files a/examples/counter/pkg/counter_bg.wasm and b/examples/counter/pkg/counter_bg.wasm differ diff --git a/examples/counter/server.py b/examples/counter/serve.py similarity index 100% rename from examples/counter/server.py rename to examples/counter/serve.py diff --git a/examples/counter/src/lib.rs b/examples/counter/src/lib.rs index 58115a8a6..3aa5f77fe 100644 --- a/examples/counter/src/lib.rs +++ b/examples/counter/src/lib.rs @@ -35,7 +35,7 @@ enum Msg { } /// The sole source of updating the model; returns a fresh one. -fn update(msg: Msg, model: Model) -> Model { +fn update(history: &History, msg: Msg, model: Model) -> Model { match msg { Msg::Increment => Model {count: model.count + 1, ..model}, Msg::Decrement => Model {count: model.count - 1, ..model}, @@ -98,5 +98,5 @@ fn view(model: Model) -> El { #[wasm_bindgen] pub fn render() { - seed::run(Model::default(), update, view, "main"); + seed::run(Model::default(), update, view, "main", None); } \ No newline at end of file diff --git a/examples/homepage/README.md b/examples/homepage/README.md index 0b93140fd..0472d0c6e 100644 --- a/examples/homepage/README.md +++ b/examples/homepage/README.md @@ -1,4 +1,4 @@ The Seed homepage, also serving as an example. Includes -lots of view syntax, and elements created from markdown. +//! simple interactions, markdown elements, basic routing, and lots of view markup. # [Homepage repo](https://github.com/David-OConnor/seed-homepage) \ No newline at end of file diff --git a/examples/server_interaction/pkg/appname.d.ts b/examples/server_interaction/pkg/appname.d.ts deleted file mode 100644 index b9241a95d..000000000 --- a/examples/server_interaction/pkg/appname.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -/* tslint:disable */ -export function render(): void; - diff --git a/examples/server_interaction/pkg/appname.js b/examples/server_interaction/pkg/appname.js deleted file mode 100644 index 266828920..000000000 --- a/examples/server_interaction/pkg/appname.js +++ /dev/null @@ -1,311 +0,0 @@ -(function() { - var wasm; - const __exports = {}; - /** - * @returns {void} - */ - __exports.render = function() { - return wasm.render(); - }; - - const heap = new Array(32); - - heap.fill(undefined); - - heap.push(undefined, null, true, false); - -function getObject(idx) { return heap[idx]; } - -let cachedTextDecoder = new TextDecoder('utf-8'); - -let cachegetUint8Memory = null; -function getUint8Memory() { - if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) { - cachegetUint8Memory = new Uint8Array(wasm.memory.buffer); - } - return cachegetUint8Memory; -} - -function getStringFromWasm(ptr, len) { - return cachedTextDecoder.decode(getUint8Memory().subarray(ptr, ptr + len)); -} - -let heap_next = heap.length; - -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1); - const idx = heap_next; - heap_next = heap[idx]; - - heap[idx] = obj; - return idx; -} - -const __widl_f_create_element_Document_target = typeof Document === 'undefined' ? null : Document.prototype.createElement || function() { - throw new Error(`wasm-bindgen: Document.createElement does not exist`); -}; - -let cachegetUint32Memory = null; -function getUint32Memory() { - if (cachegetUint32Memory === null || cachegetUint32Memory.buffer !== wasm.memory.buffer) { - cachegetUint32Memory = new Uint32Array(wasm.memory.buffer); - } - return cachegetUint32Memory; -} - -__exports.__widl_f_create_element_Document = function(arg0, arg1, arg2, exnptr) { - let varg1 = getStringFromWasm(arg1, arg2); - try { - return addHeapObject(__widl_f_create_element_Document_target.call(getObject(arg0), varg1)); - } catch (e) { - const view = getUint32Memory(); - view[exnptr / 4] = 1; - view[exnptr / 4 + 1] = addHeapObject(e); - - } -}; - -function isLikeNone(x) { - return x === undefined || x === null; -} - -const __widl_f_get_element_by_id_Document_target = typeof Document === 'undefined' ? null : Document.prototype.getElementById || function() { - throw new Error(`wasm-bindgen: Document.getElementById does not exist`); -}; - -__exports.__widl_f_get_element_by_id_Document = function(arg0, arg1, arg2) { - let varg1 = getStringFromWasm(arg1, arg2); - - const val = __widl_f_get_element_by_id_Document_target.call(getObject(arg0), varg1); - return isLikeNone(val) ? 0 : addHeapObject(val); - -}; - -const __widl_f_set_attribute_Element_target = typeof Element === 'undefined' ? null : Element.prototype.setAttribute || function() { - throw new Error(`wasm-bindgen: Element.setAttribute does not exist`); -}; - -__exports.__widl_f_set_attribute_Element = function(arg0, arg1, arg2, arg3, arg4, exnptr) { - let varg1 = getStringFromWasm(arg1, arg2); - let varg3 = getStringFromWasm(arg3, arg4); - try { - __widl_f_set_attribute_Element_target.call(getObject(arg0), varg1, varg3); - } catch (e) { - const view = getUint32Memory(); - view[exnptr / 4] = 1; - view[exnptr / 4 + 1] = addHeapObject(e); - - } -}; - -function GetOwnOrInheritedPropertyDescriptor(obj, id) { - while (obj) { - let desc = Object.getOwnPropertyDescriptor(obj, id); - if (desc) return desc; - obj = Object.getPrototypeOf(obj); - } -return {} -} - -const __widl_f_set_inner_html_Element_target = GetOwnOrInheritedPropertyDescriptor(typeof Element === 'undefined' ? null : Element.prototype, 'innerHTML').set || function() { - throw new Error(`wasm-bindgen: Element.innerHTML does not exist`); -}; - -__exports.__widl_f_set_inner_html_Element = function(arg0, arg1, arg2) { - let varg1 = getStringFromWasm(arg1, arg2); - __widl_f_set_inner_html_Element_target.call(getObject(arg0), varg1); -}; - -const __widl_f_add_event_listener_with_callback_EventTarget_target = typeof EventTarget === 'undefined' ? null : EventTarget.prototype.addEventListener || function() { - throw new Error(`wasm-bindgen: EventTarget.addEventListener does not exist`); -}; - -__exports.__widl_f_add_event_listener_with_callback_EventTarget = function(arg0, arg1, arg2, arg3, exnptr) { - let varg1 = getStringFromWasm(arg1, arg2); - try { - __widl_f_add_event_listener_with_callback_EventTarget_target.call(getObject(arg0), varg1, getObject(arg3)); - } catch (e) { - const view = getUint32Memory(); - view[exnptr / 4] = 1; - view[exnptr / 4 + 1] = addHeapObject(e); - - } -}; - -const __widl_f_append_child_Node_target = typeof Node === 'undefined' ? null : Node.prototype.appendChild || function() { - throw new Error(`wasm-bindgen: Node.appendChild does not exist`); -}; - -__exports.__widl_f_append_child_Node = function(arg0, arg1, exnptr) { - try { - return addHeapObject(__widl_f_append_child_Node_target.call(getObject(arg0), getObject(arg1))); - } catch (e) { - const view = getUint32Memory(); - view[exnptr / 4] = 1; - view[exnptr / 4 + 1] = addHeapObject(e); - - } -}; - -const __widl_f_set_text_content_Node_target = GetOwnOrInheritedPropertyDescriptor(typeof Node === 'undefined' ? null : Node.prototype, 'textContent').set || function() { - throw new Error(`wasm-bindgen: Node.textContent does not exist`); -}; - -__exports.__widl_f_set_text_content_Node = function(arg0, arg1, arg2) { - let varg1 = arg1 == 0 ? undefined : getStringFromWasm(arg1, arg2); - __widl_f_set_text_content_Node_target.call(getObject(arg0), varg1); -}; - -__exports.__widl_instanceof_Window = function(idx) { - return getObject(idx) instanceof Window ? 1 : 0; -}; - -__exports.__widl_f_document_Window = function(arg0) { - - const val = getObject(arg0).document; - return isLikeNone(val) ? 0 : addHeapObject(val); - -}; - -__exports.__wbg_newnoargs_6a80f84471205fc8 = function(arg0, arg1) { - let varg0 = getStringFromWasm(arg0, arg1); - return addHeapObject(new Function(varg0)); -}; - -__exports.__wbg_call_582b20dfcad7fee4 = function(arg0, arg1, exnptr) { - try { - return addHeapObject(getObject(arg0).call(getObject(arg1))); - } catch (e) { - const view = getUint32Memory(); - view[exnptr / 4] = 1; - view[exnptr / 4 + 1] = addHeapObject(e); - - } -}; - -__exports.__wbindgen_object_clone_ref = function(idx) { - return addHeapObject(getObject(idx)); -}; - -function dropObject(idx) { - if (idx < 36) return; - heap[idx] = heap_next; - heap_next = idx; -} - -__exports.__wbindgen_object_drop_ref = function(i) { dropObject(i); }; - -__exports.__wbindgen_number_get = function(n, invalid) { - let obj = getObject(n); - if (typeof(obj) === 'number') return obj; - getUint8Memory()[invalid] = 1; - return 0; -}; - -__exports.__wbindgen_is_null = function(idx) { - return getObject(idx) === null ? 1 : 0; -}; - -__exports.__wbindgen_is_undefined = function(idx) { - return getObject(idx) === undefined ? 1 : 0; -}; - -__exports.__wbindgen_boolean_get = function(i) { - let v = getObject(i); - if (typeof(v) === 'boolean') { - return v ? 1 : 0; - } else { - return 2; - } -}; - -__exports.__wbindgen_is_symbol = function(i) { - return typeof(getObject(i)) === 'symbol' ? 1 : 0; -}; - -let cachedTextEncoder = new TextEncoder('utf-8'); - -let WASM_VECTOR_LEN = 0; - -function passStringToWasm(arg) { - - const buf = cachedTextEncoder.encode(arg); - const ptr = wasm.__wbindgen_malloc(buf.length); - getUint8Memory().set(buf, ptr); - WASM_VECTOR_LEN = buf.length; - return ptr; -} - -__exports.__wbindgen_string_get = function(i, len_ptr) { - let obj = getObject(i); - if (typeof(obj) !== 'string') return 0; - const ptr = passStringToWasm(obj); - getUint32Memory()[len_ptr / 4] = WASM_VECTOR_LEN; - return ptr; -}; - -__exports.__wbindgen_cb_drop = function(i) { - const obj = getObject(i).original; - dropObject(i); - if (obj.cnt-- == 1) { - obj.a = 0; - return 1; - } - return 0; -}; - -__exports.__wbindgen_cb_forget = dropObject; - -__exports.__wbindgen_closure_wrapper315 = function(a, b, _ignored) { - const f = wasm.__wbg_function_table.get(6); - const d = wasm.__wbg_function_table.get(7); - const cb = function(arg0) { - this.cnt++; - let a = this.a; - this.a = 0; - try { - return f(a, b, addHeapObject(arg0)); - - } finally { - this.a = a; - if (this.cnt-- == 1) d(this.a, b); - - } - - }; - cb.a = a; - cb.cnt = 1; - let real = cb.bind(cb); - real.original = cb; - return addHeapObject(real); -}; - -__exports.__wbindgen_throw = function(ptr, len) { - throw new Error(getStringFromWasm(ptr, len)); -}; - -function init(path_or_module) { - let instantiation; - const imports = { './appname': __exports }; - if (path_or_module instanceof WebAssembly.Module) { - instantiation = WebAssembly.instantiate(path_or_module, imports) - .then(instance => { - return { instance, module: path_or_module } - }); -} else { - const data = fetch(path_or_module); - if (typeof WebAssembly.instantiateStreaming === 'function') { - instantiation = WebAssembly.instantiateStreaming(data, imports); - } else { - instantiation = data - .then(response => response.arrayBuffer()) - .then(buffer => WebAssembly.instantiate(buffer, imports)); - } -} -return instantiation.then(({instance}) => { - wasm = init.wasm = instance.exports; - -}); -}; -self.wasm_bindgen = Object.assign(init, __exports); -})(); diff --git a/examples/server_interaction/pkg/appname_bg.d.ts b/examples/server_interaction/pkg/appname_bg.d.ts deleted file mode 100644 index 75b5fdd12..000000000 --- a/examples/server_interaction/pkg/appname_bg.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* tslint:disable */ -export const memory: WebAssembly.Memory; -export function render(): void; -export function __wbindgen_malloc(a: number): number; -export const __wbg_function_table: WebAssembly.Table; diff --git a/examples/server_interaction/pkg/appname_bg.wasm b/examples/server_interaction/pkg/appname_bg.wasm deleted file mode 100644 index 3d368daf8..000000000 Binary files a/examples/server_interaction/pkg/appname_bg.wasm and /dev/null differ diff --git a/examples/server_interaction/pkg/server_interaction.js b/examples/server_interaction/pkg/server_interaction.js index 69fe3165f..d3c5aca17 100644 --- a/examples/server_interaction/pkg/server_interaction.js +++ b/examples/server_interaction/pkg/server_interaction.js @@ -305,17 +305,20 @@ __exports.__widl_f_headers_Request = function(arg0) { return addHeapObject(__widl_f_headers_Request_target.call(getObject(arg0))); }; -__exports.__widl_instanceof_Response = function(idx) { - return getObject(idx) instanceof Response ? 1 : 0; +__exports.__widl_instanceof_Window = function(idx) { + return getObject(idx) instanceof Window ? 1 : 0; }; -const __widl_f_json_Response_target = typeof Response === 'undefined' ? null : Response.prototype.json || function() { - throw new Error(`wasm-bindgen: Response.json does not exist`); +__exports.__widl_f_document_Window = function(arg0) { + + const val = getObject(arg0).document; + return isLikeNone(val) ? 0 : addHeapObject(val); + }; -__exports.__widl_f_json_Response = function(arg0, exnptr) { +__exports.__widl_f_history_Window = function(arg0, exnptr) { try { - return addHeapObject(__widl_f_json_Response_target.call(getObject(arg0))); + return addHeapObject(getObject(arg0).history); } catch (e) { const view = getUint32Memory(); view[exnptr / 4] = 1; @@ -324,27 +327,10 @@ __exports.__widl_f_json_Response = function(arg0, exnptr) { } }; -__exports.__widl_instanceof_Window = function(idx) { - return getObject(idx) instanceof Window ? 1 : 0; -}; - -__exports.__widl_f_document_Window = function(arg0) { - - const val = getObject(arg0).document; - return isLikeNone(val) ? 0 : addHeapObject(val); - -}; - __exports.__widl_f_fetch_with_request_Window = function(arg0, arg1) { return addHeapObject(getObject(arg0).fetch(getObject(arg1))); }; -const __widl_f_log_1__target = console.log; - -__exports.__widl_f_log_1_ = function(arg0) { - __widl_f_log_1__target(getObject(arg0)); -}; - __exports.__wbg_newnoargs_6a80f84471205fc8 = function(arg0, arg1) { let varg0 = getStringFromWasm(arg0, arg1); return addHeapObject(new Function(varg0)); @@ -361,17 +347,6 @@ __exports.__wbg_call_582b20dfcad7fee4 = function(arg0, arg1, exnptr) { } }; -__exports.__wbg_call_8ebb2e9cebdce6f5 = function(arg0, arg1, arg2, exnptr) { - try { - return addHeapObject(getObject(arg0).call(getObject(arg1), getObject(arg2))); - } catch (e) { - const view = getUint32Memory(); - view[exnptr / 4] = 1; - view[exnptr / 4 + 1] = addHeapObject(e); - - } -}; - __exports.__wbg_new_87f236e6bb64256a = function() { return addHeapObject(new Object()); }; @@ -387,38 +362,6 @@ __exports.__wbg_set_345cb607cede9a14 = function(arg0, arg1, arg2, exnptr) { } }; -__exports.__wbg_new_9cc98abd8c2c45e2 = function(arg0, arg1) { - let cbarg0 = function(arg0, arg1) { - let a = this.a; - this.a = 0; - try { - return this.f(a, this.b, addHeapObject(arg0), addHeapObject(arg1)); - - } finally { - this.a = a; - - } - - }; - cbarg0.f = wasm.__wbg_function_table.get(97); - cbarg0.a = arg0; - cbarg0.b = arg1; - try { - return addHeapObject(new Promise(cbarg0.bind(cbarg0))); - } finally { - cbarg0.a = cbarg0.b = 0; - - } -}; - -__exports.__wbg_resolve_71812a6f3480e88d = function(arg0) { - return addHeapObject(Promise.resolve(getObject(arg0))); -}; - -__exports.__wbg_then_8cbc8dd8be3dea68 = function(arg0, arg1) { - return addHeapObject(getObject(arg0).then(getObject(arg1))); -}; - __exports.__wbg_then_d87f182e0a9564c7 = function(arg0, arg1, arg2) { return addHeapObject(getObject(arg0).then(getObject(arg1), getObject(arg2))); }; @@ -498,19 +441,9 @@ __exports.__wbindgen_cb_drop = function(i) { return 0; }; -__exports.__wbindgen_json_parse = function(ptr, len) { - return addHeapObject(JSON.parse(getStringFromWasm(ptr, len))); -}; - -__exports.__wbindgen_json_serialize = function(idx, ptrptr) { - const ptr = passStringToWasm(JSON.stringify(getObject(idx))); - getUint32Memory()[ptrptr / 4] = ptr; - return WASM_VECTOR_LEN; -}; - -__exports.__wbindgen_closure_wrapper812 = function(a, b, _ignored) { - const f = wasm.__wbg_function_table.get(16); - const d = wasm.__wbg_function_table.get(17); +__exports.__wbindgen_closure_wrapper670 = function(a, b, _ignored) { + const f = wasm.__wbg_function_table.get(7); + const d = wasm.__wbg_function_table.get(8); const cb = function(arg0) { this.cnt++; let a = this.a; @@ -532,9 +465,9 @@ __exports.__wbindgen_closure_wrapper812 = function(a, b, _ignored) { return addHeapObject(real); }; -__exports.__wbindgen_closure_wrapper1489 = function(a, b, _ignored) { - const f = wasm.__wbg_function_table.get(59); - const d = wasm.__wbg_function_table.get(60); +__exports.__wbindgen_closure_wrapper1156 = function(a, b, _ignored) { + const f = wasm.__wbg_function_table.get(40); + const d = wasm.__wbg_function_table.get(41); const cb = function(arg0) { this.cnt++; let a = this.a; diff --git a/examples/server_interaction/pkg/server_interaction_bg.wasm b/examples/server_interaction/pkg/server_interaction_bg.wasm index ca0983f52..abac152ce 100644 Binary files a/examples/server_interaction/pkg/server_interaction_bg.wasm and b/examples/server_interaction/pkg/server_interaction_bg.wasm differ diff --git a/examples/server_interaction/server.py b/examples/server_interaction/serve.py similarity index 100% rename from examples/server_interaction/server.py rename to examples/server_interaction/serve.py diff --git a/examples/server_interaction/src/lib.rs b/examples/server_interaction/src/lib.rs index 76d7336cf..c098a6fbd 100644 --- a/examples/server_interaction/src/lib.rs +++ b/examples/server_interaction/src/lib.rs @@ -35,12 +35,10 @@ struct Model { // Setup a default here, for initialization later. impl Default for Model { fn default() -> Self { - let url = "https://seed-example.herokuapp.com/data"; +// let url = "https://seed-example.herokuapp.com/data"; + let url = "https://api.github.com/repos/rust-lang/rust/branches/master"; let mut headers = HashMap::new(); - headers.insert("Content-type", "application/json"); - // todo don't do this in production -// headers.insert("Access-Control-Allow-Origin", "*"); - headers.insert("Access-Control-Allow-Origin", "https://seed-example.herokuapp.com"); +// headers.insert("Content-Type", "application/json"); let data = seed::fetch::fetch(seed::fetch::Method::Get, url, None, Some(headers)); @@ -73,7 +71,7 @@ enum Msg { Replace(Data), } -fn update(msg: Msg, model: Model) -> Model { +fn update(history: &mut History, msg: Msg, model: Model) -> Model { match msg { Msg::Replace(data) => Model {data}, } @@ -88,5 +86,5 @@ fn view(model: Model) -> El { #[wasm_bindgen] pub fn render() { - seed::run(Model::default(), update, view, "main"); + seed::run(Model::default(), update, view, "main", None); } \ No newline at end of file diff --git a/examples/todomvc/server.py b/examples/todomvc/serve.py similarity index 100% rename from examples/todomvc/server.py rename to examples/todomvc/serve.py diff --git a/examples/todomvc/src/lib.rs b/examples/todomvc/src/lib.rs index d8174a418..cefc3f6d3 100644 --- a/examples/todomvc/src/lib.rs +++ b/examples/todomvc/src/lib.rs @@ -110,7 +110,7 @@ enum Msg { EditKeyDown(usize, u32), // item position, keycode } -fn update(msg: Msg, model: Model) -> Model { +fn update(history: &History, msg: Msg, model: Model) -> Model { // We take a verbose immutable-design/functional approach in this example. // Alternatively, you could re-declare model as mutable at the top, and mutate // what we need in each match leg. See the Update section of the guide for details. @@ -323,5 +323,5 @@ fn todo_app(model: Model) -> El { #[wasm_bindgen] pub fn render() { - seed::run(Model::default(), update, todo_app, "main"); + seed::run(Model::default(), update, todo_app, "main", None); } \ No newline at end of file diff --git a/src/dom_types.rs b/src/dom_types.rs index 4ffdfa2b9..12d2f0520 100644 --- a/src/dom_types.rs +++ b/src/dom_types.rs @@ -10,7 +10,7 @@ use wasm_bindgen::{prelude::*, JsCast}; use crate::vdom::Mailbox; // todo temp -use regex::Regex; +//use regex::Regex; // todo cleanup enums vs &strs for restricting events/styles/attrs to @@ -21,8 +21,6 @@ use regex::Regex; // todo and reacttack when you need = 'static. -// TODO REATTACK when you need box - //pub trait UpdateListener { // // T is the type of thing we're updating; eg attrs, style, events etc. // fn update_l(self, el: &mut T); @@ -302,7 +300,8 @@ impl Attrs { Self { vals: HashMap::new() } } - pub fn as_str(&self) -> String { + /// Create an HTML-compatible string representation + pub fn to_string(&self) -> String { let mut result = String::new(); for (key, val) in &self.vals { result += &format!(" {k}=\"{v}\"", k=key, v=val); @@ -310,9 +309,19 @@ impl Attrs { result } + /// Add a new key, value pair pub fn add(&mut self, key: &str, val: &str) { self.vals.insert(key.to_string(), val.to_string()); } + + /// Combine with another Attrs; if there's a conflict, use the other one. + pub fn merge(&self, other: &Self) -> Self { + let mut result = self.clone(); + for (key, val) in &other.vals { + result.vals.insert(key.clone(), val.clone()); + } + result + } } @@ -345,7 +354,7 @@ impl Style { /// Output style as a string, as would be set in the DOM as the attribute value /// for 'style'. Eg: "display: flex; font-size: 1.5em" - pub fn as_str(&self) -> String { + pub fn to_string(&self) -> String { let mut result = String::new(); if self.vals.keys().len() > 0 { for (key, val) in &self.vals { @@ -359,6 +368,15 @@ impl Style { pub fn add(&mut self, key: &str, val: &str) { self.vals.insert(key.to_string(), val.to_string()); } + + /// Combine with another Style; if there's a conflict, use the other one. + pub fn merge(&self, other: &Self) -> Self { + let mut result = self.clone(); + for (key, val) in &other.vals { + result.vals.insert(key.clone(), val.clone()); + } + result + } } /// Similar to tag population. @@ -531,21 +549,21 @@ pub struct El { // todo temp? // pub key: Option, - pub markdown: bool, + pub raw_html: bool, } impl El { pub fn new(tag: Tag, attrs: Attrs, style: Style, listeners: Vec>, text: &str, children: Vec>) -> Self { Self {tag, attrs, style, text: Some(text.into()), children, - el_ws: None, listeners, id: None, nest_level: None, markdown: false} + el_ws: None, listeners, id: None, nest_level: None, raw_html: false} } /// Create an empty element, specifying only the tag pub fn empty(tag: Tag) -> Self { Self {tag, attrs: Attrs::empty(), style: Style::empty(), text: None, children: Vec::new(), el_ws: None, - listeners: Vec::new(), id: None, nest_level: None, markdown: false} + listeners: Vec::new(), id: None, nest_level: None, raw_html: false} } /// Create an element that will display markdown from the text you pass to it, as HTML @@ -554,20 +572,40 @@ impl El { let mut html_text = String::new(); pulldown_cmark::html::push_html(&mut html_text, parser); // -// let ss = SyntaxSet::load_defaults_newlines(); -// let sr = SyntaxReference::load_defaults_newlines(); -// let ts = ThemeSet::load_defaults(); + // todo: Syntect crate is currently bugged with wasm target. +// let ss = syntect::parsing::SyntaxSet::load_defaults_newlines(); +// let sr = ss.find_syntax_by_token("rust").unwrap(); +// let ts = syntect::highlighting::Theme::default(); // -// let re = Regex::new(r"(.*)").expect("Error creating Regex"); +// let replacer = |match_group| { +// syntect::html::highlighted_html_for_string( +// match_group, &ss, sr, &ts +// ) +// }; + +// let replacer = |match_grp: ®ex::Captures| match_grp.name("code").unwrap().as_str(); + +// let re = Regex::new(r"(?P.*?)").expect("Error creating Regex"); +// re.replace_all(&html_text, replacer); + +// crate::log(&html_text); // // let highlighted_html = syntect::html::highlighted_html_for_string(text, ss, sr, ts); let mut result = Self::empty(Tag::Span); - result.markdown = true; + result.raw_html = true; result.text = Some(html_text); result } + /// Create an element that will display raw HTML + pub fn from_html(html: &str) -> Self { + let mut result = Self::empty(Tag::Span); + result.raw_html = true; + result.text = Some(html.into()); + result + } + /// Add a new child to the element pub fn add_child(&mut self, element: El) { self.children.push(element); @@ -593,8 +631,8 @@ impl El { fn _html(&self) -> String { let text = self.text.clone().unwrap_or_default(); - let opening = String::from("<") + self.tag.as_str() + &self.attrs.as_str() + - " style=\"" + &self.style.as_str() + ">\n"; + let opening = String::from("<") + self.tag.as_str() + &self.attrs.to_string() + + " style=\"" + &self.style.to_string() + ">\n"; let inner = self.children.iter().fold(String::new(), |result, child| result + &child._html()); @@ -616,7 +654,7 @@ impl El { id: None, nest_level: None, el_ws: self.el_ws.clone(), - markdown: self.markdown, + raw_html: self.raw_html, } } @@ -647,7 +685,7 @@ impl Clone for El { nest_level: self.nest_level, el_ws: self.el_ws.clone(), listeners: Vec::new(), - markdown: self.markdown, + raw_html: self.raw_html, } } } diff --git a/src/fetch.rs b/src/fetch.rs index e87a96ae6..099c9bfab 100644 --- a/src/fetch.rs +++ b/src/fetch.rs @@ -3,6 +3,7 @@ //! See https://rustwasm.github.io/wasm-bindgen/reference/js-promises-and-rust-futures.html //! https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Request.html //! https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/ +//! https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Response.html //#[macro_use] //extern crate serde_derive; @@ -16,7 +17,7 @@ use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; //use wasm_bindgen_futures::future_to_promise; use wasm_bindgen_futures; -use web_sys::{Request, RequestInit, RequestMode, Response}; +use web_sys::{Response}; // todo debuggins @@ -62,16 +63,18 @@ pub fn fetch(method: Method, url: &str, payload: Option, // headers: Option>, // cl: impl FnMut(wasm_bindgen::JsValue) -> future::FutureResult) -> js_sys::Promise { // headers: Option>, cl: impl FnMut(wasm_bindgen::JsValue)) -> js_sys::Promise - headers: Option>) -> js_sys::Promise +// headers: Option>) -> js_sys::Promise + headers: Option>) // headers: Option>) -> wasm_bindgen_futures::JsFuture { // headers: Option>) -> wasm_bindgen_futures::JsFuture { where S: BuildHasher { - let mut opts = RequestInit::new(); + let mut opts = web_sys::RequestInit::new(); opts.method(method.as_str()); - opts.mode(RequestMode::Cors); - - let request = Request::new_with_str_and_init(url, &opts).unwrap(); + // https://rustwasm.github.io/wasm-bindgen/api/web_sys/enum.RequestMode.html + // We get a CORS error without this setting. + opts.mode(web_sys::RequestMode::NoCors); + let request = web_sys::Request::new_with_str_and_init(url, &opts).unwrap(); // Set headers: // https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Headers.html @@ -82,43 +85,52 @@ pub fn fetch(method: Method, url: &str, payload: Option, req_headers.set(&name, &value).unwrap(); } } -// req_headers.unwrap(); let window = web_sys::window().unwrap(); let request_promise = window.fetch_with_request(&request); + + let future = wasm_bindgen_futures::JsFuture::from(request_promise) .and_then(|resp_value| { // `resp_value` is a `Response` object. assert!(resp_value.is_instance_of::()); let resp: Response = resp_value.dyn_into().unwrap(); - resp.json() - }) - .and_then(|json_value: js_sys::Promise| { - // Convert this other `Promise` into a rust `Future`. - wasm_bindgen_futures::JsFuture::from(json_value) - }) - -// .and_then(cl); + crate::log("RESP"); - .and_then(|json| { - // Use serde to parse the JSON into a struct. - let data: Data = json.into_serde().unwrap(); +// crate::log(resp.status()); +// let text = resp.text().unwrap(); +// let text2 = text.as_string().unwrap(); +// crate::log(text2); + resp.json() + }); +// .and_then(|json_value: js_sys::Promise| { +// // Convert this other `Promise` into a rust `Future`. +// wasm_bindgen_futures::JsFuture::from(json_value) +// }) - crate::log(data.text.clone()); - +// .and_then(cl); - // Send the `Branch` struct back to JS as an `Object`. - future::ok(JsValue::from_serde(&data).unwrap()) - }); +// .and_then(|json| { +// // Use serde to parse the JSON into a struct. +// let data: Data = json.into_serde().unwrap(); +// +// +// crate::log(data.text.clone()); +// +// +// +// // Send the `Branch` struct back to JS as an `Object`. +// future::ok(JsValue::from_serde(&data).unwrap()) +// }); // Convert this Rust `Future` back into a JS `Promise`. // future - wasm_bindgen_futures::future_to_promise(future) +// wasm_bindgen_futures::future_to_promise(future) } diff --git a/src/lib.rs b/src/lib.rs index 0d28a58d0..ebe24263c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ #![allow(unused_macros)] +use std::collections::HashMap; use std::panic; use wasm_bindgen::JsCast; @@ -50,6 +51,8 @@ pub fn to_select(target: &web_sys::EventTarget ) -> &web_sys::HtmlSelectElement target.dyn_ref::().expect("Unable to cast as a select element") } +/// Convert a web_sys::Event to a web_sys::KeyboardEvent. Useful for extracting +/// info like which key has been pressed, which is not available with normal Events. pub fn to_kbevent(event: &web_sys::Event ) -> &web_sys::KeyboardEvent { // This might be more appropriate for web_sys::bridge, but I'd // like to expose it without making websys_bridge public. @@ -57,8 +60,8 @@ pub fn to_kbevent(event: &web_sys::Event ) -> &web_sys::KeyboardEvent { } /// The entry point for the app -pub fn run(model: Mdl, update: fn(Ms, Mdl) -> Mdl, - view: fn(Mdl) -> dom_types::El, mount_point_id: &str) +pub fn run(model: Mdl, update: fn(&mut vdom::History, Ms, Mdl) -> Mdl, + view: fn(Mdl) -> dom_types::El, mount_point_id: &str, route_map: Option>) where Ms: Clone + Sized + 'static, Mdl: Clone + Sized + 'static { let app = vdom::App::new(model.clone(), update, view, mount_point_id); @@ -74,6 +77,22 @@ pub fn run(model: Mdl, update: fn(Ms, Mdl) -> Mdl, app.data.main_el_vdom.replace(topel_vdom); + // If a route map is inlcluded, update the state on page load, based + // on the starting URL. Must be set up on the server as well. + if let Some(r_map) = route_map { + // todo switch back to path name. + let window = web_sys::window().expect("no global `window` exists"); + let path_name = window.location().href().expect("Can't find pathname"); +// let path_name = window.location().pathname().expect("Can't find pathname"); + for (route, message) in r_map.into_iter() { + if route == &path_name { + app.update_dom(message); + break; + } + } + } + + // Allows panic messages to output to the browser console.error. panic::set_hook(Box::new(console_error_panic_hook::hook)); } @@ -96,7 +115,9 @@ pub fn log(text: S) { /// Introduce El into the global namespace for convenience (It will be repeated /// often in the output type of components), and UpdateEl, which is required -/// for element-creation macros. +/// for element-creation macros, input event constructors, and the History struct. pub mod prelude { pub use crate::dom_types::{El, UpdateEl, simple_ev, input_ev, keyboard_ev, raw_ev}; + pub use crate::vdom::History; + pub use std::collections::HashMap; } diff --git a/src/router.rs b/src/router.rs new file mode 100644 index 000000000..ecbcd80ba --- /dev/null +++ b/src/router.rs @@ -0,0 +1,11 @@ +//! https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.History.html +//! https://developer.mozilla.org/en-US/docs/Web/API/History +//! https://stackoverflow.com/questions/26475746/how-to-manage-browser-back-and-forward-buttons-when-creating-a-javascript-wi +//! https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate +//! https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate +//! https://developer.mozilla.org/en-US/docs/Web/Events/popstate +//! https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.PopStateEvent.html +//! https://developer.mozilla.org/en-US/docs/Web/API/PopStateEvent + +use wasm_bindgen::prelude::*; + diff --git a/src/vdom.rs b/src/vdom.rs index f87118a27..6d5e24e99 100644 --- a/src/vdom.rs +++ b/src/vdom.rs @@ -1,8 +1,12 @@ use std::{cell::{RefCell}, rc::Rc}; +use std::collections::HashMap; use crate::dom_types; use crate::dom_types::El; use crate::websys_bridge; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + // todo: Get rid of the clone assiated with MS everywhere if you can! @@ -33,6 +37,29 @@ impl Clone for Mailbox { } } +/// A wrapper for web_sys::History, with helper methods to simplify syntax. +#[derive(Clone)] +pub struct History { + history: web_sys::History, + stored: HashMap, + stored_m: HashMap, +} + +impl History { + pub fn push(&mut self, data: &str, title: &str, message: Ms, model: Mdl) { +// crate::log(data); + crate::log("TEST"); + self.history.push_state(&JsValue::from_str(data), title); + + // todo sloppy +// let window = + + + self.stored.insert(data.into(), model); + self.stored_m.insert(data.into(), message); + } +} + // todo: Examine what needs to be ref cells, rcs etc /// Used as part of an interior-mutability pattern, ie Rc> @@ -41,9 +68,12 @@ pub struct Data { pub mount_point: web_sys::Element, // Model is in a RefCell here so we can replace it in self.update_dom(). pub model: RefCell, - update: fn(Ms, Mdl) -> Mdl, + pub update: fn(&mut History, Ms, Mdl) -> Mdl, pub view: fn(Mdl) -> El, pub main_el_vdom: RefCell>, + + pub history: RefCell>, + pub pop_listeners: Vec> } pub struct App { @@ -53,12 +83,50 @@ pub struct App { /// We use a struct instead of series of functions, in order to avoid passing /// repetative sequences of parameters. impl App { - pub fn new(model: Mdl, update: fn(Ms, Mdl) -> Mdl, + pub fn new(model: Mdl, update: fn(&mut History, Ms, Mdl) -> Mdl, view: fn(Mdl) -> El, parent_div_id: &str) -> Self { let window = web_sys::window().expect("no global `window` exists"); let document = window.document().expect("should have a document on window"); + let history: web_sys::History = window.history().expect("aw shucks"); + let history_wrapper = History{history, stored: HashMap::new(), stored_m: HashMap::new(),}; + +// let mut h2 = history_wrapper.clone(); +// // todo temp router +// let history_closure = Closure::wrap( +// +// Box::new(move |event: web_sys::Event| { +// let event = event.dyn_into::() +// .expect("Unable to cast as a PopStateEvent"); +// crate::log("POP POP!"); +// +//// crate::log(&document.location().expect("Can't find location")); +// if let Some(state) = event.state().as_string() { +// crate::log(&state); +// +// let message = h2.stored_m.get(&state).expect("Can't find msg for pop"); +// let mdl = h2.stored.get(&state).expect("Can't find mdl for pop"); +// update(&mut h2.clone(), message.clone(), mdl.clone()); +// +// +// } +// +// }) +// as Box, +// ); +// +// (window.as_ref() as &web_sys::EventTarget) +// .add_event_listener_with_callback("popstate", history_closure.as_ref().unchecked_ref()) +// .expect("Problem adding popstate listener"); +// +// history_closure.forget(); + + + + + + let mount_point = document.get_element_by_id(parent_div_id).unwrap(); @@ -71,6 +139,9 @@ impl App { view, main_el_vdom: RefCell::new(El::empty(dom_types::Tag::Div)), + + history: RefCell::new(history_wrapper), + pop_listeners: Vec::new(), }) } } @@ -86,14 +157,15 @@ impl App { /// We re-render the virtual DOM on every change, but (attempt to) only change /// the actual DOM, via web_sys, when we need. /// The model storred in inner is the old model; updated_model is a newly-calculated one. - fn update_dom(&self, message: Ms) { + pub fn update_dom(&self, message: Ms) { // data.model is the old model; pass it to the update function created in the app, // which outputs an updated model. // We clone the model before running update, and again before passing it // to the view func, instead of using refs, to improve API syntax. // This approach may have performance impacts of unknown magnitude. let model_to_update = self.data.model.borrow().clone(); - let updated_model = (self.data.update)(message, model_to_update); + let mut h = self.data.history.borrow().clone(); + let updated_model = (self.data.update)(&mut h, message, model_to_update); // Create a new vdom: The top element, and all its children. Does not yet // have ids, nest levels, or associated web_sys elements. @@ -132,6 +204,51 @@ impl App { // Now that we've re-rendered, replace our stored El with the new one; // it will be used as the old El next (. self.data.main_el_vdom.replace(topel_new_vdom); + + + + +// let window = web_sys::window().expect("no global `window` exists"); // todo don't recreate this every time +// +// let mut h2 = self.data.history.borrow_mut().clone(); +// let history_closure = Closure::wrap( +// Box::new(move |event: web_sys::Event| { +// let event = event.dyn_into::() +// .expect("Unable to cast as a PopStateEvent"); +// crate::log("POP POP!"); +// +//// crate::log(&document.location().expect("Can't find location")); +// if let Some(state) = event.state().as_string() { +// crate::log(&state); +//// +// let message = h2.stored_m.get(&state).expect("Can't find msg for pop"); +// let mdl = h2.stored.get(&state).expect("Can't find mdl for pop"); +//// let updated_model = (self.data.update)(&mut h2.clone(), message.clone(), mdl.clone()); +// +//// self.data.model.replace(mdl.clone()); +//// +//// +// } +// +// }) +// as Box, +// ); + +// (window.as_ref() as &web_sys::EventTarget) +// .add_event_listener_with_callback("popstate", history_closure.as_ref().unchecked_ref()) +// .expect("Problem adding popstate listener"); +// +// history_closure.forget(); + + + + + + + + + + } pub fn mailbox(&self) -> Mailbox { diff --git a/src/websys_bridge.rs b/src/websys_bridge.rs index 52c348939..e769d631d 100644 --- a/src/websys_bridge.rs +++ b/src/websys_bridge.rs @@ -44,26 +44,20 @@ pub fn make_websys_el(el_vdom: &mut dom_types::El, document: &web // Style is just an attribute in the actual Dom, but is handled specially in our vdom; // merge the different parts of style here. if el_vdom.style.vals.keys().len() > 0 { - el_ws.set_attribute("style", &el_vdom.style.as_str()).expect("Problem setting style"); + el_ws.set_attribute("style", &el_vdom.style.to_string()).expect("Problem setting style"); } // We store text as Option, but set_text_content uses Option<&str>. // A naive match Some(t) => Some(&t) does not work. // See https://stackoverflow.com/questions/31233938/converting-from-optionstring-to-optionstr let text = el_vdom.text.as_ref().map(String::as_ref); - if el_vdom.markdown { + if el_vdom.raw_html { el_ws.set_inner_html(text.unwrap()) } else { el_ws.set_text_content(text); } - // Don't attach listeners here: It'll cause a conflict when you try to - // attach a second time. ? - // todo delete these lines once events are sorted -// for listener in &mut el_vdom.listeners { -// listener.attach(&el_ws, mailbox.clone()); -// } - + // Don't attach listeners here, el_ws } @@ -134,7 +128,7 @@ pub fn patch_el_details(old: &mut dom_types::El, new: &mut dom_ty // Patch style. if old.style != new.style { // We can't patch each part of style; rewrite the whole attribute. - old_el_ws.set_attribute("style", &new.style.as_str()) + old_el_ws.set_attribute("style", &new.style.to_string()) .expect("Setting style"); } @@ -149,7 +143,7 @@ pub fn patch_el_details(old: &mut dom_types::El, new: &mut dom_ty let text = new.text.clone().unwrap_or_default(); - if new.markdown { + if new.raw_html { old_el_ws.set_inner_html(&text) } else {