Coder Social home page Coder Social logo

maciejhirsz / kobold Goto Github PK

View Code? Open in Web Editor NEW
385.0 5.0 7.0 927 KB

Easy declarative web interfaces.

Home Page: https://docs.rs/kobold/

License: Mozilla Public License 2.0

Rust 98.98% JavaScript 0.86% Shell 0.16%
rust declarative ui wasm wasm-bindgen

kobold's Introduction

Kobold

Kobold logo

Join Discord Test Docs Crates.io MPL-2.0

Easy declarative web interfaces.

Key features:

  • Declarative view! macro that uses HTML-esque syntax complete with optional closing tags.
  • Functional components with optional parameters.
  • State management and event handling.
  • High performance and consistently the lowest Wasm footprint in the Rust ecosystem.

Zero-Cost Static HTML

The view! macro produces opaque impl View types that by default do no allocations. All static DOM elements compile to inline JavaScript code that constructs them. Expressions are injected into the constructed DOM on first render. Kobold keeps track of the DOM node references for these expressions.

Since the exact types the expressions evaluate to are known to the Rust compiler, update calls can diff them by value (or pointer) and surgically update the DOM should they change. Changing a string or an integer only updates the exact Text node that string or integer was rendered to.

If the view! macro invocation contains DOM elements with no expressions, the constructed View type will be zero-sized, and its View::update method will be empty, making updates of static DOM literally zero-cost.

Hello World!

Components in Kobold are created by annotating a render function with a #[component] attribute.

use kobold::prelude::*;

#[component]
fn hello(name: &str) -> impl View + '_ {
    view! {
        <h1>"Hello "{ name }"!"</h1>
    }
}

fn main() {
    kobold::start(view! {
        <!hello name="Kobold">
    });
}

The component function must return a type that implements the View trait. Since the view! macro produces transient locally defined types the best approach here is to always use the opaque impl View return type.

Everything here is statically typed and the macro doesn't delete any information when manipulating the token stream, so the Rust compiler can tell you when you've made a mistake:

error[E0560]: struct `Hello` has no field named `nam`
  --> examples/hello_world/src/main.rs:12:16
   |
12 |         <!hello nam="Kobold">
   |                 ^^^ help: there is a method with a similar name: `name`

You can even use rust-analyzer to refactor component or field names, and it will change the invocations inside the macros for you.

State management

The stateful function can be used to create views that own and manipulate their state:

use kobold::prelude::*;

#[component]
fn counter(init: u32) -> impl View {
    stateful(init, |count| {
        bind! { count:
            // Create an event handler with access to `&mut u32`
            let onclick = move |_event| *count += 1;
        }

        view! {
            <p>
                "You clicked the "
                // `{onclick}` here is shorthand for `onclick={onclick}`
                <button {onclick}>"Button"</button>
                " "{ count }" times."
            </p>
        }
    })
}

fn main() {
    kobold::start(view! {
        <!counter init={0}>
    });
}

The stateful function takes two parameters:

  • State constructor that implements the IntoState trait. Kobold comes with default implementations for most primitive types, so we can use u32 here.
  • The anonymous render closure that uses the constructed state, in our case its argument is &Hook<u32>.

The Hook here is a smart pointer to the state itself that allows non-mutable access to the state. The bind! macro can be invoked for any Hook to create closures with &mut references to the underlying state.

For more details visit the stateful module documentation.

Optional parameters

Use #[component(<param>?)] syntax to set a component parameter as default:

// `code` will default to `200` if omitted
#[component(code?: 200)]
fn status(code: u32) -> impl View {
    view! {
        <p> "Status code was "{ code }
    }
}

view! {
    // Status code was 200
    <!status>
    // Status code was 404
    <!status code={404}>
}

For more details visit the #[component] macro documentation.

Conditional Rendering

Because the view! macro produces unique transient types, if and match expressions that invoke the macro will naturally fail to compile.

Using the auto_branch flag on the #[component] attribute Kobold will scan the body of of your component render function, and make all view! macro invocations inside an if or match expression, and wrap them in an enum making them the same type:

#[component(auto_branch)]
fn conditional(illuminatus: bool) -> impl View {
    if illuminatus {
        view! { <p> "It was the year when they finally immanentized the Eschaton." }
    } else {
        view! { <blockquote> "It was love at first sight." }
    }
}

For more details visit the branching module documentation.

Lists and Iterators

To render an iterator use the for keyword:

use kobold::prelude::*;

#[component]
fn iterate_numbers(count: u32) -> impl View {
    view! {
        <ul>
        {
            for (1..=count).map(|n| view! { <li> "Item #"{n} })
        }
    }
}

On updates the iterator is consumed once and all items are diffed with the previous version. No allocations are made by Kobold when updating such a list, unless the rendered list needs to grow past its original capacity.

For more information about keywords visit the keywords module documentation.

Borrowed Values

View types are truly transient and only need to live for the duration of the initial render, or for the duration of the subsequent update. This means that you can easily and cheaply render borrowed state without unnecessary clones:

#[component]
fn users<'a>(names: &'a [&'a str]) -> impl View + 'a {
    view! {
        <ul>
        {
            for names.iter().map(|name| view! { <li> { name } })
        }
    }
}

Components with Children

If you wish to capture children from parent view! invocation, simply change #[component] to #[component(children)]:

use kobold::prelude::*;

#[component(children)]
fn header(children: impl View) -> impl View {
    view! {
        <header><h1>{ children }</h1></header>
    }
}

fn main() {
    kobold::start(view! {
        <!header>"Hello Kobold"</!header>
    });
}

You can change the name of the parameter used and even set it to a concrete:

use kobold::prelude::*;

// Capture children into the argument `n`
#[component(children: n)]
fn add_ten(n: i32) -> i32 {
    // integers implement `View` so they can be passed by value
    n + 10
}

fn main() {
    kobold::start(view! {
        <p>
            "Meaning of life is "
            <!add_ten>{ 32 }</!add_ten>
        </p>
    });
}

More Examples

To run Kobold you'll need to install trunk:

cargo install --locked trunk

You might also need to add the Wasm target to Rust:

rustup target add wasm32-unknown-unknown

Then just run an example:

## Go to an example
cd examples/todomvc

## Run with trunk
trunk serve

Acknowledgements

License

Kobold is free software, and is released under the terms of the Mozilla Public License version 2.0. See LICENSE.

kobold's People

Contributors

cryptjar avatar fatfingers23 avatar ltfschoen avatar maciejhirsz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

kobold's Issues

Update `bind!` documentation to show example of equivalent techniques using `state`

In the docs here, it shows how to use bind!

In the invoice example, I wanted an editable input field, and I wanted to be able to load from a file (like your csv example), and populate the editable fields value with a value from the file, and still have that input field being editable (like your csv example).

But I there were different techniques being used in the examples. After I got it to work I found that the following:

#[component]
fn MyView<'a>(state: &'a Hook<State>) -> impl View + 'a {
    let ondblclick = state.bind(move |s, _| s.entry.editing = true);
    view! {
        <div {ondblclick} >click to edit</div>
    }
}

is equivalent to this:

#[component]
fn MyView<'a>(state: &'a Hook<State>) -> impl View + 'a {
    bind! { state:
        let ondblclick = move |_| state.editing = true;
    }
    view! {
        <div {ondblclick} >click to edit</div>
    }
}

So in the examples the following:

bind! { state:
    let onchange = move |e: Event<InputElement>| {
        state.table.rows[0][0] = Text::Owned(e.target().value().into());
        state.editing = false;
    };
}

is equivalent to this:

let onchange = state.bind(move |state, e: Event<InputElement>| {
    state.table.rows[0][0] = Text::Owned(e.target().value().into());
    state.editing = false;
});

If I have another look at the docs it does show how it's desugered, but it wasn't immediately obvious to me.
Could it be beneficial to update the bind docs to show an example with both options using the state?

ERR_CONNECTION_REFUSED when running on remote server

If I run this on my local machine which is macOS by following the README.md instructions by installing Rust and running:

git clone https://github.com/maciejhirsz/kobold
cd kobold
cargo install --locked trunk
rustup target add wasm32-unknown-unknown
cd examples/hello_world
trunk serve

Then it outputs server listening at http://127.0.0.1:8080
And when I go to http://127.0.0.1:8080 in my web browser it displays: Hello Kobold!

However, if I try to run it on a remote machine.
The output said server listening at http://127.0.0.1:8080
But when I went to view the website in my web browser by going to http://127.0.0.1:8080, but it returned error ERR_CONNECTION_REFUSED.
So I instead I found the IP address of the remote server I was running Kobold on, which was <REMOTE_IP_ADDRESS>, and went to http://<REMOTE_IP_ADDRESS>:8080, but it also returned error ERR_CONNECTION_REFUSED.
So I went through the process of checking if I had a firewall blocking port 8080, but I didn't.
So then I tried running a different web server on port 8080 by running npx -y http-server in the examples/hello_world/dist/ directory, which output:

Starting up http-server, serving ./

http-server version: 14.1.1

http-server settings: 
CORS: disabled
Cache: 3600 seconds
Connection Timeout: 120 seconds
Directory Listings: visible
AutoIndex: visible
Serve GZIP Files: false
Serve Brotli Files: false
Default File Extension: none

Available on:
  http://127.0.0.1:8080
  http://<REMOTE_IP_ADDRESS>:8080

And when I opened http://<REMOTE_IP_ADDRESS>:8080 in my web browser it displayed: Hello Kobold!
But I don't want to have to run a JS server to access Kobold, so then I thought it may require a custom Trunk server configuration.
So I ran trunk serve --help, and found there was an --address option
So I ran trunk serve --address <REMOTE_IP_ADDRESS> and then when I opened http://<REMOTE_IP_ADDRESS>:8080 in my web browser it displayed: Hello Kobold!
And when I ran curl -v http://<REMOTE_IP_ADDRESS>:8080 from a different browser tab, it responded with the HTML.
However when I ran curl -v http://127.0.0.1:8080 it refused to connect.

On my local machine if I ran trunk serve --open it automatically opened http://127.0.0.1:8080 in my browser,
but if I ran trunk serve --address <REMOTE_IP_ADDRESS> --open on my remote machine via ssh, whilst it ran the server, it also output: ERROR error opening browser error=Custom { kind: Other, error: "gio: http://<REMOTE_IP_ADDRESS>:8080/: Operation not supported (exit status: 2)" }

Suggest updating the README.md instructions to mention how to handle remote machine usage.
Also need to figure out how to run Trunk so that it's served on both 127.0.0.1 (localhost) + remote IP at the same time

Unable to render using nested if else statement

I can get it to render if i do a single level of if, else if, and else statements
Screen Shot 2023-04-03 at 12 29 38 pm

But if i use the code that has been commented out instead, which seems to be the same code, but so i have just nested if else statements, then it gives the following error. is the idea that i should proceed trying to figure out how to use Box::new if i want to use nested if else statements?
Screen Shot 2023-04-03 at 12 32 26 pm

Unsure if its possible to use conditional statement within a view! or if its possible to branch within a loop

I've created this for loop here, code snippet:

    for index in 0..data.len() {
        if state.entry[index].editing == true {
            return Branch2::A(view! {
                <DetailEditing {index} {data} {placeholders_file} {state} />
            });
        }
        Branch2::B(view! {
            <DetailView {index} {data} {state} />
        })
    }

It iterates through the number of properties in data variable.
for context, data is a vector of tuples Vec<(String, String)>, where each tuple contains the property key and property value that was converted from the Details struct here from default state values or from loading the details.csv file (also see unit test of how that was done here)

Then I want to do the conditional branching from the docs. So to make the code clearer I've created a:
*DetailEditing component (which is to only render if state.entry[index].editing == true

  • DetailView component (which renders they're not editing the input field with that index)

The problems that I'm facing is:

  1. It gives error:
click to show error
error[E0308]: mismatched types
   --> examples/invoice/src/main.rs:389:9
    |
311 |   fn DetailView(index: usize, data: Vec<(String, String)>, state: &Hook<State>) -> impl View + '_ {
    |                                                                                    -------------- the found opaque type
...
389 | /         Branch2::B(view! {
390 | |             <DetailView {index} {data} {state} />
391 | |         })
    | |__________^ expected `()`, found enum `Branch2`
    |
    = note: expected unit type `()`
                    found enum `Branch2<_, impl kobold::View + '_>`
help: consider using a semicolon here
    |
391 |         });
    |           +
help: you might have meant to return this value
    |
389 ~         return Branch2::B(view! {
390 |             <DetailView {index} {data} {state} />
391 ~         });
    |

error[E0308]: mismatched types
   --> examples/invoice/src/main.rs:383:5
    |
255 |   fn DetailEditing(index: usize, data: Vec<(String, String)>, placeholders_file: Vec<String>, state: &Hook<State>) -> impl View + '_ {
    |                                                                                                                       -------------- the expected opaque type
...
328 |   fn EntryView<'a>(state: &'a Hook<State>) -> impl View + 'a {
    |                                               -------------- expected `Branch2<impl kobold::View + '_, _>` because of return type
...
383 | /     for index in 0..data.len() {
384 | |         if state.entry[index].editing == true {
385 | |             return Branch2::A(view! {
386 | |                 <DetailEditing {index} {data} {placeholders_file} {state} />
...   |
391 | |         })
392 | |     }
    | |_____^ expected enum `Branch2`, found `()`
    |
    = note:   expected enum `Branch2<impl kobold::View + '_, _>`
            found unit type `()`
help: try wrapping the expression in `kobold::branching::Branch2::B`
    |
383 ~     kobold::branching::Branch2::B(for index in 0..data.len() {
384 |         if state.entry[index].editing == true {
  ...
391 |         })
392 ~     })

data.len() equals 10, since there are 10 columns of data being loaded from the details.csv file. And so in this case I think I need to use branching with #[component] and Branch9 instead of Branch2, but the max branch I can use is Branch9, and even if I only had 9 columns of data, and used Branch9, I then need to use each of A,B,C,D,E,F,G,H,I for each of the 9 times that I loop through with for index in 0..data.len() { (i.e. Branch9::A, Branch9::B, ..., Branch9::I), so I need to fit 9 branches in 2 blocks, since I only have an if and an else block to put them in...
So I could remove the outer loop for index in 0..data.len() { and just manually add a branch for every possibility like the following, but that seems like an extreme measure, and again, I'm not sure whether I need Branch9 or if Branch2 is actually sufficient in this scenerio and I'm just using it wrong. And to fully cater for indexes 0 to 3 I have to use Branch8, since if I use Branch9 I can only cater for an ::I branch state.entry[4].editing == true but not a ::J branch to cater for state.entry[4].editing == false, but I can use ::J to cater for the else at the end.

    if state.entry[0].editing == true {
        return Branch9::A(view! { <DetailEditing index={0} {data} {placeholders_file} {state} /> });
    } else if state.entry[0].editing == false {
        return Branch9::B(view! { <DetailView index={0} {data} {state} /> });
    } else if state.entry[1].editing == true { 
        return Branch9::C(view! { <DetailEditing index={1} {data} {placeholders_file} {state} /> });
    } else if state.entry[1].editing == false {
        return Branch9::D(view! { <DetailView index={1} {data} {state} /> });
    } else if state.entry[2].editing == true { 
        return Branch9::E(view! { <DetailEditing index={2} {data} {placeholders_file} {state} /> });
    } else if state.entry[2].editing == false {
        return Branch9::F(view! { <DetailView index={2} {data} {state} /> });
    } else if state.entry[3].editing == true { 
        return Branch9::G(view! { <DetailEditing index={3} {data} {placeholders_file} {state} /> });
    } else if state.entry[3].editing == false {
        return Branch9::H(view! { <DetailView index={3} {data} {state} /> });
    } else {
        return Branch9::I(view! { <div>{ "branch not configured" }</div> });
    }

Whilst that compiles, it only renders one of the input fields instead of all of them.

So then I reverted my code to the way it was before doing all this:
But that only works when I change if state.entry[index].editing == true { and let editing = class!("editing" if state.entry[index].editing); to have hard-coded values of index like 0, but then it triggers out of bounds errors as expected since I'm not sure how to move the if statement inside the view!

if state.entry[index].editing == true {
    Branch2::A(view! {
        <div>
            {
                for (0..data.len()).map(move |index|
                    view! {
                        <div.edit>
                            { data[index].1.clone() }
                            <input.edit
                                value={ data[index].1.clone() }
                                type="text"
                                placeholder={ format!("<Enter {:#?}>", placeholders_file[index]) }
                                data_index={ index.to_string() }
                                onchange={
                                    state.bind(move |state, e: Event<InputElement>| {
                                        if let Some(data_index) = e.target().get_attribute("data_index") {
                                            let index: usize = data_index.parse::<usize>().unwrap();
                                            state.details.table.rows[0][index] = Text::Owned(e.target().value().into());
                                            state.entry[index].editing = false;
                                        }
                                    })
                                }
                                onmouseover={
                                    |e: MouseEvent<InputElement>| e.target().focus()
                                }
                                onkeypress={
                                    state.bind(move |state, e: KeyboardEvent<InputElement>| {
                                        if e.key() == "Enter" && e.target().value() != "" {
                                            state.update(index, e.target().value());

                                            Then::Render
                                        } else {
                                            Then::Stop
                                        }
                                    })
                                }
                                onkeypress={
                                    state.bind(move |state, e: KeyboardEvent<InputElement>| {
                                        if e.key() == "Enter" && e.target().value() != "" {
                                            state.update(index, e.target().value());

                                            Then::Render
                                        } else {
                                            Then::Stop
                                        }
                                    })
                                }
                                onblur={
                                    state.bind(move |state, e: Event<InputElement>| {
                                        if e.target().value() != "" {
                                            state.update(index, e.target().value())
                                        }
                                    })
                                }
                            />
                        </div>
                    }
                )
            }
        </div>
    })
} else {
    let editing = class!("editing" if state.entry[index].editing);

    Branch2::B(view! {
        <div>
            {
                for (0..data.len()).map(move |index|
                    view! {
                        <div .details.{editing}>
                            <div .view>
                                <label
                                    ondblclick={
                                        state.bind(move |s, _| {
                                            // s.editing = Cell { index, 0 };
                                            s.edit_entry(index);
                                            // s.entry[index].editing = true;
                                        })
                                    }
                                >
                                    { data[index].1.clone() }
                                </label>
                            </div>
                        </div>
                    }
                )
            }
        </div>
    })
}

Sorry for the convoluted post but any tips on whether it's possible to have an if statement inside for (0..data.len()).map(move |index|, and whether it's bad practice to have a loop and the condition outside a Branch like:

for index in 0..data.len() {
  if state.entry[index].editing == true {
    Branch2::A(view! {

I'm going to revert back to using Cell and Head in the interim.

Question: Kobols vs Dioxus

So I've tried different HTML generators with rust, i.e. markup.rs and ructe for example.

I finally ended up using Dioxus SSR because it supports a component model.

You can see a component library I've built up here https://github.com/purton-tech/cloak/tree/main/crates/primer-rsx

The main things I need are

  1. Default properties. i.e. for an input I want to be able to default most attributes and just supply the ones that I need. i.e. type: Option
  2. Loops - Some way of lopping and using a variable from the loop. i.e. creating tables. See an example table
  3. Child components. i.e. a Card has a header and a body.
  4. Fast compile times. when doing web I want sub 1 second incremental compiles times ideally.
  5. The ability to do raw html. i
  6. Nice to have - Type safety, i.e. don't let me do h7 but h1-h6 are ok.
  7. Allow me to add web-components in the markup.
  8. A pre-built component library so I don't have to build my own.
  9. Server side rendering.

How does that sound?

Refer user to Trunk website installation instructions since they vary depending on the user's OS

I noticed when I was following the installation instructions in the README.md file that some of the steps to install Trunk aren't the same as the installation instructions at https://trunkrs.dev/#install. If they didn't go to https://trunkrs.dev/#install and just assumed that following the README.md instructions were sufficient they might give up if they were inexperienced or have an Apple M1 or were using macOS.

Suggest removing the README.md instruction cargo install --locked trunk and instead just referring the user to go to https://trunkrs.dev/#install and follow the instructions there, where there are OS-specific instructions.

I think it's fine to leave trunk serve in the README.md file since I'd expect that to be common across different OSes

`Signal::set` needs to panic instead of silently failing on double-borrow

In this draft PR #55, I was trying to see if I could use the get function implementation of Hook https://docs.rs/kobold/latest/kobold/stateful/struct.Hook.html#method.get just for fun.
It states that its used to "Get the value of state if state implements Copy". I used a state that is just a struct State that has a my_state property with a bool type so it can implement the Copy trait, since if the State was more complex and had properties of types that don't automatically implement the Copy trait (i.e. Vec, String, Range, Box) then you get errors like the following, which were unnecesassarily complicated for me to resolve when I just wanted to a simple example of using the Hook's get function :

error[E0204]: the trait `Copy` may not be implemented for this type
  --> examples/invoice/src/state.rs:33:32
   |
33 | #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
   |                                ^^^^
...
39 |     pub qr_code: String,
   |     ------------------- this field does not implement `Copy`
   |
   = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0204]: the trait `Copy` may not be implemented for this type
  --> examples/invoice/src/state.rs:41:32
   |
41 | #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
   |                                ^^^^
42 | pub struct Entry {
43 |     pub description: String,
   |     ----------------------- this field does not implement `Copy`
   |
   = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0204]: the trait `Copy` may not be implemented for this type
  --> examples/invoice/src/state.rs:47:32
   |
47 | #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
   |                                ^^^^
...
50 |     pub columns: Vec<Text>,
   |     ---------------------- this field does not implement `Copy`
51 |     pub rows: Vec<Vec<Text>>,
   |     ------------------------ this field does not implement `Copy`
   |
   = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0204]: the trait `Copy` may not be implemented for this type
  --> examples/invoice/src/state.rs:54:32
   |
54 | #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
   |                                ^^^^
55 | pub enum Text {
56 |     Insitu(Range<usize>),
   |            ------------ this field does not implement `Copy`
57 |     Owned(Box<str>),
   |           -------- this field does not implement `Copy`
   |
   = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0204]: the trait `Copy` may not be implemented for this type
   --> examples/invoice/src/state.rs:204:32
    |
204 | #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
    |                                ^^^^
205 | pub struct TextSource {
206 |     pub source: String,
    |     ------------------ this field does not implement `Copy`

So my first thought was, is the intention for developers whose State has properties that don't automatically implement the Copy trait to try to get them to implement the Copy trait if they want to use the Hook's public get function (even though it might require changes to the Kobold library itself)? Or is the intention for it to just be used by developers with simple projects where the State has properties that automatically implement the Copy trait?

Then when I was playing with trying different parts of the docs, I tried to use stateful::Signal public function .set https://docs.rs/kobold/latest/kobold/stateful/struct.Signal.html#method.set, which says it's used to "Replace the entire state with a new value and trigger an update.", but when I tried using it to change the state, it actually didn't change the state at all as expected, i thought it would have set the my_state value to true. See screenshot below of UI output:

Screen Shot 2023-04-04 at 12 24 31 pm

Note: I also tried using update, but that doesn't appear to have worked either for some reason even though I thought I'd been using it previously without issues

Unable to debug

I'm been trying to figure out how to setup debugging for introspection using breakpoints. I'm using VSCode.
I pushed my attempts so far in this commit d6b170d.
I want to debug struct Table.
If I run the code with RUST_LOG=debug (i.e. RUST_LOG=debug trunk serve --address=127.0.0.1) then it outputs more debugging logs in the terminal, but not the ones that i introduce in the code using gloo_console::debug or log::debug.
I'm aware that i can use the following to use console.log in the browser https://rustwasm.github.io/wasm-bindgen/examples/console-log.html, but i want to debug the non-private parts of kobold.

I managed to get it to log to console in the browser by following this https://blog.urth.org/2022/02/14/frontend-rust-without-node/#just-use-trunkhttpstrunkrsdev where they suggested using wasm-logger, so it worked in this commit dfc527f, but it only output main() and Editor(), but not the move and ## table logs

I found the info about how to try and debug from these sources.

Unsure how to dereference multiple fields of a struct using `Deref` and `DerefMut`

I'm trying to make this Invoice example, here is the latest.

It uses code from csv_editor and todomvc. In the csv_editor example Table is used to store a table of rows and columns in the State after the data is loaded from a CSV file no. 1 and it uses Deref and DerefMut here for Table

In the Invoice example, i added another struct TableFileDetails here that stores a separate table of rows and columns in the State after the data is loaded from a different CSV file no. 2

But I don't know how to implement Deref and DerefMut for both Table and TableFileDetails that are both used by State?

In the DerefMut docs they only give this example https://doc.rust-lang.org/std/ops/trait.DerefMut.html#examples of a "struct with a single field which is modifiable by dereferencing the struct".

If I follow along with trying to use their example I end up with:

pub struct State<U, V> {
    ...
    pub table: U,
    pub table_file_details: V,
    ...
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Table {
    pub source: TextSource,
    pub columns: Vec<Text>,
    pub rows: Vec<Vec<Text>>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct TableFileDetails {
    pub source: TextSource,
    pub columns: Vec<Text>,
    pub rows: Vec<Vec<Text>>,
}

impl<T, U> Deref for State<T, U> {
    type Target = T;

    fn deref(&self) -> &mut Self::Target {
        &self.table
    }
}

impl<T, U> DerefMut for State<T, U> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.table
    }
}

impl<T, U> Deref for State<T, U> {
    type Target = U;

    fn deref(&self) -> &mut Self::Target {
        &self.table_file_details
    }
}

impl<T, U> DerefMut for State<T, U> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.table_file_details
    }
}

But then I get a lot of errors that I try to resolve, but the main one is the following:

error[E0119]: conflicting implementations of trait `Deref` for type `state::State<_, _>`
   --> examples/invoice/src/state.rs:204:1
    |
190 | impl<T, U> Deref for State<T, U> {
    | -------------------------------- first implementation here
...
204 | impl<T, U> Deref for State<T, U> {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `state::State<_, _>`

error[E0119]: conflicting implementations of trait `DerefMut` for type `state::State<_, _>`
   --> examples/invoice/src/state.rs:212:1
    |
198 | impl<T, U> DerefMut for State<T, U> {
    | ----------------------------------- first implementation here
...
212 | impl<T, U> DerefMut for State<T, U> {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `state::State<_, _>`

And I can't see how to to return multiple targets from deref and deref_mut

License seems a bit scary

Maybe add an explanation at the bottom of your README that explains how using an LGPL library in rust affects your code you build?

Support ARIA

It doesn't seem possible right now to add ARIA properties or states (i.e. aria-hidden="true" or aria-disabled="true").

`view!` macro syntax changes

Following the discussion in #48 and having slept on it, I think the best solution for syntax would be to avoid the </> implied closing tags, and instead follow the actual HTML5 spec for tags.

Self-closing tags

The following elements wouldn't require the closing tailing slash /> and would be forbidden from having child nodes:

Optional closing tags

The following elements would would have their closing tags made optional altogether:

Aside from being closed by the opening tags mentioned above (if any), those elements are closed if their parent elements are closed (including implicit closing of the parent element).

All tags close on termination

To top it all of all open tags would automatically close at the end of the view! macro invocation.

Examples

All these rules combined would mean the following would all be valid syntax:

view! { <span.fancy-text> "Text" }
view! {
    <p>
        "Paragraph 1"
        <br>
        "Still paragraph 1"
    <p>
        "Paragraph 2"
        <img src="kobold.png" alt="Kobold">
        "Still paragraph 2"
}
view! {
    <ul.my-list>
        <li> "Item 1"
        <li> "Item 2"
        <li> "Item 3"
}
view! {
    <table.some-class>
        <tr>
            <td> "Row 1, Col 1"
            <td> "Row 1, Col 2"
            <td> "Row 1, Col 3"
        <tr>
            <td> "Row 2, Col 1"
            <td> "Row 2, Col 2"
            <td> "Row 2, Col 3"
} 

This should all be backwards compatible with what's currently implemented, though it adds ambiguity to closing tags making implicit closing </> unfeasible (and also unnecessary I believe).

In addition components would still have to follow the XML rules for closing since there is no way for the macro to know whether a component allows closing tags or not.

CC: @nanoqsh

error[E0507]: cannot move out of `on_________`, a captured variable in an `FnMut` closure

In this "draft" Pull Request, i am getting the following errors in the last commit since trying to introduce new changes mentioned below. the last commit where it was working before any of these changes is this one

click to show error
error[E0507]: cannot move out of `onchange`, a captured variable in an `FnMut` closure
   --> examples/invoice/src/main.rs:342:42
    |
299 |         let onchange = state.bind(move |state, e: Event<InputElement>| {
    |             -------- captured outer variable
...
331 |                         for (0..indexes.len()-1).map(move |index|
    |                                                      ------------ captured by this `FnMut` closure
...
342 |                                         {onchange}
    |                                          ^^^^^^^^ move occurs because `onchange` has type `impl Fn(kobold::event::Event<HtmlInputElement>) + 'static`, which does not implement the `Copy` trait

error[E0507]: cannot move out of `onmouseover`, a captured variable in an `FnMut` closure
   --> examples/invoice/src/main.rs:343:42
    |
313 |         let onmouseover = state.bind(move |state, e: MouseEvent<InputElement>| {
    |             ----------- captured outer variable
...
331 |                         for (0..indexes.len()-1).map(move |index|
    |                                                      ------------ captured by this `FnMut` closure
...
343 |                                         {onmouseover}
    |                                          ^^^^^^^^^^^ move occurs because `onmouseover` has type `impl Fn(kobold::event::Event<HtmlInputElement, web_sys::MouseEvent>) + 'static`, which does not implement the `Copy` trait

error[E0507]: cannot move out of `onkeypress`, a captured variable in an `FnMut` closure
   --> examples/invoice/src/main.rs:344:42
    |
317 |         let onkeypress = state.bind(move |state, e: KeyboardEvent<InputElement>| {
    |             ---------- captured outer variable
...
331 |                         for (0..indexes.len()-1).map(move |index|
    |                                                      ------------ captured by this `FnMut` closure
...
344 |                                         {onkeypress}
    |                                          ^^^^^^^^^^ move occurs because `onkeypress` has type `impl Fn(kobold::event::Event<HtmlInputElement, web_sys::KeyboardEvent>) + 'static`, which does not implement the `Copy` trait

error[E0507]: cannot move out of `onblur`, a captured variable in an `FnMut` closure
   --> examples/invoice/src/main.rs:345:42
    |
307 |         let onblur = state.bind(move |state, e: Event<InputElement>| {
    |             ------ captured outer variable
...
331 |                         for (0..indexes.len()-1).map(move |index|
    |                                                      ------------ captured by this `FnMut` closure
...
345 |                                         {onblur}
    |                                          ^^^^^^ move occurs because `onblur` has type `impl Fn(kobold::event::Event<HtmlInputElement>) + 'static`, which does not implement the `Copy` trait

error[E0507]: cannot move out of `ondblclick`, a captured variable in an `FnMut` closure
   --> examples/invoice/src/main.rs:366:49
    |
355 |         let ondblclick = state.bind(move |s, _| s.entry.editing = true);
    |             ---------- captured outer variable
...
362 |                         for (0..indexes.len()-1).map(move |index|
    |                                                      ------------ captured by this `FnMut` closure
...
366 |                                         <label {ondblclick} >
    |                                                 ^^^^^^^^^^ move occurs because `ondblclick` has type `impl Fn(kobold::event::Event<HtmlElement, web_sys::MouseEvent>) + 'static`, which does not implement the `Copy` trait

The most obvious solution for me was to update struct Event and give it #[derive(Copy)] as shown below:

#[derive(Copy)]
pub struct Event<T = HtmlElement, E = web_sys::Event> {

but that gave error:

error[E0204]: the trait `Copy` may not be implemented for this type
  --> crates/kobold/src/event.rs:19:10
   |
19 | #[derive(Copy)]
   |          ^^^^
20 | pub struct Event<T = HtmlElement, E = web_sys::Event> {
21 |     event: web_sys::Event,
   |     --------------------- this field does not implement `Copy`

Update: I realised when I did this that I was still using kobold "0.6.0" instead of "0.7.0", which doesn't have the same code, but even after I updated to "0.7.0" I still got the same errors

To provide some context of what I'm doing, I'm customizing the EntryView component, so it gets the state.details.table (which is a table like in its mock here where it just has a first row of label columns and second row with corresponding data columns.

Then here I populate a valid_labels Vec with its labels from the first row, then populate a values Vec with the data from the second row, and I populate an indexes Vec with the corresponding indexes of each column where the data came from (since i need to loop through them with for (0..indexes.len()-1).map(move |index| in the view!. So each of those Vec have the same length.

Then I use Branch2::A if they are editing, or Branch2::B otherwise.

I'm having trouble in Branch2::A for editing at the moment, relevant code snippet below:

let onchange = state.bind(move |state, e: Event<InputElement>| {
    if let Some(data_index) = e.target().get_attribute("data-index") {
        let index: usize = data_index.parse::<usize>().unwrap();
        state.details.table.rows[0][index] = Text::Owned(e.target().value().into());
        state.entry.editing = false;
    }
});

let onblur = state.bind(move |state, e: Event<InputElement>| {
    if e.target().value() != "" {
        state.update(e.target().value())
    }
});

let onmouseover = state.bind(move |state, e: MouseEvent<InputElement>| {
    let _ = e.target().focus();
});

let onkeypress = state.bind(move |state, e: KeyboardEvent<InputElement>| {
    if e.key() == "Enter" && e.target().value() != "" {
        state.update(e.target().value());

        Then::Render
    } else {
        Then::Stop
    }
});

Branch2::A(
    view! {
        <div>
            {
                for (0..indexes.len()-1).map(move |index|
                    view! {
                        <div.edit>
                            { values[index].clone() }
                            <input.edit
                                value={ values[index].clone() }
                                type="text"
                                placeholder="<Enter biller address>"
                                // if i use `data-index` it gives error
                                // `expected expression`
                                data_index={ index.to_string() }
                                {onchange}
                                {onmouseover}
                                {onkeypress}
                                {onblur}
                            />
                        </div>
                    }
                )
            }
        </div>
    }
)

I try to use for (0..indexes.len()-1).map(move |index| to generate an editable input field for each column of data.
I try to store a reference to the index in a data attribute like this data_index={ ref index.to_string() }.
I show the current value being edited in the input field with value={ ref values[index] } as shown.
Note: I tried to use the HTML syntax data-index there, but that gave an error expected expression, hence why i used data_index (with an _ underscore instead of dash -). Maybe I also need to add that data-* attribute in the Kobold macros here https://github.com/maciejhirsz/kobold/blob/master/crates/kobold_macros/src/gen/element.rs#L260

Then since there are multiple input fields (for each column from the table), I need the onchange handler to cater for each one, so I get the value of the data_index attribute from the input element, since it contains the index that corresponds to the column with the data that we want to populate if there are any changes, so I can use the index variable like this state.details.table.rows[0][index]. code snippet below

let onchange = state.bind(move |state, e: Event<InputElement>| {
    if let Some(data_index) = e.target().get_attribute("data_index") {
        let index: usize = data_index.parse::<usize>().unwrap();
        state.details.table.rows[0][index] = Text::Owned(e.target().value().into());
        ...
    }
});

Here I'm not sure whether I should use get_attribute("data_index") or get_attribute("data-index") (i.e. dash or underscore).

The errors shown above disappear if I comment out the following code, but I want to use that code:

<input.edit
  ...
  // {onchange}
  // {onmouseover}
  // {onkeypress}
  // {onblur}

...

// let ondblclick = state.bind(move |s, _| s.entry.editing = true);

...

// <label {ondblclick} >
//     { values[index].clone() }
// </label>

error[E0658]: use of unstable library feature 'toowned_clone_into': recently added

In the commit '576495100f3d7fc4ec93b621cbdfee8295648145' of this fork https://github.com/**ltfschoen/kobold** where I've rebased with the latest commit in 'master' branch commit '53eacf69d2ea3f25a47e58a3ccd11dcaf73d48bf' of maciejhirsz/kobold. If I run trunk serve when using the latest "nightly" version with rustup override set nightly-2022-03-22-x86_64-unknown-linux-gnu it gives the following error when i run trunk serve due to the use of clone_into:

Screen Shot 2023-03-17 at 10 27 03 pm

But if I use the latest "stable" version with rustup override set stable-x86_64-unknown-linux-gnu it doesn't produce any error when i run trunk serve.

To fix the error in "nightly" it was necessary to add #![feature(toowned_clone_into)] to the crate attributes at the top of the following files:

  • crates/kobold/src/lib.rs
  • examples/stateful/src/main.rs

Whilst I suspect we'd only be using the "stable" Rust version in this repo, is there a reason why we wouldn't want to create a PR into this repo to use #![feature(toowned_clone_into)] so it resolved the error error[E0658]: use of unstable library feature 'toowned_clone_into': recently added that is caused by the use of clone_into in a few places, so that this repo builds successfully for users that are using Rust "nightly"?

Add persistence in localStorage to TodoMVC

I sensed from your blog post that you might be interested in using TodoMVC as a benchmark for WASM binary size comparisons across frameworks. I think this is is a great idea, since TodoMVC is pretty ubiquitous as a small-but-reasonable app example, and did some similar comparisons a couple years ago.

I'd note that if you want to compare across frameworks you should either add persisting serialized todos to localStorage (see TodoMVC spec) or remove it from other frameworks before comparing, since serde_json tends to make up a pretty large fraction of binary size for typical Rust/WASM apps.

Empty tag that is not self-closing generates invalid javascript

The following snippet causes Kobold to generate invalid javascript:

html! {
    <svg></svg>
}

generates

export function __e0_77bcc8fe2fa4f331() {
let e0=document.createElement("svg");
e0.append);
return e0;
}

which then causes

Uncaught SyntaxError: Unexpected token ')' (at inline0.js:3:10)

<svg/> and <svg>""</svg> both work fine, only an empty tag causes this problem.

error[E0658]: use of unstable library feature 'toowned_clone_into': recently added

I tried to create a project based on the Kobold dependency kobold = "0.4.1" in this repository https://github.com/ltfschoen/kobold-test
I've added the steps I took in the README file.
But when I do the trunk serve step I get the following errors whether I use the latest stable or nightly nightly-2022-03-22-x86_64-apple-darwin or nightly-2021-03-10-x86_64-unknown-linux-gnu. I've tried on both macOS and Ubuntu.

nightly errors
luke @ ~/code/github/ltfschoen/kobold-test - [master] $ rustup default nightly-2022-03-22-x86_64-apple-darwin
info: using existing install for 'nightly-2022-03-22-x86_64-apple-darwin'
info: default toolchain set to 'nightly-2022-03-22-x86_64-apple-darwin'

  nightly-2022-03-22-x86_64-apple-darwin unchanged - rustc 1.61.0-nightly (3c17c84a3 2022-03-21)

luke @ ~/code/github/ltfschoen/kobold-test - [master] $ trunk serve
2023-03-17T01:07:01.587923Z  INFO ๐Ÿ“ฆ starting build
2023-03-17T01:07:01.589003Z  INFO spawning asset pipelines
2023-03-17T01:07:02.881202Z  INFO building kobold-test
   Compiling proc-macro2 v1.0.52
   Compiling unicode-ident v1.0.8
   Compiling quote v1.0.26
   Compiling syn v1.0.109
   Compiling log v0.4.17
   Compiling wasm-bindgen-shared v0.2.84
   Compiling cfg-if v1.0.0
   Compiling bumpalo v3.12.0
   Compiling once_cell v1.17.1
   Compiling wasm-bindgen v0.2.84
   Compiling arrayvec v0.7.2
   Compiling fnv v1.0.7
   Compiling beef v0.5.2
   Compiling itoa v1.0.6
   Compiling ryu v1.0.13
   Compiling kobold_macros v0.4.1
   Compiling wasm-bindgen-backend v0.2.84
   Compiling wasm-bindgen-macro-support v0.2.84
   Compiling wasm-bindgen-macro v0.2.84
   Compiling js-sys v0.3.61
   Compiling console_error_panic_hook v0.1.7
   Compiling web-sys v0.3.61
   Compiling kobold v0.4.1
   Compiling kobold-test v0.1.0 (/Users/luke/code/github/ltfschoen/kobold-test)
error[E0432]: unresolved import `kobold::prelude::component`
 --> src/main.rs:1:23
  |
1 | use kobold::prelude::{component, html, Html};
  |                       ^^^^^^^^^ no `component` in `prelude`

error: cannot determine resolution for the attribute macro `wasm_bindgen::prelude::wasm_bindgen`
 --> src/main.rs:5:5
  |
5 | /     html! {
6 | |         <h1>"Hello "{ name }"!"</h1>
7 | |     }
  | |_____^
  |
  = note: import resolution is stuck, try simplifying macro imports
  = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)

error: cannot determine resolution for the attribute macro `component`
 --> src/main.rs:3:3
  |
3 | #[component]
  |   ^^^^^^^^^
  |
  = note: import resolution is stuck, try simplifying macro imports

error[E0574]: expected struct, variant or union type, found function `Hello`
  --> src/main.rs:12:10
   |
12 |         <Hello name="Kobold" />
   |          ^^^^^ not a struct, variant or union type

error[E0759]: `name` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
 --> src/main.rs:6:23
  |
4 | fn Hello(name: &str) -> impl Html + '_ {
  |                ---- this data with an anonymous lifetime `'_`...
5 |     html! {
6 |         <h1>"Hello "{ name }"!"</h1>
  |                       ^^^^ ...is used here...
  |
note: ...and is required to live as long as `'static` here
 --> src/main.rs:4:25
  |
4 | fn Hello(name: &str) -> impl Html + '_ {
  |                         ^^^^^^^^^^^^^^

Some errors have detailed explanations: E0432, E0574, E0759.
For more information about an error, try `rustc --explain E0432`.
error: could not compile `kobold-test` due to 5 previous errors
2023-03-17T01:07:50.267241Z ERROR โŒ error
error from HTML pipeline

Caused by:
    0: error from asset pipeline
    1: error during cargo build execution
    2: cargo call returned a bad status
2023-03-17T01:07:50.268910Z  INFO ๐Ÿ“ก serving static assets at -> /
2023-03-17T01:07:50.269144Z  INFO ๐Ÿ“ก server listening at http://127.0.0.1:8080

I think that's because the code I'm using is directly from the latest 'master' branch at https://github.com/maciejhirsz/kobold, which is version kobold = "0.5.0", so it's not compatible with the older version 0.4.0.

But I actually want to develop based on the latest commit in https://github.com/maciejhirsz/kobold.

So I tried switching to nightly and installing the latest commit in the 'master' branch of https://github.com/maciejhirsz/kobold

rustup default nightly-2022-03-22
cargo install --bins --examples --git=https://github.com/maciejhirsz/kobold --branch=master --rev=7fd98d2aec28c06b031efcae2e31405ca0683c9e

But even though i specified --bins to install all binaries, it still gave error:

cargo install --bins --examples --git=https://github.com/maciejhirsz/kobold --branch=master --rev=7fd98d2aec28c06b031efcae2e31405ca0683c9e
info: syncing channel updates for 'nightly-2022-03-22-x86_64-apple-darwin'
info: latest update on 2022-03-22, rust version 1.61.0-nightly (3c17c84a3 2022-03-21)
...
    Updating git repository `https://github.com/maciejhirsz/kobold`
error: multiple packages with binaries found: kobold_counter_example, kobold_csv_editor_example, kobold_hello_world_example, kobold_interval_example, kobold_list_example, kobold_qrcode_example, kobold_stateful_example, kobold_todomvc_example. When installing a git repository, cargo will always search the entire repo for any Cargo.toml. Please specify which to install.

So I removed Cargo.lock, and removed the kobold = 0.4.1 dependency from Cargo.toml, and updated cargo cargo update,
then I tried specifying all the packages at the end of the command so it wouldn't error:

cargo install --bins --examples --git=https://github.com/maciejhirsz/kobold --branch=master --rev=7fd98d2aec28c06b031efcae2e31405ca0683c9e kobold_counter_example kobold_csv_editor_example kobold_hello_world_example kobold_interval_example kobold_list_example kobold_qrcode_example kobold_stateful_example kobold_todomvc_example --verbose

But that output the following for each of the packages:

error[E0658]: use of unstable library feature 'toowned_clone_into': recently added
   --> crates/kobold/src/value.rs:191:18
    |
191 |             self.clone_into(&mut p.value);
    |                  ^^^^^^^^^^
    |
    = note: see issue #41263 <https://github.com/rust-lang/rust/issues/41263> for more information
    = help: add `#![feature(toowned_clone_into)]` to the crate attributes to enable

error[E0658]: use of unstable library feature 'toowned_clone_into': recently added
   --> crates/kobold/src/value.rs:218:18
    |
218 |             self.clone_into(state);
    |                  ^^^^^^^^^^
    |
    = note: see issue #41263 <https://github.com/rust-lang/rust/issues/41263> for more information
    = help: add `#![feature(toowned_clone_into)]` to the crate attributes to enable

For more information about this error, try `rustc --explain E0658`.
error: could not compile `kobold` due to 2 previous errors

So I switched to stable rustup default stable, and tried again:

cargo install --bins --examples --git=https://github.com/maciejhirsz/kobold --branch=master --rev=7fd98d2aec28c06b031efcae2e31405ca0683c9e kobold_counter_example kobold_csv_editor_example kobold_hello_world_example kobold_interval_example kobold_list_example kobold_qrcode_example kobold_stateful_example kobold_todomvc_example --verbose

but that gave the same errors.
So I made those changes (adding #![feature(toowned_clone_into)] above every clone_into in the codebase) in my fork https://github.com/ltfschoen/kobold 'master' branch commit '2617dc3e4cff227d68e8a7ae883d8aa7cec6de6f', and then ran:

cargo install --bins --examples --git=https://github.com/ltfschoen/kobold --branch=master --rev=2617dc3e4cff227d68e8a7ae883d8aa7cec6de6f kobold_counter_example kobold_csv_editor_example kobold_hello_world_example kobold_interval_example kobold_list_example kobold_qrcode_example kobold_stateful_example kobold_todomvc_example --verbose

And that finally compiled successfully.

But when I ran trunk serve it still gave errors

       error: failed to select a version for the requirement `kobold_macros = "^0.5.0"`
       candidate versions found which didn't match: 0.4.1, 0.4.0, 0.3.0, ...
       location searched: crates.io index
       required by package `kobold v0.5.0 (https://github.com/ltfschoen/kobold.git?rev=7fd98d2aec28c06b031efcae2e31405ca0683c9e#7fd98d2a)`

Then it occurred to me after reading https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#multiple-locations, that the dependencies weren't right in the Cargo.toml files, as they were referring to crates.io when version 0.5.0 hadn't been published yet, but those docs say that you can specify the path so you can still use the Github repo for local development if it isn't publlished yet at crates.io for the version you specify.

So I updated all the Cargo.toml files in this commit https://github.com/ltfschoen/kobold.git, d56e8f6

and also updated the Cargo.toml file for my project at https://github.com/ltfschoen/kobold-test so it uses the changes in my fork:

[dependencies]
kobold = { version = "0.5.0", git = "https://github.com/ltfschoen/kobold.git", rev = "d56e8f69d7678040c63d57e93f78d4c3aa35656d" }
kobold_macros = { version = "0.5.0", git = "https://github.com/ltfschoen/kobold.git", rev = "d56e8f69d7678040c63d57e93f78d4c3aa35656d" }
kobold_qr = { version = "0.5.0", git = "https://github.com/ltfschoen/kobold.git", rev = "d56e8f69d7678040c63d57e93f78d4c3aa35656d" }

And finally it worked without errors when i ran trunk serve

Explicitly specify state table property to use to prevent conflicts

In the csv example, table is a property of State https://github.com/maciejhirsz/kobold/blob/master/examples/csv_editor/src/state.rs#L13
and columns and rows are properties of Table https://github.com/maciejhirsz/kobold/blob/master/examples/csv_editor/src/state.rs#L18
and we can use columns() or rows() to get them for Table https://github.com/maciejhirsz/kobold/blob/master/examples/csv_editor/src/state.rs#L81
but in main.rs, its calling state.columns() and state.rows() in places like here https://github.com/maciejhirsz/kobold/blob/master/examples/csv_editor/src/main.rs#L59, but i think it should be calling state.table.columns() and state.table.rows().

otherwise you get conflicts, like in the invoice example here where both Table and TableFileDetails have properties columns and rows, and also the methods columns() and rows(), so i have to explicitly specify like here state.table.columns() and state.table.rows(), or state.table_file_details.rows() and state.table_file_details.columns(), otherwise i might get the wrong one. for example if i just used state.columns() it'd output columns 0..2, but if i used state.table_file_details.columns() it output columns 0..10, which was what i was after

i think it should even use state.table.source instead of juststate.source

error[E0463]: can't find crate for `core`

This is part of a longer error message, gist here

My environment:

  • Ubuntu 20.04
  • rustc -vV
    rustc 1.68.1 (8460ca823 2023-03-20)
    binary: rustc
    commit-hash: 8460ca823e8367a30dda430efda790588b8c84d3
    commit-date: 2023-03-20
    host: x86_64-unknown-linux-gnu
    release: 1.68.1
    LLVM version: 15.0.6
    
  • rustup show
      rustup show
    Default host: x86_64-unknown-linux-gnu
    rustup home:  /home/julian/.rustup
    
    installed targets for active toolchain
    --------------------------------------
    
    wasm32-unknown-unknown
    x86_64-unknown-linux-gnu
    
    active toolchain
    ----------------
    
    stable-x86_64-unknown-linux-gnu (default)
    rustc 1.68.1 (8460ca823 2023-03-20)
    

Thanks for your help

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.