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">
|
<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>
|
|
@ -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,16 +45,22 @@ 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>];
|
||||||
let state = props.state;
|
|
||||||
link_fields!(cx, $($prop,)* => state as Self);
|
impl<'a, G: Html> Editor<'a, G, $struct> for [<$struct Edit>] {
|
||||||
view! { cx,
|
fn edit(cx: Scope<'a>, props: EditProps<'a, $struct>) -> View<G> {
|
||||||
Block(title=stringify!($struct).into()) {
|
let state = props.state;
|
||||||
(edit_fields!(cx, $(($name, $prop),)*))
|
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>;
|
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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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") {}
|
||||||
|
|
Loading…
Reference in New Issue