I introduce: Manual-Automatic Trait Implementation

- Because automatic doesn't allow overlap
- And manual is, well, too manual for me
This commit is contained in:
Daan Vanoverloop 2022-09-07 19:13:08 +02:00
parent 5f0317c0fa
commit d9988fbf6c
Signed by: Danacus
GPG Key ID: F2272B50E129FC5C
4 changed files with 123 additions and 27 deletions

6
web/dist/index.html vendored
View File

@ -4,9 +4,9 @@
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css" rel="stylesheet">
<title>Yew App</title> <title>Yew App</title>
<link rel="preload" href="/index-fe4ec1b3239a32ea_bg.wasm" as="fetch" type="application/wasm" crossorigin=""> <link rel="preload" href="/index-15f6ea5300c4a038_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
<link rel="modulepreload" href="/index-fe4ec1b3239a32ea.js"></head> <link rel="modulepreload" href="/index-15f6ea5300c4a038.js"></head>
<body class="base theme-dark bg-gray-900 text-gray-400"> <body class="base theme-dark bg-gray-900 text-gray-400">
<script type="module">import init from '/index-fe4ec1b3239a32ea.js';init('/index-fe4ec1b3239a32ea_bg.wasm');</script></body></html> <script type="module">import init from '/index-15f6ea5300c4a038.js';init('/index-15f6ea5300c4a038_bg.wasm');</script></body></html>

View File

@ -1,7 +1,23 @@
use std::str::FromStr;
use crate::components::Block; use crate::components::Block;
use lan_party_core::event::EventSpec; use lan_party_core::event::{
free_for_all_game::FreeForAllGameSpec, team_game::TeamGameSpec, test::TestSpec, EventSpec,
EventTypeSpec,
};
use paste::paste;
use sycamore::prelude::*; use sycamore::prelude::*;
use super::input_classes;
macro_rules! editable {
($type:ty => $editor:ty) => {
impl<'a, G: Html> Editable<'a, G> for $type {
type Editor = $editor;
}
};
}
macro_rules! edit_fields { macro_rules! edit_fields {
($cx:ident, $(($name:expr, $prop:expr)),* $(,)?) => { ($cx:ident, $(($name:expr, $prop:expr)),* $(,)?) => {
view! { $cx, view! { $cx,
@ -19,7 +35,7 @@ macro_rules! link_fields {
$(let $field = create_signal($cx, $state.get().$field.clone());)* $(let $field = create_signal($cx, $state.get().$field.clone());)*
create_effect($cx, || { create_effect($cx, || {
$state.set(Self { $state.set($t {
$($field: $field.get().as_ref().clone(),)* $($field: $field.get().as_ref().clone(),)*
..Default::default() ..Default::default()
}); });
@ -29,10 +45,13 @@ macro_rules! link_fields {
macro_rules! edit_struct { macro_rules! edit_struct {
($struct:ident => $(($name:expr, $prop:ident)),* $(,)?) => { ($struct:ident => $(($name:expr, $prop:ident)),* $(,)?) => {
impl<'a, G: Html> Edit<'a, G> for $struct { paste! {
fn edit(cx: Scope<'a>, props: EditProps<'a, Self>) -> View<G> { pub struct [<$struct Edit>];
impl<'a, G: Html> Editor<'a, G, $struct> for [<$struct Edit>] {
fn edit(cx: Scope<'a>, props: EditProps<'a, $struct>) -> View<G> {
let state = props.state; let state = props.state;
link_fields!(cx, $($prop,)* => state as Self); link_fields!(cx, $($prop,)* => state as $struct);
view! { cx, view! { cx,
Block(title=stringify!($struct).into()) { Block(title=stringify!($struct).into()) {
(edit_fields!(cx, $(($name, $prop),)*)) (edit_fields!(cx, $(($name, $prop),)*))
@ -40,6 +59,9 @@ macro_rules! edit_struct {
} }
} }
} }
editable!($struct => [<$struct Edit>]);
}
}; };
} }
@ -58,7 +80,7 @@ pub trait IntoEdit<'a, G: Html> {
fn edit(self, cx: Scope<'a>) -> View<G>; fn edit(self, cx: Scope<'a>) -> View<G>;
} }
impl<'a, G: Html, T: Edit<'a, G>> IntoEdit<'a, G> for &'a Signal<T> impl<'a, G: Html, T: Editable<'a, G>> IntoEdit<'a, G> for &'a Signal<T>
where where
EditProps<'a, T>: From<&'a Signal<T>>, EditProps<'a, T>: From<&'a Signal<T>>,
{ {
@ -71,22 +93,92 @@ pub trait Edit<'a, G: Html>: Sized {
fn edit(cx: Scope<'a>, props: EditProps<'a, Self>) -> View<G>; fn edit(cx: Scope<'a>, props: EditProps<'a, Self>) -> View<G>;
} }
edit_struct!(EventSpec => ("Name", name)); impl<'a, G: Html, E: Editor<'a, G, Type>, Type: Editable<'a, G, Editor = E>> Edit<'a, G> for Type {
/*
impl<'a, G: Html> Edit<'a, G> for EventSpec {
fn edit(cx: Scope<'a>, props: EditProps<'a, Self>) -> View<G> { fn edit(cx: Scope<'a>, props: EditProps<'a, Self>) -> View<G> {
let state = props.state; E::edit(cx, props)
link_fields!(cx, name => state as Self);
edit_fields!(cx, ("Name", name))
} }
} }
*/
impl<'a, G: Html> Edit<'a, G> for String { pub trait Editor<'a, G: Html, Type>: Sized {
fn edit(cx: Scope<'a>, props: EditProps<'a, Self>) -> View<G> { fn edit(cx: Scope<'a>, props: EditProps<'a, Type>) -> View<G>;
}
pub trait Editable<'a, G: Html>: Sized {
type Editor: Editor<'a, G, Self>;
}
edit_struct!(EventSpec => ("Name", name), ("Description", description), ("Event type", event_type));
edit_struct!(TestSpec => ("Number of players", num_players));
edit_struct!(TeamGameSpec => );
edit_struct!(FreeForAllGameSpec => );
pub struct StringEdit;
impl<'a, G: Html> Editor<'a, G, String> for StringEdit {
fn edit(cx: Scope<'a>, props: EditProps<'a, String>) -> View<G> {
view! { cx, view! { cx,
input(bind:value=props.state) input(class=input_classes(), bind:value=props.state)
} }
} }
} }
editable!(String => StringEdit);
pub struct StubEdit;
impl<'a, G: Html, T> Editor<'a, G, T> for StubEdit
where
T: Editable<'a, G, Editor = StubEdit>,
{
fn edit(cx: Scope<'a>, _props: EditProps<'a, T>) -> View<G> {
view! { cx,
"Unimplemented"
}
}
}
editable!(EventTypeSpec => StubEdit);
pub struct InputEdit;
impl<'a, G: Html, T> Editor<'a, G, T> for InputEdit
where
T: Editable<'a, G, Editor = InputEdit> + FromStr + ToString + Default,
{
fn edit(cx: Scope<'a>, props: EditProps<'a, T>) -> View<G> {
let value = create_signal(cx, props.state.get().to_string());
create_memo(cx, || {
props
.state
.set((*value.get()).parse().unwrap_or(T::default()))
});
view! { cx,
input(class=input_classes(), bind:value=value)
}
}
}
editable!(i64 => InputEdit);
editable!(i32 => InputEdit);
editable!(isize => InputEdit);
editable!(u64 => InputEdit);
editable!(u32 => InputEdit);
editable!(usize => InputEdit);
editable!(f64 => InputEdit);
editable!(f32 => InputEdit);
pub struct BoolEdit;
impl<'a, G: Html> Editor<'a, G, bool> for BoolEdit {
fn edit(cx: Scope<'a>, props: EditProps<'a, bool>) -> View<G> {
view! { cx,
input(type="checkbox", bind:checked=props.state)
}
}
}
editable!(bool => BoolEdit);

View File

@ -111,6 +111,10 @@ pub fn Block<'a, G: Html>(cx: Scope<'a>, props: BlockProps<'a, G>) -> View<G> {
} }
} }
pub fn input_classes() -> &'static str {
"mx-2 appearance-none block bg-gray-700 text-slate-400 border border-gray-600 rounded py-2 px-2 w-50 leading-tight focus:outline-none focus:ring-1 focus:ring-gray-500 focus:border-gray-500"
}
/* /*
#[function_component(Loading)] #[function_component(Loading)]
pub fn loading() -> Html { pub fn loading() -> Html {

View File

@ -1,4 +1,4 @@
use crate::components::{Button, Page, Table}; use crate::components::{input_classes, Button, Page, Table};
use lan_party_core::user::User; use lan_party_core::user::User;
use log::debug; use log::debug;
use reqwasm::http::Method; use reqwasm::http::Method;
@ -112,7 +112,7 @@ pub fn UsersPage<'a, G: Html>(cx: Scope<'a>) -> View<G> {
td(class="whatespace-nowrap px-3 text-sm") { td(class="whatespace-nowrap px-3 text-sm") {
(if Some(&user.name) == (*score_edit.get()).as_ref() { view! { cx, (if Some(&user.name) == (*score_edit.get()).as_ref() { view! { cx,
span(class="inline-block") { span(class="inline-block") {
input(bind:value=new_score, class="mx-2 appearance-none block bg-gray-700 text-slate-400 border border-gray-600 rounded py-2 px-2 w-20 leading-tight focus:outline-none focus:ring-1 focus:ring-gray-500 focus:border-gray-500") input(bind:value=new_score, class=input_classes())
} }
Button(icon="mdi-check".into(), onclick=oncheck) Button(icon="mdi-check".into(), onclick=oncheck)
}} else { view! { cx, }} else { view! { cx,
@ -139,7 +139,7 @@ pub fn UsersPage<'a, G: Html>(cx: Scope<'a>) -> View<G> {
tr { tr {
td(class="whatespace-nowrap px-3 text-sm") { td(class="whatespace-nowrap px-3 text-sm") {
span(class="inline-block") { span(class="inline-block") {
input(bind:value=new_username, class="mx-2 appearance-none block bg-gray-700 text-slate-400 border border-gray-600 rounded py-2 px-2 w-50 leading-tight focus:outline-none focus:ring-1 focus:ring-gray-500 focus:border-gray-500") input(bind:value=new_username, class=input_classes())
} }
} }
td(class="whatespace-nowrap px-3 py-4 text-sm") {} td(class="whatespace-nowrap px-3 py-4 text-sm") {}