{"id":24136240,"url":"https://github.com/oxide-byte/todo-tauri","last_synced_at":"2025-03-01T12:17:35.795Z","repository":{"id":271928044,"uuid":"907457322","full_name":"oxide-byte/todo-tauri","owner":"oxide-byte","description":"Simple Leptos Todo App embedded in Tauri as Desktop application","archived":false,"fork":false,"pushed_at":"2025-02-11T11:34:49.000Z","size":372,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-11T12:33:46.264Z","etag":null,"topics":["leptos","poc","rust","tauri","tauri-app","todo-app"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oxide-byte.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-23T16:15:15.000Z","updated_at":"2025-02-11T11:34:53.000Z","dependencies_parsed_at":"2025-01-10T20:23:36.386Z","dependency_job_id":"74adb28d-91a4-440e-87ca-10ab31f99097","html_url":"https://github.com/oxide-byte/todo-tauri","commit_stats":null,"previous_names":["oxide-byte/todo-tauri"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxide-byte%2Ftodo-tauri","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxide-byte%2Ftodo-tauri/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxide-byte%2Ftodo-tauri/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxide-byte%2Ftodo-tauri/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oxide-byte","download_url":"https://codeload.github.com/oxide-byte/todo-tauri/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241361447,"owners_count":19950382,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["leptos","poc","rust","tauri","tauri-app","todo-app"],"created_at":"2025-01-12T02:46:59.605Z","updated_at":"2025-03-01T12:17:35.786Z","avatar_url":"https://github.com/oxide-byte.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Todo - Leptos - Tauri\n\n## Introduction\n\nThis is a simple POC of a [Leptos (0.7)](https://leptos.dev/) Todo Web Application embedded in the [Tauri 2](https://v2.tauri.app/) Framework for Desktop and/or Mobile Applications.\n\n## Preparation\n\n* cargo install trunk\n* cargo install tauri-cli\n* rustup target add wasm32-unknown-unknown\n\n## Creation\n\ncargo new todo-tauri\n\n## Todo Leptos\n\nWe start doing a CSR (client-side rendering) Todo Lepos application in a couple of steps. \nThis part could easily replaced by an Angular, React or other Web Framework.\n\nAdding the dependency to cargo.toml\n\n```yaml\n[dependencies]\nleptos = { version = \"0.7.3\", features = [\"csr\"] }\n```\n\nWe validate the current environment by running the default Hello World\n\n```shell\ncargo build\ncargo run\n```\n\nLet add a simple container index.html file:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n    \u003ctitle\u003eTodo-Tauri\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\u003c/body\u003e\n\u003c/html\u003e\n```\n\nAnd modify the current main.rs\n\n```rust\nuse leptos::prelude::*;\n\nfn main() {\n    mount_to_body(|| view! { \u003ch1\u003eHello World\u003c/h1\u003e });\n}\n```\n\nAs we use Leptos and WebAssembly we now render a code that is shown in a browser. \nTrunk offers us a Hot Reload option for developing the Frontend part.\n\nwe add a general configuration file trunk.toml\n```toml\n[build]\ntarget = \"index.html\"\ndist = \"dist\"\n```\n\nstart the service\n\n```shell\ntrunk serve\n```\n\nand open in the browser the default url http://127.0.0.1:8080/\n\nNext apply some style to the page in configuring Tailwind (version 4)\n\nAdd a file tailwind.css under /style\n\n```css\n@import \"tailwindcss\";\n```\n\nAdd the link on the header of the index.html\n\n```html\n...\n\u003chead\u003e\n    \u003clink data-trunk rel=\"tailwind-css\" href=\"/style/tailwind.css\" /\u003e\n\u003c/head\u003e\n...\n```\nand add in the root a config file /tailwind.config.js\n\n```javascript\n/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n    content: {\n        files: [\"*.html\", \"./src/**/*.rs\"],\n        transform: {\n            rs: (content) =\u003e content.replace(/(?:^|\\s)class:/g, ' '),\n        },\n    },\n    theme: {\n        extend: {},\n    },\n    plugins: [],\n}\n```\n\nFinally we add a Tailwind operator to the code:\n\n```rust\nmount_to_body(|| view! { \u003ch1 class=\"text-4xl\"\u003eHello World with Tailwind\u003c/h1\u003e });\n```\n\nWith this we have closed the preparation for creating a client-side web page. The current\nproduced artifacts are in the folder /dist and could deployed to a server, on Github as Github Page or on AWS in a S3Bucket.\n\n## Todo App\n\nFirst step, we add some dependencies to the cargo file:\n```yaml\n[dependencies]\n  leptos = { version = \"0.7.3\", features = [\"csr\"] }\n  uuid = { version = \"1.11.0\", features = [\"v4\", \"js\"] }\n  instant = { version = \"0.1.13\", features = [ \"wasm-bindgen\", \"inaccurate\" ] }\n```\n\nNow we add a couple of files in our source:\n\nin src/entities\n\ntodo.rs\n```rust\nuse instant::Instant;\nuse uuid::Uuid;\n\n#[derive(Clone, Debug)]\npub struct Todo {\n    pub id: String,\n    pub title: String,\n    pub description: String,\n    pub created: Instant,\n}\n\nimpl Todo {\n    pub fn new(title: String, description: String) -\u003e Self {\n        let id = Uuid::new_v4().to_string();\n        let created = Instant::now();\n        Todo { id, title, description, created }\n    }\n\n    pub(crate) fn new_empty() -\u003e Todo {\n        Self::new(\"\".to_string(), \"\".to_string())\n    }\n}\n```\n\nand include it in the mod.rs\n```rust\nmod todo;\n\npub use todo::Todo;\n```\n\nNext the UI components in src/components:\n\ntodo_item.rs\n```rust\nuse crate::entities::Todo;\nuse leptos::html::*;\nuse leptos::prelude::*;\n\n#[component]\npub fn TodoItem\u003cE, D\u003e(todo: Todo, edit: E, delete: D,\n) -\u003e impl IntoView\nwhere\n    D: Fn(Todo) + 'static,\n    E: Fn(Todo) + 'static,\n{\n    let button_mod_class = \"text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-full text-sm p-2.5 text-center inline-flex items-center mr-2\";\n    let button_del_class = \"text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-full text-sm p-2.5 text-center inline-flex items-center mr-2\";\n    let todo_item: RwSignal\u003cTodo\u003e = RwSignal::new(todo);\n\n    let on_edit = move |_| {\n        edit(todo_item.get());\n    };\n\n    let on_delete = move |_| {\n        delete(todo_item.get());\n    };\n\n    view! {\n          \u003cdiv class=\"bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 flex flex-row\"\u003e\n\n                \u003cdiv class=\"basis-11/12\"\u003e\n                    \u003cp class=\"text-lg text-gray-900\"\u003e\n                        {todo_item.get().title}\n                    \u003c/p\u003e\n\n                    \u003ctextarea class=\"text-left text-gray-500 w-full\" rows=3\u003e\n                        {todo_item.get().description}\n                    \u003c/textarea\u003e\n                \u003c/div\u003e\n\n                \u003cdiv class=\"basis-1/12 flex items-center justify-center\"\u003e\n                   \u003cdiv class=\"flex flex-row-reverse space-x-4 space-x-reverse\"\u003e\n                        \u003cbutton\n                            class=button_mod_class\n                            on:click=on_edit\u003e\n                            \u003ci class=\"fa-solid fa-edit\"\u003e\u003c/i\u003e\n                        \u003c/button\u003e\n                        \u003cbutton\n                            class=button_del_class\n                            on:click=on_delete\u003e\n                            \u003ci class=\"fa-solid fa-minus\"\u003e\u003c/i\u003e\n                        \u003c/button\u003e\n                   \u003c/div\u003e\n                \u003c/div\u003e\n          \u003c/div\u003e\n    }\n}\n```\n\ntodo_modal.rs\n```rust\nuse crate::entities::Todo;\nuse leptos::html::*;\nuse leptos::prelude::*;\n\n#[component]\npub fn TodoModal\u003cF\u003e(todo: RwSignal\u003cTodo\u003e, on_close_modal: F) -\u003e impl IntoView\nwhere\n    F: Fn(Option\u003cTodo\u003e) + 'static + Copy,\n{\n    let input_field_class = \"shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline\";\n\n    let (title, _set_title) = signal(todo.get().title);\n    let title_node: NodeRef\u003cInput\u003e = NodeRef::new();\n\n    let (description, _set_description) = signal(todo.get().description);\n    let description_node: NodeRef\u003cTextarea\u003e = NodeRef::new();\n\n    let submit = move |_| {\n        let title = title_node\n            .get()\n            .expect(\"\u003ctitle\u003e should be mounted\")\n            .value();\n\n        let description = description_node\n            .get()\n            .expect(\"\u003cdescription\u003e should be mounted\")\n            .value();\n\n        let mut mod_todo = todo.get().clone();\n        mod_todo.title = title;\n        mod_todo.description = description;\n        on_close_modal(Some(mod_todo));\n    };\n\n    let cancel = move |_| {\n        on_close_modal(None);\n    };\n\n    view! {\n\n    \u003cdiv class=\"fixed inset-0 z-50 flex items-center justify-center bg-gray-900 bg-opacity-60\"\u003e\n\n        \u003cdiv\n          class=\"block rounded-lg bg-white w-2/5 p-4 shadow-[0_2px_15px_-3px_rgba(0,0,0,0.07),0_10px_20px_-2px_rgba(0,0,0,0.04)] z-70\"\u003e\n\n            \u003ch5 class=\"mb-5 text-xl font-medium leading-tight text-neutral-800\"\u003e\n                Create new Todo\n            \u003c/h5\u003e\n\n                \u003cdiv class=\"mb-5\"\u003e\n                    \u003clabel class=\"block text-gray-700 text-sm font-bold mb-2\" for=\"title\"\u003e\n                        Title\n                    \u003c/label\u003e\n                    \u003cinput\n                        node_ref=title_node\n                        class=input_field_class\n                        id=\"title\"\n                        type=\"text\"\n                        value=title\n                        placeholder=\"Title\"/\u003e\n                \u003c/div\u003e\n\n                \u003cdiv class=\"mb-5\"\u003e\n                    \u003clabel class=\"block text-gray-700 text-sm font-bold mb-2\" for=\"description\"\u003e\n                        Description\n                    \u003c/label\u003e\n                    \u003ctextarea\n                        node_ref=description_node\n                        class=input_field_class\n                        rows=\"3\"\n                        id=\"description\"\n                        placeholder=\"Description\"\u003e{\n                            description\n                        }\u003c/textarea\u003e\n                \u003c/div\u003e\n\n                \u003cdiv class=\"flex flex-row-reverse space-x-4 space-x-reverse\"\u003e\n                    \u003cbutton type=\"submit\"\n                        on:click=submit\n                        class=\"bg-blue-700 hover:bg-blue-800 px-5 py-3 text-white rounded-lg\"\u003e\n                        Save\n                    \u003c/button\u003e\n                    \u003cbutton type=\"cancel\"\n                        on:click=cancel\n                        class=\"bg-gray-300 hover:bg-gray-400 px-5 py-3 text-white rounded-lg\"\u003e\n                        Cancel\n                    \u003c/button\u003e\n                \u003c/div\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n    }\n}\n```\n\napp.rs\n```rust\nuse crate::components::*;\nuse crate::entities::*;\nuse leptos::prelude::*;\n\n#[component]\npub fn App() -\u003e impl IntoView {\n    let show_modal: RwSignal\u003cbool\u003e = RwSignal::new(false);\n    let edit_todo_item: RwSignal\u003cTodo\u003e = RwSignal::new(Todo::new_empty());\n\n    let button_new_class = \"rounded-full pl-5 pr-5 bg-blue-700 text-white rounded hover:bg-blue-800\";\n\n    let todos: RwSignal\u003cVec\u003cTodo\u003e\u003e = RwSignal::new(Vec::new());\n\n    let add_new_todo = move |x: Todo| {\n        edit_todo_item.set(x);\n        show_modal.set(true);\n    };\n\n    let edit_todo = move |todo: Todo| {\n        edit_todo_item.set(todo);\n        show_modal.set(true);\n    };\n\n    let delete_todo = move |todo: Todo| {\n        todos.update(|old| {\n            old.retain(|x| x.id != todo.id);\n        });\n    };\n\n    let close_modal_todo = move |x: Option\u003cTodo\u003e| {\n        if let Some(todo) = x {\n            todos.update(|old| {\n                old.retain(|x| x.id != todo.id);\n                old.push(todo);\n                old.sort_by(|a, b| a.created.cmp(\u0026b.created));\n            });\n        }\n        show_modal.set(false);\n    };\n\n    view! {\n        \u003cdiv class=\"max-w-md mx-auto mt-10 mt-3 p-5 bg-white rounded-lg shadow-lg\"\u003e\n            \u003cdiv class=\"flex justify-between\"\u003e\n\n                \u003ch1 class=\"text-4xl font-bold mb-4\"\u003eTodo List\u003c/h1\u003e\n\n                \u003cbutton on:click={move |_| add_new_todo(Todo::new_empty())} class=button_new_class\u003e\n                    \u003ci class=\"fa-solid fa-plus\"\u003e\u003c/i\u003e\n                \u003c/button\u003e\n\n            \u003c/div\u003e\n            \u003cFor\n                each=move || todos.get()\n                key=|state| (state.id.clone(), state.title.clone(), state.description.clone())\n                let:child\u003e\n                \u003cTodoItem todo=child delete=delete_todo edit=edit_todo/\u003e\n            \u003c/For\u003e\n\n            \u003cShow when = move || todos.get().is_empty()\u003e\n                \u003cdiv class=\"flex justify-between\"\u003e\n                    \u003ch2 class=\"text-2xl font-bold mb-4\"\u003eCurrently no Todos\u003c/h2\u003e\n                \u003c/div\u003e\n            \u003c/Show\u003e\n        \u003c/div\u003e\n\n        \u003cShow when = move || show_modal.get()\u003e\n            \u003cTodoModal todo=edit_todo_item on_close_modal=close_modal_todo/\u003e\n        \u003c/Show\u003e\n\n    }\n}\n```\n\nand include all files in mod.rs\n```rust\nmod app;\nmod todo_item;\nmod todo_modal;\n\npub use app::App;\npub use todo_item::TodoItem;\npub use todo_modal::TodoModal;\n```\n\nattaching the mod files in the main.rs\n```rust\nmod components;\nmod entities;\n\nuse crate::components::App;\nuse leptos::prelude::*;\n\nfn main() {\n    mount_to_body(App)\n}\n```\n\nstart the service\n\n```shell\ntrunk serve\n```\n\nWe have a Todo web application running.\n\n## Tauri\n\nWe could apply the command [cargo create-tauri-app] on a new application or [cargo tauri init] on an existing. \n\n```shell\ncargo tauri init\n```\n\nwith the following configuration:\n\n```text\n? What is your app name? › Tauri Todo App\n? What should the window title be? › Tauri Todo App\n? Where are your web assets (HTML/CSS/JS) located, relative to the \"\u003ccurrent dir\u003e/src-tauri/tauri.conf.json\" file that will be created? › ../dist\n? What is the url of your dev server? › http://localhost:1420\n? What is your frontend dev command? · trunk serve\n? What is your frontend build command? · trunk build\n```\n\nthat creates a new module in our project under the folder: src-tauri.\n\nIn the main module we add to Cargo.toml\n```toml\nwasm-bindgen = \"0.2\"\nwasm-bindgen-futures = \"0.4\"\njs-sys = \"0.3\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde-wasm-bindgen = \"0.6\"\nconsole_error_panic_hook = \"0.1.7\"\n\n[workspace]\nmembers = [\"src-tauri\"]\n```\n\nand we add to trunk.toml\n```toml\n[watch]\nignore = [\"./src-tauri\"]\n\n[serve]\nport = 1420\nopen = false\n```\nThe change of port is necessary for Tauri, as we defined during the Tauri generation: \"What is the url of your dev server: http://localhost:1420\".\n\nStart the application with:\n\n```shell\ncargo tauri dev\n```\n\nWe have the first run of the application on the Desktop. \n\n## Separate Backend / Frontend\n\nThe Frontend has limited capabilities, for this we transfer some parts of our application to the Backend, for example to include a communication to a Database. \n\nWe add some dependencies to the main cargo.toml\n\n```toml\n[dependencies]\nleptos = { version = \"0.7.3\", features = [\"csr\"] }\nuuid = { version = \"1.11.0\", features = [\"v4\", \"js\"] }\nchrono = { version = \"0.4.39\", features = [\"serde\", \"wasm-bindgen\"] }\nwasm-bindgen = { version = \"0.2.99\", features = [\"serde\"] }\nwasm-bindgen-futures = \"0.4.49\"\nweb-sys = \"0.3.76\"\njs-sys = \"0.3.76\"\nserde = { version = \"1.0.216\", features = [\"derive\"] }\nserde-wasm-bindgen = { version = \"0.6.5\"}\ngloo-utils = { version = \"0.2.0\", features = [\"serde\"] }\n```\n\nand to the cargo.toml in src-tauri:\n```toml\n[dependencies]\ntauri = { version = \"2.1.1\", features = [] }\ntauri-plugin-opener = \"2.2.2\"\nserde = { version = \"1.0.216\", features = [\"derive\"] }\nserde_json = { version = \"1.0.133\" }\nchrono = { version = \"0.4.39\", features = [\"serde\"] }\n```\n\nAs you can I switch for the timestamps to the chrono library as it had less problems for the serialization.\n\nIn the main.rs file from the Frontend we add the #[wasm_bindgen] binders:\n\n```rust\nmod components;\nmod entities;\n\nuse crate::components::App;\nuse leptos::prelude::*;\n\nuse wasm_bindgen::prelude::*;\n\n#[wasm_bindgen]\nextern \"C\" {\n    #[wasm_bindgen(js_namespace = [\"window\", \"__TAURI__\", \"core\"], js_name = invoke)]\n    async fn invoke_without_args(cmd: \u0026str) -\u003e JsValue;\n\n    #[wasm_bindgen(js_namespace = [\"window\", \"__TAURI__\", \"core\"])]\n    async fn invoke(cmd: \u0026str, args: JsValue) -\u003e JsValue;\n}\n\nfn main() {\n    mount_to_body(App)\n}\n```\n\nTwo binding methods, to communicate from the Frontend to the Backend.\n\nThe Todo Entity get modified:\n\n```rust\n#[derive(Clone, Debug, Serialize, Deserialize)]\npub struct Todo {\n    pub id: String,\n    pub title: String,\n    pub description: String,\n    pub created: DateTime\u003cUtc\u003e,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\nstruct TodoJsValue {\n    pub todo: Todo\n}\n\nimpl Todo {\n    pub fn new(title: String, description: String) -\u003e Self {\n        let id = Uuid::new_v4().to_string();\n        let created = Utc::now();\n        Todo { id, title, description, created }\n    }\n\n    pub fn new_empty() -\u003e Todo {\n        Self::new(\"\".to_string(), \"\".to_string())\n    }\n    pub fn js_value(\u0026self) -\u003e JsValue {\n        let container = TodoJsValue { todo: self.clone() };\n        JsValue::from_serde(\u0026container).unwrap()\n    }\n}\n```\n\nThe TodoJsValue is a container/wrapper to sent the Todo Data to the Backend by JsValue.\n\nWe can now modify the app.rs:\n\n```rust\nuse chrono::{DateTime, Utc};\nuse crate::components::*;\nuse crate::entities::*;\nuse leptos::prelude::*;\nuse crate::{invoke, invoke_without_args};\nuse gloo_utils::format::JsValueSerdeExt;\n\n#[derive(Debug, PartialEq)]\nenum Mode {\n    ADD,\n    EDIT\n}\n\nasync fn load_data(_trigger: DateTime\u003cUtc\u003e) -\u003e Vec\u003cTodo\u003e {\n    let rtn = invoke_without_args(\"get_todo_list\").await;\n    let todos = rtn.into_serde::\u003cVec\u003cTodo\u003e\u003e().unwrap();\n    todos\n}\n\n#[component]\npub fn App() -\u003e impl IntoView {\n    let show_modal: RwSignal\u003cbool\u003e = RwSignal::new(false);\n    let show_modal_mode: RwSignal\u003cMode\u003e = RwSignal::new(Mode::ADD);\n    let edit_todo_item: RwSignal\u003cTodo\u003e = RwSignal::new(Todo::new_empty());\n\n    let button_new_class = \"rounded-full pl-5 pr-5 bg-blue-700 text-white rounded hover:bg-blue-800\";\n\n    let refresh :RwSignal\u003cDateTime\u003cUtc\u003e\u003e = RwSignal::new(Utc::now());\n    let fetch_todos = LocalResource::new(move || load_data(refresh.get()));\n\n    let add_new_todo = move |x: Todo| {\n        edit_todo_item.set(x);\n        show_modal_mode.set(Mode::ADD);\n        show_modal.set(true);\n    };\n\n    let edit_todo = move |todo: Todo| {\n        edit_todo_item.set(todo);\n        show_modal_mode.set(Mode::EDIT);\n        show_modal.set(true);\n    };\n\n    let delete_todo = move |todo: Todo| {\n        leptos::task::spawn_local(async move {\n            let data = todo.js_value();\n            invoke(\"delete_todo\", data).await;\n            refresh.set(Utc::now());\n        });\n    };\n\n    let close_modal_todo = move |x: Option\u003cTodo\u003e| {\n        leptos::task::spawn_local(async move {\n            if show_modal_mode.read() == Mode::ADD {\n                let data = x.unwrap().js_value();\n                invoke(\"add_todo\", data).await;\n            } else {\n                let data = x.unwrap().js_value();\n                invoke(\"edit_todo\", data).await;\n            }\n            refresh.set(Utc::now());\n        });\n        show_modal.set(false);\n    };\n\n    view! {\n        \u003cdiv class=\"max-w-md mx-auto mt-10 mt-3 p-5 bg-white rounded-lg shadow-lg\"\u003e\n            \u003cdiv class=\"flex justify-between\"\u003e\n\n                \u003ch1 class=\"text-4xl font-bold mb-4\"\u003eTodo List\u003c/h1\u003e\n\n                \u003cbutton on:click={move |ev| {\n                        ev.prevent_default();\n                        add_new_todo(Todo::new_empty())\n                        }} class=button_new_class\u003e\n                    \u003ci class=\"fa-solid fa-plus\"\u003e\u003c/i\u003e\n                \u003c/button\u003e\n\n            \u003c/div\u003e\n\n            \u003cSuspense fallback=move || view! { \u003cp\u003e\"Loading...\"\u003c/p\u003e }\u003e\n\n            {move || Suspend::new(async move {\n            let todos = fetch_todos.await;\n            let todos_is_empty = todos.is_empty();\n\n            view!{\n            \u003cFor\n                each=move || todos.clone()\n                key=|state| (state.id.clone(), state.title.clone(), state.description.clone())\n                let:child\u003e\n                \u003cTodoItem todo=child delete=delete_todo edit=edit_todo/\u003e\n            \u003c/For\u003e\n\n            \u003cShow when = move || todos_is_empty\u003e\n                \u003cdiv class=\"flex justify-between\"\u003e\n                    \u003ch2 class=\"text-2xl font-bold mb-4\"\u003eCurrently no Todos\u003c/h2\u003e\n                \u003c/div\u003e\n            \u003c/Show\u003e\n\n            }})}\n            \u003c/Suspense\u003e\n        \u003c/div\u003e\n\n        \u003cShow when = move || show_modal.get()\u003e\n            \u003cTodoModal todo=edit_todo_item on_close_modal=close_modal_todo/\u003e\n        \u003c/Show\u003e\n\n    }\n}\n```\n\nThe call to the Backend are asynchronous, for this we use spawn_local and call the WASM invoke methods.\n\nFinally in the src-tauri backend part we include the business logic in the lib.rs\n\n```rust\n#[tauri::command]\nfn get_todo_list(storage: State\u003cStorage\u003e) -\u003e Vec\u003cTodo\u003e {\n    println!(\"Backend:get_todo_list\");\n    let s = storage.store.lock().unwrap();\n    s.to_vec()\n}\n\n#[tauri::command]\nfn add_todo(todo: Todo, storage: State\u003cStorage\u003e) {\n    println!(\"Backend:add_todo: {:?}\", todo);\n    let mut s = storage.store.lock().unwrap();\n    s.push(todo);\n}\n\n#[tauri::command]\nfn edit_todo(todo: Todo, storage: State\u003cStorage\u003e) {\n    println!(\"Backend:edit_todo\");\n    let mut s = storage.store.lock().unwrap();\n    s.retain(|x| x.id != todo.id);\n    s.push(todo);\n}\n\n#[tauri::command]\nfn delete_todo(todo: Todo, storage: State\u003cStorage\u003e) {\n    println!(\"Backend:delete_todo\");\n    let mut s = storage.store.lock().unwrap();\n    s.retain(|x| x.id != todo.id);\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\npub struct Todo {\n    pub id: String,\n    pub title: String,\n    pub description: String,\n    pub created: DateTime\u003cUtc\u003e\n}\n\n#[derive(Debug)]\nstruct Storage {\n    store: Mutex\u003cVec\u003cTodo\u003e\u003e,\n}\n// To evaluate in the future\n// #[cfg_attr(mobile, tauri::mobile_entry_point)]\npub fn run() {\n    tauri::Builder::default()\n        .plugin(tauri_plugin_opener::init())\n        .manage(Storage { store: Default::default() })\n        .invoke_handler(tauri::generate_handler![\n            get_todo_list,\n            add_todo,\n            edit_todo,\n            delete_todo,\n        ])\n        .run(tauri::generate_context!())\n        .expect(\"error while running tauri application\");\n}\n```\nand declare the different commands.\n\n## Final Build\n\nWe can now do the final build:\n\n```shell\ncargo tauri build\n```\n\nand run for your machine the correspondent file: target/release/bundle","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foxide-byte%2Ftodo-tauri","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foxide-byte%2Ftodo-tauri","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foxide-byte%2Ftodo-tauri/lists"}