Browse Source

Unify todomvc and todomvc-native examples (#4228)

Signed-off-by: Nico Burns <nico@nicoburns.com>
Nico Burns 3 weeks ago
parent
commit
f865e3f559
3 changed files with 5 additions and 648 deletions
  1. 0 379
      examples/assets/todomvc-native.css
  2. 5 0
      examples/assets/todomvc.css
  3. 0 269
      examples/todomvc-native.rs

+ 0 - 379
examples/assets/todomvc-native.css

@@ -1,379 +0,0 @@
-html,
-body, pre {
-    margin: 0;
-    padding: 0;
-}
-
-button {
-    margin: 0;
-    padding: 0;
-    border: 0;
-    background: none;
-    font-size: 100%;
-    vertical-align: baseline;
-    font-family: inherit;
-    font-weight: inherit;
-    color: inherit;
-    -webkit-appearance: none;
-    appearance: none;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-}
-
-body {
-    font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
-    line-height: 1.4em;
-    background: #f5f5f5;
-    color: #4d4d4d;
-    min-width: 230px;
-    max-width: 550px;
-    margin: 0 auto;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-    font-weight: 300;
-}
-
-:focus {
-    outline: 0;
-}
-
-.hidden {
-    display: none;
-}
-
-.todoapp {
-    background: #fff;
-    margin: 130px 0 40px 0;
-    position: relative;
-    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
-}
-
-.todoapp input::-webkit-input-placeholder {
-    font-style: italic;
-    font-weight: 300;
-    color: #e6e6e6;
-}
-
-.todoapp input::-moz-placeholder {
-    font-style: italic;
-    font-weight: 300;
-    color: #e6e6e6;
-}
-
-.todoapp input::input-placeholder {
-    font-style: italic;
-    font-weight: 300;
-    color: #e6e6e6;
-}
-
-.todoapp h1 {
-    position: absolute;
-    top: -155px;
-    width: 100%;
-    font-size: 100px;
-    font-weight: 100;
-    text-align: center;
-    color: rgba(175, 47, 47, 1.0);
-    -webkit-text-rendering: optimizeLegibility;
-    -moz-text-rendering: optimizeLegibility;
-    text-rendering: optimizeLegibility;
-}
-
-.new-todo,
-.edit {
-    position: relative;
-    margin: 0;
-    width: 100%;
-    font-size: 24px;
-    font-family: inherit;
-    font-weight: inherit;
-    line-height: 1.4em;
-    border: 0;
-    color: inherit;
-    padding: 6px;
-    border: 1px solid #999;
-    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-    box-sizing: border-box;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-}
-
-.new-todo {
-    padding: 16px 16px 16px 60px;
-    border: none;
-    background: rgba(0, 0, 0, 0.003);
-    box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
-}
-
-.main {
-    position: relative;
-    z-index: 2;
-    border-top: 1px solid #e6e6e6;
-}
-
-.toggle-all {
-    text-align: center;
-    border: none;
-    /* Mobile Safari */
-    opacity: 0;
-    position: absolute;
-}
-
-.toggle-all+label {
-    width: 60px;
-    height: 34px;
-    font-size: 0;
-    position: absolute;
-    top: -52px;
-    left: -13px;
-    -webkit-transform: rotate(90deg);
-    transform: rotate(90deg);
-}
-
-.toggle-all+label:before {
-    content: '❯';
-    font-size: 22px;
-    color: #e6e6e6;
-    padding: 10px 27px 10px 27px;
-}
-
-.toggle-all:checked+label:before {
-    color: #737373;
-}
-
-.todo-list {
-    margin: 0;
-    padding: 0;
-    list-style: none;
-}
-
-.todo-list li {
-    position: relative;
-    font-size: 24px;
-    border-bottom: 1px solid #ededed;
-}
-
-.todo-list li:last-child {
-    border-bottom: none;
-}
-
-.todo-list li.editing {
-    border-bottom: none;
-    padding: 0;
-}
-
-.todo-list li.editing .edit {
-    display: block;
-    width: 506px;
-    padding: 12px 16px;
-    margin: 0 0 0 43px;
-}
-
-.todo-list li.editing .view {
-    display: none;
-}
-
-.todo-list li .toggle {
-    text-align: center;
-    width: 40px;
-    /* auto, since non-WebKit browsers doesn't support input styling */
-    height: auto;
-    position: absolute;
-    top: 0;
-    bottom: 0;
-    margin: auto 0;
-    border: none;
-    /* Mobile Safari */
-    -webkit-appearance: none;
-    appearance: none;
-}
-
-.todo-list li .toggle {
-    opacity: 0;
-}
-
-.todo-list li .toggle+label {
-    /*
-		Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
-		IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
-	*/
-    background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
-    background-repeat: no-repeat;
-    background-position: center left;
-}
-
-.todo-list li .toggle:checked+label {
-    background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
-}
-
-.todo-list li label {
-    word-break: break-all;
-    padding: 15px 15px 15px 60px;
-    display: block;
-    line-height: 1.2;
-    transition: color 0.4s;
-}
-
-.todo-list li.completed label {
-    color: #d9d9d9;
-    text-decoration: line-through;
-}
-
-.todo-list li .destroy {
-    display: none;
-    position: absolute;
-    top: 0;
-    right: 10px;
-    bottom: 0;
-    width: 40px;
-    height: 40px;
-    margin: auto 0;
-    font-size: 30px;
-    color: #cc9a9a;
-    margin-bottom: 11px;
-    transition: color 0.2s ease-out;
-}
-
-.todo-list li .destroy:hover {
-    color: #af5b5e;
-}
-
-.todo-list li .destroy:after {
-    content: '×';
-}
-
-.todo-list li:hover .destroy {
-    display: block;
-}
-
-.todo-list li .edit {
-    display: none;
-}
-
-.todo-list li.editing:last-child {
-    margin-bottom: -1px;
-}
-
-.footer {
-    color: #777;
-    padding: 10px 15px;
-    height: 20px;
-    text-align: center;
-    border-top: 1px solid #e6e6e6;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-}
-
-.footer:before {
-    content: '';
-    position: absolute;
-    right: 0;
-    bottom: 0;
-    left: 0;
-    height: 50px;
-    overflow: hidden;
-    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2);
-}
-
-.todo-count {
-    float: left;
-    text-align: left;
-    white-space-collapse: preserve;
-}
-
-.todo-count strong {
-    font-weight: 300;
-}
-
-.filters {
-    margin: 0;
-    padding: 0;
-    list-style: none;
-    position: absolute;
-    right: 0;
-    left: 0;
-}
-
-.filters li {
-    display: inline-block;
-}
-
-.filters li a {
-    display: inline-block;
-    color: inherit;
-    margin: 3px;
-    padding: 3px 7px;
-    text-decoration: none;
-    border: 1px solid transparent;
-    border-radius: 3px;
-}
-
-.filters li a:hover {
-    border-color: rgba(175, 47, 47, 0.1);
-}
-
-.filters li a.selected {
-    border-color: rgba(175, 47, 47, 0.2);
-}
-
-.clear-completed,
-html .clear-completed:active {
-    float: right;
-    position: relative;
-    line-height: 20px;
-    text-decoration: none;
-    cursor: pointer;
-    display: inline-block;
-}
-
-.clear-completed:hover {
-    text-decoration: underline;
-}
-
-.info {
-    margin: 65px auto 0;
-    color: #bfbfbf;
-    font-size: 10px;
-    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
-    text-align: center;
-}
-
-.info p {
-    line-height: 1;
-}
-
-.info a {
-    color: inherit;
-    text-decoration: none;
-    font-weight: 400;
-}
-
-.info a:hover {
-    text-decoration: underline;
-}
-
-
-/*
-	Hack to remove background from Mobile Safari.
-	Can't use it globally since it destroys checkboxes in Firefox
-*/
-
-@media screen and (-webkit-min-device-pixel-ratio:0) {
-    .toggle-all,
-    .todo-list li .toggle {
-        background: none;
-    }
-    .todo-list li .toggle {
-        height: 40px;
-    }
-}
-
-@media (max-width: 430px) {
-    .footer {
-        height: 50px;
-    }
-    .filters {
-        bottom: 10px;
-    }
-}

+ 5 - 0
examples/assets/todomvc.css

@@ -255,6 +255,9 @@ body {
 }
 
 .footer {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
     color: #777;
     padding: 10px 15px;
     height: 20px;
@@ -276,6 +279,7 @@ body {
 .todo-count {
     float: left;
     text-align: left;
+    white-space-collapse: preserve;
 }
 
 .todo-count strong {
@@ -296,6 +300,7 @@ body {
 }
 
 .filters li a {
+    display: inline-block;
     color: inherit;
     margin: 3px;
     padding: 3px 7px;

+ 0 - 269
examples/todomvc-native.rs

@@ -1,269 +0,0 @@
-//! The typical TodoMVC app, implemented in Dioxus.
-
-use dioxus::prelude::*;
-use std::collections::HashMap;
-
-const STYLE: Asset = asset!("/examples/assets/todomvc-native.css");
-
-fn main() {
-    dioxus::launch(app);
-}
-
-#[derive(PartialEq, Eq, Clone, Copy)]
-enum FilterState {
-    All,
-    Active,
-    Completed,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-struct TodoItem {
-    id: u32,
-    checked: bool,
-    contents: String,
-}
-
-fn app() -> Element {
-    // We store the todos in a HashMap in a Signal.
-    // Each key is the id of the todo, and the value is the todo itself.
-    let mut todos = use_signal(HashMap::<u32, TodoItem>::new);
-
-    let filter = use_signal(|| FilterState::All);
-
-    // We use a simple memoized signal to calculate the number of active todos.
-    // Whenever the todos change, the active_todo_count will be recalculated.
-    let active_todo_count =
-        use_memo(move || todos.read().values().filter(|item| !item.checked).count());
-
-    // We use a memoized signal to filter the todos based on the current filter state.
-    // Whenever the todos or filter change, the filtered_todos will be recalculated.
-    // Note that we're only storing the IDs of the todos, not the todos themselves.
-    let filtered_todos = use_memo(move || {
-        let mut filtered_todos = todos
-            .read()
-            .iter()
-            .filter(|(_, item)| match filter() {
-                FilterState::All => true,
-                FilterState::Active => !item.checked,
-                FilterState::Completed => item.checked,
-            })
-            .map(|f| *f.0)
-            .collect::<Vec<_>>();
-
-        filtered_todos.sort_unstable();
-
-        filtered_todos
-    });
-
-    // Toggle all the todos to the opposite of the current state.
-    // If all todos are checked, uncheck them all. If any are unchecked, check them all.
-    let toggle_all = move |_| {
-        let check = active_todo_count() != 0;
-        for (_, item) in todos.write().iter_mut() {
-            item.checked = check;
-        }
-    };
-
-    rsx! {
-        document::Link { rel: "stylesheet", href: STYLE }
-        body {
-            section { class: "todoapp",
-                TodoHeader { todos }
-                section { class: "main",
-                    if !todos.read().is_empty() {
-                        input {
-                            id: "toggle-all",
-                            class: "toggle-all",
-                            r#type: "checkbox",
-                            onchange: toggle_all,
-                            checked: active_todo_count() == 0
-                        }
-                        label { r#for: "toggle-all" }
-                    }
-
-                    // Render the todos using the filtered_todos signal
-                    // We pass the ID into the TodoEntry component so it can access the todo from the todos signal.
-                    // Since we store the todos in a signal too, we also need to send down the todo list
-                    ul { class: "todo-list",
-                        for id in filtered_todos() {
-                            TodoEntry { key: "{id}", id, todos }
-                        }
-                    }
-
-                    // We only show the footer if there are todos.
-                    if !todos.read().is_empty() {
-                        ListFooter { active_todo_count, todos, filter }
-                    }
-                }
-            }
-
-            // A simple info footer
-            footer { class: "info",
-                p { "Double-click to edit a todo" }
-                p {
-                    "Created by "
-                    a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }
-                }
-                p {
-                    "Part of "
-                    a { href: "http://todomvc.com", "TodoMVC" }
-                }
-            }
-        }
-    }
-}
-
-#[component]
-fn TodoHeader(mut todos: Signal<HashMap<u32, TodoItem>>) -> Element {
-    let mut draft = use_signal(|| "".to_string());
-    let mut todo_id = use_signal(|| 0);
-
-    let onkeydown = move |evt: KeyboardEvent| {
-        if evt.key() == Key::Enter && !draft.read().is_empty() {
-            let id = todo_id();
-            let todo = TodoItem {
-                id,
-                checked: false,
-                contents: draft.to_string(),
-            };
-            todos.write().insert(id, todo);
-            todo_id += 1;
-            draft.set("".to_string());
-            evt.prevent_default();
-        }
-    };
-
-    rsx! {
-        header { class: "header",
-            h1 { "todos" }
-            input {
-                class: "new-todo",
-                r#type: "text",
-                placeholder: "What needs to be done?",
-                value: "{draft}",
-                autofocus: "true",
-                oninput: move |evt| draft.set(evt.value()),
-                onkeydown
-            }
-        }
-    }
-}
-
-/// A single todo entry
-/// This takes the ID of the todo and the todos signal as props
-/// We can use these together to memoize the todo contents and checked state
-#[component]
-fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
-    let mut is_editing = use_signal(|| false);
-
-    // To avoid re-rendering this component when the todo list changes, we isolate our reads to memos
-    // This way, the component will only re-render when the contents of the todo change, or when the editing state changes.
-    // This does involve taking a local clone of the todo contents, but it allows us to prevent this component from re-rendering
-    let checked = use_memo(move || todos.read().get(&id).unwrap().checked);
-    let contents = use_memo(move || todos.read().get(&id).unwrap().contents.clone());
-
-    rsx! {
-        li {
-            // Dioxus lets you use if statements in rsx to conditionally render attributes
-            // These will get merged into a single class attribute
-            class: if checked() { "completed" },
-            class: if is_editing() { "editing" },
-
-            // Some basic controls for the todo
-            div { class: "view",
-                input {
-                    class: "toggle",
-                    r#type: "checkbox",
-                    id: "cbg-{id}",
-                    checked: "{checked}",
-                    oninput: move |evt| todos.write().get_mut(&id).unwrap().checked = evt.checked()
-                }
-                label {
-                    r#for: "cbg-{id}",
-                    onclick: move |evt| {
-                        is_editing.set(true);
-                        evt.prevent_default()
-                    },
-                    "{contents}"
-                }
-                button {
-                    class: "destroy",
-                    onclick: move |evt| {
-                        todos.write().remove(&id);
-                        evt.prevent_default();
-                    },
-                }
-            }
-
-            // Only render the actual input if we're editing
-            if is_editing() {
-                input {
-                    class: "edit",
-                    r#type: "text",
-                    value: "{contents}",
-                    oninput: move |evt| todos.write().get_mut(&id).unwrap().contents = evt.value(),
-                    autofocus: "true",
-                    onfocusout: move |_| is_editing.set(false),
-                    onkeydown: move |evt| {
-                        if matches!(evt.key(), Key::Enter | Key::Escape | Key::Tab) {
-                            evt.prevent_default();
-                            is_editing.set(false);
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-#[component]
-fn ListFooter(
-    mut todos: Signal<HashMap<u32, TodoItem>>,
-    active_todo_count: ReadOnlySignal<usize>,
-    mut filter: Signal<FilterState>,
-) -> Element {
-    // We use a memoized signal to calculate whether we should show the "Clear completed" button.
-    // This will recompute whenever the todos change, and if the value is true, the button will be shown.
-    let show_clear_completed = use_memo(move || todos.read().values().any(|todo| todo.checked));
-
-    rsx! {
-        footer { class: "footer",
-            span { class: "todo-count",
-                strong { "{active_todo_count} " }
-                span {
-                    match active_todo_count() {
-                        1 => "item",
-                        _ => "items",
-                    },
-                    " left"
-                }
-            }
-            ul { class: "filters",
-                for (state , state_text , url) in [
-                    (FilterState::All, "All", "#/"),
-                    (FilterState::Active, "Active", "#/active"),
-                    (FilterState::Completed, "Completed", "#/completed"),
-                ] {
-                    li {
-                        a {
-                            href: url,
-                            class: if filter() == state { "selected" },
-                            onclick: move |evt| {
-                                filter.set(state);
-                                evt.prevent_default();
-                            },
-                            {state_text}
-                        }
-                    }
-                }
-            }
-            if show_clear_completed() {
-                button {
-                    class: "clear-completed",
-                    onclick: move |_| todos.write().retain(|_, todo| !todo.checked),
-                    "Clear completed"
-                }
-            }
-        }
-    }
-}