diff --git a/Cargo.lock b/Cargo.lock index dc828f4..deb8862 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1158,6 +1158,7 @@ version = "0.1.0" dependencies = [ "displaydoc", "lan_party_macros", + "log", "paste", "rocket", "schemars", diff --git a/core/Cargo.toml b/core/Cargo.toml index 426c65f..74ac2f4 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -21,3 +21,4 @@ lan_party_macros = { path = "../macros", optional = true } paste = "1" thiserror = "1.0" displaydoc = "0.2" +log = "0.4" diff --git a/core/src/edit.rs b/core/src/edit.rs index f335761..a4fce2a 100644 --- a/core/src/edit.rs +++ b/core/src/edit.rs @@ -6,6 +6,7 @@ use std::{ }; use crate::components::Block; +use log::debug; use paste::paste; use sycamore::prelude::*; @@ -262,7 +263,7 @@ pub struct VecEdit; impl<'a, G, T, I> Editor<'a, G, I> for VecEdit where G: Html, - T: for<'b> Editable<'b, G> + Clone + PartialEq + Default + 'a, + T: for<'b> Editable<'b, G> + Clone + PartialEq + Default + std::fmt::Debug + 'a, I: IntoIterator + FromIterator + Clone, { fn edit(cx: Scope<'a>, props: EditProps<'a, I>) -> View { @@ -290,6 +291,17 @@ where }); let onadd = move |_| vec.modify().push(create_signal(cx, T::default())); + let onremove = move |item: &'a Signal| { + move |_| { + let cloned = vec.get().as_ref().clone(); + vec.set( + cloned + .into_iter() + .filter(|x| x.get().as_ref() != item.get().as_ref()) + .collect(), + ); + } + }; Block( cx, @@ -297,62 +309,30 @@ where title: "List".into(), children: Children::new(cx, move |_| { view! { cx, - //Block(title="List".into()) { div { Indexed( iterable=vec, - view=|cx: BoundedScope<'_, 'a>, x: &'a Signal| { + view=move |cx: BoundedScope<'_, 'a>, x: &'a Signal| { view! { cx, (x.edit(cx)) + Button(onclick=onremove(x), icon="mdi-delete".into()) br() } }, ) Button(onclick=onadd, text="Add new".into(), icon="mdi-plus".into()) } - //} } }), }, ) - - /* - Block( - cx, - BlockProps { - title: "List".into(), - children: Children::new(cx, move |_| { - div() - .dyn_c_scoped(move |cx| { - View::new_fragment( - vec.get() - .as_ref() - .clone() - .into_iter() - .map(|x| div().c(x.edit(cx)).c(br()).view(cx)) - .collect(), - ) - }) - .c(Button( - cx, - ButtonProps { - onclick: onadd, - text: "Add new".into(), - icon: "mdi-plus".into(), - }, - )) - .view(cx) - }), - }, - ) - */ } } impl<'a, G, T> Editable<'a, G> for Vec where G: Html, - T: for<'b> Editable<'b, G> + Clone + PartialEq + Default + 'a, + T: for<'b> Editable<'b, G> + Clone + PartialEq + Default + std::fmt::Debug + 'a, { type Editor = VecEdit; } @@ -360,7 +340,7 @@ where impl<'a, G, T> Editable<'a, G> for HashSet where G: Html, - T: for<'b> Editable<'b, G> + Clone + PartialEq + Default + Hash + Eq + 'a, + T: for<'b> Editable<'b, G> + Clone + PartialEq + Default + Hash + Eq + std::fmt::Debug + 'a, { type Editor = VecEdit; } @@ -370,7 +350,8 @@ where G: Html, K: Clone + Hash + Eq, V: Clone, - (K, V): for<'b> Editable<'b, G> + Clone + PartialEq + Default + Hash + Eq + 'a, + (K, V): + for<'b> Editable<'b, G> + Clone + PartialEq + Default + Hash + Eq + std::fmt::Debug + 'a, { type Editor = VecEdit; } diff --git a/core/src/event.rs b/core/src/event.rs index bc25763..a6141c5 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -22,7 +22,7 @@ pub struct EventOutcome { /// # Event /// /// An event in which participants can win or lose points -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "openapi", derive(JsonSchema))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "sycamore", derive(WebView))] @@ -36,7 +36,6 @@ pub struct Event { /// Description of the event #[cfg_attr(feature = "serde", serde(default))] pub description: String, - /// Event type pub event_type: EventType, } @@ -60,7 +59,9 @@ impl Event { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))] pub struct EventSpec { + /// Name of the event pub name: String, + /// Description of the event pub description: String, pub event_type: EventTypeSpec, } @@ -93,7 +94,7 @@ macro_rules! events { /// # EventType /// /// An enumeration of event types - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "openapi", derive(JsonSchema))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))] @@ -160,7 +161,7 @@ pub trait EventTrait { pub mod test { use super::*; - #[derive(Clone, Debug, Default)] + #[derive(Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "openapi", derive(JsonSchema))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))] @@ -219,7 +220,7 @@ pub mod team_game { *, }; - #[derive(Clone, Debug, Default)] + #[derive(Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "openapi", derive(JsonSchema))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))] @@ -421,7 +422,7 @@ pub mod free_for_all_game { } } - #[derive(Clone, Debug, Default)] + #[derive(Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "openapi", derive(JsonSchema))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))] diff --git a/core/src/view.rs b/core/src/view.rs index f6d56b5..df7b07d 100644 --- a/core/src/view.rs +++ b/core/src/view.rs @@ -27,11 +27,11 @@ macro_rules! viewable { #[derive(Prop)] pub struct ViewProps<'a, T> { - pub state: &'a Signal, + pub state: &'a T, } -impl<'a, T> From<&'a Signal> for ViewProps<'a, T> { - fn from(state: &'a Signal) -> Self { +impl<'a, T> From<&'a T> for ViewProps<'a, T> { + fn from(state: &'a T) -> Self { ViewProps { state } } } @@ -40,9 +40,9 @@ pub trait IntoView<'a, G: Html> { fn view(self, cx: Scope<'a>) -> View; } -impl<'a, G: Html, T: Viewable<'a, G>> IntoView<'a, G> for &'a Signal +impl<'a, G: Html, T: Viewable<'a, G>> IntoView<'a, G> for &'a T where - ViewProps<'a, T>: From<&'a Signal>, + ViewProps<'a, T>: From<&'a T>, { fn view(self, cx: Scope<'a>) -> View { T::view(cx, self.into()) @@ -80,7 +80,7 @@ where { fn view(cx: Scope<'a>, props: ViewProps<'a, T>) -> View { view! { cx, - (props.state.get().clone().create()) + (props.state.create()) } } } @@ -102,9 +102,8 @@ pub struct BoolView; impl<'a, G: Html> Viewer<'a, G, bool> for BoolView { fn view(cx: Scope<'a>, props: ViewProps<'a, bool>) -> View { - let signal = create_signal(cx, props.state.get()); view! { cx, - input(type="checkbox", checked=*signal.get().as_ref().clone(), disabled=true) + input(type="checkbox", checked=*props.state, disabled=true) } } } @@ -128,8 +127,7 @@ where view! { cx, //Block(title="List".into()) { div { - (View::new_fragment(props.state.get().as_ref().clone().into_iter().map(|x| { - let x = create_signal(cx, x); + (View::new_fragment(props.state.clone().into_iter().map(|x| create_ref(cx, x)).map(|x| { div().c(x.view(cx)).c(br()).view(cx) }).collect())) } @@ -173,12 +171,10 @@ impl<'a, G: Html, A: for<'b> Viewable<'b, G> + Clone, B: for<'b> Viewable<'b, G> Viewer<'a, G, (A, B)> for TupleView { fn view(cx: Scope<'a>, props: ViewProps<'a, (A, B)>) -> View { - let a = create_signal(cx, props.state.get().as_ref().clone().0); - let b = create_signal(cx, props.state.get().as_ref().clone().1); view! { cx, Block(title="Tuple".into()) { - (a.view(cx)) - (b.view(cx)) + (props.state.0.view(cx)) + (props.state.1.view(cx)) } } } @@ -197,8 +193,8 @@ pub struct OptionView; impl<'a, G: Html, T: for<'b> Viewable<'b, G> + Clone> Viewer<'a, G, Option> for OptionView { fn view(cx: Scope<'a>, props: ViewProps<'a, Option>) -> View { - match props.state.get().as_ref().clone() { - Some(x) => view! { cx, (create_signal(cx, x.clone()).view(cx)) }, + match props.state { + Some(x) => view! { cx, (x.view(cx)) }, None => view! { cx, "None" }, } } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 9172c75..0b20019 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -147,7 +147,7 @@ fn struct_edit(props: &ItemProps, s: DataStruct) -> TokenStream2 { view! { cx, Block(title=#title.to_string()) { - p { + p(class="description") { #description } #(#fields_view)* @@ -264,7 +264,7 @@ fn enum_edit(props: &ItemProps, e: DataEnum) -> TokenStream2 { view! { cx, Block(title=#title.to_string()) { - p { + p(class="description") { #description } select(bind:value=selected) { @@ -307,10 +307,10 @@ fn struct_view(props: &ItemProps, s: DataStruct) -> TokenStream2 { } }); - let signals = fields.clone().map(|f| { + let refs = fields.clone().map(|f| { let name = f.name; quote! { - let #name = create_signal(cx, state.get().#name.clone()); + let #name = create_ref(cx, state.#name.clone()); } }); @@ -337,11 +337,11 @@ fn struct_view(props: &ItemProps, s: DataStruct) -> TokenStream2 { quote! { let state = props.state; - #(#signals)* + #(#refs)* view! { cx, Block(title=#title.to_string()) { - p { + p(class="description") { #description } #(#fields_view)* @@ -380,7 +380,7 @@ fn enum_view(props: &ItemProps, e: DataEnum) -> TokenStream2 { let variant = v.variant; quote! { - #name::#variant(x) => create_signal(cx, x).view(cx) + #name::#variant(x) => x.view(cx) } }); @@ -406,14 +406,14 @@ fn enum_view(props: &ItemProps, e: DataEnum) -> TokenStream2 { view! { cx, Block(title=#title.to_string()) { - p { + p(class="description") { #description } - (match state.get().as_ref().clone() { + (match state { #(#view_description,)* _ => view! { cx, } }) - (match state.get().as_ref().clone() { + (match state { #(#view_match,)* _ => view! { cx, } }) diff --git a/web/dist/index.html b/web/dist/index.html index 4250ef0..fe8688f 100644 --- a/web/dist/index.html +++ b/web/dist/index.html @@ -2,11 +2,11 @@ - + LAN Party - - + + - \ No newline at end of file + \ No newline at end of file diff --git a/web/src/pages/events.rs b/web/src/pages/events.rs index 8605c6d..a324784 100644 --- a/web/src/pages/events.rs +++ b/web/src/pages/events.rs @@ -17,18 +17,15 @@ pub fn EventsPage<'a, G: Html>(cx: Scope<'a>) -> View { let event_spec = create_signal(cx, EventSpec::default()); let event_update = create_signal(cx, EventUpdate::default()); - let events = create_signal(cx, None); - let test_event = create_signal(cx, None); + let events: &'a Signal> = create_signal(cx, Vec::::new()); spawn_local_scoped(cx, async move { - events.set(Some( + events.set( api_request::<_, Vec>(Method::GET, "/event", Option::<()>::None) .await .map(|inner| inner.unwrap()) .unwrap(), - )); - test_event.set(Some(events.get().unwrap().get(0).unwrap().clone())); - debug!("{:#?}", test_event); + ); }); let onadd = move |_| { @@ -41,17 +38,25 @@ pub fn EventsPage<'a, G: Html>(cx: Scope<'a>) -> View { .await .unwrap(); debug!("{:#?}", new_event); - let mut cloned = (*events).get().as_ref().clone(); - cloned.unwrap().push(new_event.unwrap()); - events.set(cloned); + events.modify().push(new_event.unwrap()); }); }; view! { cx, Block(title="Events".into()) { - (test_event.view(cx)) - (events.view(cx)) + Keyed( + iterable=&events, + view=move |cx, event| { + let event = create_ref(cx, event); + view! { cx, + (event.view(cx)) + br() + } + }, + key=|event| (event.name.clone()), + ) } + br() Block(title="Create new event".into()) { (event_spec.edit(cx)) Button(icon="mdi-check".into(), onclick=onadd) diff --git a/web/src/pages/users.rs b/web/src/pages/users.rs index af7428e..731b244 100644 --- a/web/src/pages/users.rs +++ b/web/src/pages/users.rs @@ -93,9 +93,7 @@ pub fn UsersPage<'a, G: Html>(cx: Scope<'a>) -> View { ) .await .unwrap(); - let mut cloned = (*users).get().as_ref().clone(); - cloned.push(user.unwrap()); - users.set(cloned); + users.modify().push(user.unwrap()); }); }; diff --git a/web/style.css b/web/style.css index 677b804..76294ed 100644 --- a/web/style.css +++ b/web/style.css @@ -62,3 +62,7 @@ textarea:focus, input:focus{ font-size: 0.9rem; font-style: italic; } + +body { + grid-template-columns: 1fr min(60rem, 90%) 1fr; +}