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:
parent
5f0317c0fa
commit
d9988fbf6c
|
@ -4,9 +4,9 @@
|
|||
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<title>Yew App</title>
|
||||
|
||||
<link rel="preload" href="/index-fe4ec1b3239a32ea_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<link rel="modulepreload" href="/index-fe4ec1b3239a32ea.js"></head>
|
||||
<link rel="preload" href="/index-15f6ea5300c4a038_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<link rel="modulepreload" href="/index-15f6ea5300c4a038.js"></head>
|
||||
<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>
|
|
@ -1,7 +1,23 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
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 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 {
|
||||
($cx:ident, $(($name:expr, $prop:expr)),* $(,)?) => {
|
||||
view! { $cx,
|
||||
|
@ -19,7 +35,7 @@ macro_rules! link_fields {
|
|||
$(let $field = create_signal($cx, $state.get().$field.clone());)*
|
||||
|
||||
create_effect($cx, || {
|
||||
$state.set(Self {
|
||||
$state.set($t {
|
||||
$($field: $field.get().as_ref().clone(),)*
|
||||
..Default::default()
|
||||
});
|
||||
|
@ -29,16 +45,22 @@ macro_rules! link_fields {
|
|||
|
||||
macro_rules! edit_struct {
|
||||
($struct:ident => $(($name:expr, $prop:ident)),* $(,)?) => {
|
||||
impl<'a, G: Html> Edit<'a, G> for $struct {
|
||||
fn edit(cx: Scope<'a>, props: EditProps<'a, Self>) -> View<G> {
|
||||
let state = props.state;
|
||||
link_fields!(cx, $($prop,)* => state as Self);
|
||||
view! { cx,
|
||||
Block(title=stringify!($struct).into()) {
|
||||
(edit_fields!(cx, $(($name, $prop),)*))
|
||||
paste! {
|
||||
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;
|
||||
link_fields!(cx, $($prop,)* => state as $struct);
|
||||
view! { cx,
|
||||
Block(title=stringify!($struct).into()) {
|
||||
(edit_fields!(cx, $(($name, $prop),)*))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
editable!($struct => [<$struct Edit>]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -58,7 +80,7 @@ pub trait IntoEdit<'a, G: Html> {
|
|||
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
|
||||
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>;
|
||||
}
|
||||
|
||||
edit_struct!(EventSpec => ("Name", name));
|
||||
|
||||
/*
|
||||
impl<'a, G: Html> Edit<'a, G> for EventSpec {
|
||||
impl<'a, G: Html, E: Editor<'a, G, Type>, Type: Editable<'a, G, Editor = E>> Edit<'a, G> for Type {
|
||||
fn edit(cx: Scope<'a>, props: EditProps<'a, Self>) -> View<G> {
|
||||
let state = props.state;
|
||||
link_fields!(cx, name => state as Self);
|
||||
edit_fields!(cx, ("Name", name))
|
||||
E::edit(cx, props)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl<'a, G: Html> Edit<'a, G> for String {
|
||||
fn edit(cx: Scope<'a>, props: EditProps<'a, Self>) -> View<G> {
|
||||
pub trait Editor<'a, G: Html, Type>: Sized {
|
||||
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,
|
||||
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);
|
||||
|
|
|
@ -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)]
|
||||
pub fn loading() -> Html {
|
||||
|
|
|
@ -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 log::debug;
|
||||
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") {
|
||||
(if Some(&user.name) == (*score_edit.get()).as_ref() { view! { cx,
|
||||
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)
|
||||
}} else { view! { cx,
|
||||
|
@ -139,7 +139,7 @@ pub fn UsersPage<'a, G: Html>(cx: Scope<'a>) -> View<G> {
|
|||
tr {
|
||||
td(class="whatespace-nowrap px-3 text-sm") {
|
||||
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") {}
|
||||
|
|
Loading…
Reference in New Issue