185 lines
4.6 KiB
Rust
185 lines
4.6 KiB
Rust
use std::str::FromStr;
|
|
|
|
use crate::components::Block;
|
|
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,
|
|
$(
|
|
p {
|
|
($name) ": " ($prop.edit($cx))
|
|
}
|
|
)*
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! link_fields {
|
|
($cx:ident, $($field:ident),* $(,)? => $state:ident as $t:ident) => {
|
|
$(let $field = create_signal($cx, $state.get().$field.clone());)*
|
|
|
|
create_effect($cx, || {
|
|
$state.set($t {
|
|
$($field: $field.get().as_ref().clone(),)*
|
|
..Default::default()
|
|
});
|
|
});
|
|
};
|
|
}
|
|
|
|
macro_rules! edit_struct {
|
|
($struct:ident => $(($name:expr, $prop:ident)),* $(,)?) => {
|
|
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>]);
|
|
}
|
|
};
|
|
}
|
|
|
|
#[derive(Prop)]
|
|
pub struct EditProps<'a, T> {
|
|
pub state: &'a Signal<T>,
|
|
}
|
|
|
|
impl<'a, T> From<&'a Signal<T>> for EditProps<'a, T> {
|
|
fn from(state: &'a Signal<T>) -> Self {
|
|
EditProps { state }
|
|
}
|
|
}
|
|
|
|
pub trait IntoEdit<'a, G: Html> {
|
|
fn edit(self, cx: Scope<'a>) -> View<G>;
|
|
}
|
|
|
|
impl<'a, G: Html, T: Editable<'a, G>> IntoEdit<'a, G> for &'a Signal<T>
|
|
where
|
|
EditProps<'a, T>: From<&'a Signal<T>>,
|
|
{
|
|
fn edit(self, cx: Scope<'a>) -> View<G> {
|
|
T::edit(cx, self.into())
|
|
}
|
|
}
|
|
|
|
pub trait Edit<'a, G: Html>: Sized {
|
|
fn edit(cx: Scope<'a>, props: EditProps<'a, Self>) -> View<G>;
|
|
}
|
|
|
|
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> {
|
|
E::edit(cx, props)
|
|
}
|
|
}
|
|
|
|
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(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);
|