Skip to content

Commit

Permalink
feat(fe/utils): Add EditableField struct
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnynotsolucky committed Aug 2, 2023
1 parent 34c2575 commit dec5f45
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl SchoolDetails {
{
Err(_) => todo!(),
Ok(school_account) => {
state.school.set(Some(school_account.school));
state.school.set(Some(school_account.school.into()));
state.users.lock_mut()
.replace_cloned(school_account.users.into_iter().map(|user| Rc::new(user))
.collect());
Expand Down
31 changes: 20 additions & 11 deletions frontend/apps/crates/entry/admin/src/schools/details/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,53 +52,62 @@ impl SchoolDetails {
.prop("slot", "inputs")
.children(&mut [
html!("input-wrapper", {
.prop("label", "School name")
.prop("label", "Internal School name")
.child(html!("input", {
.prop("type", "text")
.prop("disabled", true)
.prop("value", school.school_name)
.prop("value", school.internal_school_name
.map(|school_name| school_name.name)
.unwrap_or_default()
)
}))
}),
html!("input-wrapper", {
.prop("label", "Internal School name")
.prop("label", "School name")
.child(html!("input", {
.prop("type", "text")
.prop("disabled", true)
.prop("value", school.internal_school_name
.map(|school_name| school_name.name)
.unwrap_or_default()
)
.prop_signal("value", school.school_name.signal())
}))
}),
html!("input-wrapper", {
.prop("label", "Contact email")
.child(html!("input", {
.prop("type", "text")
.prop("disabled", true)
.prop("value", school.email)
.prop_signal("value", school.email.signal())
}))
}),
html!("input-wrapper", {
.prop("label", "Description")
.child(html!("textarea", {
.prop("disabled", true)
.prop("value", school.description.unwrap_or_default())
.prop_signal(
"value",
school.description.signal().map(|value| value.unwrap_or_default())
)
}))
}),
html!("input-wrapper", {
.prop("label", "Website")
.child(html!("input", {
.prop("type", "text")
.prop("disabled", true)
.prop("value", school.website.unwrap_or_default())
.prop_signal(
"value",
school.website.signal().map(|value| value.unwrap_or_default())
)
}))
}),
html!("input-wrapper", {
.prop("label", "Organization type")
.child(html!("input", {
.prop("type", "text")
.prop("disabled", true)
.prop("value", school.organization_type.unwrap_or_default())
.prop_signal(
"value",
school.organization_type.signal().map(|value| value.unwrap_or_default())
)
}))
}),
])
Expand Down
54 changes: 52 additions & 2 deletions frontend/apps/crates/entry/admin/src/schools/details/state.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
use crate::schools::Schools;
use futures_signals::signal::Mutable;
use futures_signals::signal_vec::MutableVec;
use serde_json::Value;
use shared::domain::admin::InviteSchoolUserFailure;
use shared::domain::billing::{AccountUser, AdminSchool, SchoolId};
use shared::domain::billing::{
AccountUser, AdminSchool, SchoolId, SchoolName, UpdateSchoolAccountRequest,
};
use shared::domain::image::ImageId;
use std::rc::Rc;
use utils::editable_field::{EditableField, NonNullable, Nullable};
use web_sys::HtmlTextAreaElement;

pub struct SchoolDetails {
pub parent: Rc<Schools>,
pub school_id: SchoolId,
pub school: Mutable<Option<AdminSchool>>,
pub school: Mutable<Option<EditableAdminSchool>>,
pub users: MutableVec<Rc<AccountUser>>,
pub current_action: Mutable<CurrentAction>,
pub errored_users: Mutable<Vec<String>>,
Expand All @@ -35,3 +40,48 @@ pub enum CurrentAction {
AddingUsers,
Results(Vec<InviteSchoolUserFailure>),
}

#[derive(Clone)]
pub struct EditableAdminSchool {
pub id: SchoolId,
pub internal_school_name: Option<SchoolName>,
pub verified: bool,
pub school_name: EditableField<NonNullable<String>>,
pub email: EditableField<NonNullable<String>>,
pub location: EditableField<Nullable<Value>>,
pub description: EditableField<Nullable<String>>,
pub profile_image: EditableField<Nullable<ImageId>>,
pub website: EditableField<Nullable<String>>,
pub organization_type: EditableField<Nullable<String>>,
}

impl From<AdminSchool> for EditableAdminSchool {
fn from(value: AdminSchool) -> Self {
Self {
id: value.id,
internal_school_name: value.internal_school_name,
verified: value.verified,
school_name: value.school_name.into(),
email: value.email.into(),
location: value.location.into(),
description: value.description.into(),
profile_image: value.profile_image.into(),
website: value.website.into(),
organization_type: value.organization_type.into(),
}
}
}

impl From<EditableAdminSchool> for UpdateSchoolAccountRequest {
fn from(value: EditableAdminSchool) -> Self {
Self {
email: value.email.into(),
school_name: value.school_name.into(),
location: value.location.into(),
description: value.description.into(),
profile_image: value.profile_image.into(),
website: value.website.into(),
organization_type: value.organization_type.into(),
}
}
}
91 changes: 91 additions & 0 deletions frontend/apps/crates/utils/src/editable_field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use futures_signals::signal::{Mutable, Signal};
use shared::domain::{UpdateNonNullable, UpdateNullable};

#[derive(Clone)]
pub struct EditableField<U> {
inner: U,
}

#[derive(Clone)]
pub struct NonNullable<T> {
update: Mutable<UpdateNonNullable<T>>,
value: Mutable<T>,
}

#[derive(Clone)]
pub struct Nullable<T> {
update: Mutable<UpdateNullable<T>>,
value: Mutable<Option<T>>,
}

impl<T> From<T> for EditableField<NonNullable<T>> {
fn from(value: T) -> Self {
Self {
inner: NonNullable {
update: Mutable::default(),
value: Mutable::new(value),
},
}
}
}

impl<T: Clone> From<EditableField<NonNullable<T>>> for UpdateNonNullable<T> {
fn from(value: EditableField<NonNullable<T>>) -> Self {
value.inner.update.get_cloned()
}
}

impl<T: Clone> EditableField<NonNullable<T>> {
pub fn signal(&self) -> impl Signal<Item = T> {
self.inner.value.signal_cloned()
}

pub fn get(&self) -> T {
self.inner.value.get_cloned()
}

pub fn set(&self, value: T) {
self.inner.value.set(value.clone());
self.inner.update.set(UpdateNonNullable::Change(value));
}
}

impl<T> From<Option<T>> for EditableField<Nullable<T>> {
fn from(value: Option<T>) -> Self {
Self {
inner: Nullable {
update: Mutable::default(),
value: Mutable::new(value),
},
}
}
}

impl<T: Clone> From<EditableField<Nullable<T>>> for UpdateNullable<T> {
fn from(value: EditableField<Nullable<T>>) -> Self {
value.inner.update.get_cloned()
}
}

impl<T: Clone> EditableField<Nullable<T>> {
pub fn signal(&self) -> impl Signal<Item = Option<T>> {
self.inner.value.signal_cloned()
}

pub fn get(&self) -> Option<T> {
self.inner.value.get_cloned()
}

pub fn set(&self, value: Option<T>) {
match value {
Some(value) => {
self.inner.value.set(Some(value.clone()));
self.inner.update.set(UpdateNullable::Change(value));
}
None => {
self.inner.value.set(None);
self.inner.update.set(UpdateNullable::Unset);
}
}
}
}
1 change: 1 addition & 0 deletions frontend/apps/crates/utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod component;
pub mod constants;
pub mod drag;
pub mod editable_asset;
pub mod editable_field;
pub mod env;
pub mod error_ext;
pub mod events;
Expand Down
9 changes: 2 additions & 7 deletions shared/rust/src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,21 +260,16 @@ impl<T> UpdateNullable<T> {
///
/// Requires `#[serde(default, skip_serializing_if = "Update::is_keep")]` to be applied to
/// fields which use this type.
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, Serialize, Default)]
#[serde(untagged)]
pub enum UpdateNonNullable<T> {
/// Use the current value stored in the database. Equivalent of `undefined` in JS.
#[default]
Keep,
/// Use the given value.
Change(T),
}

impl<T> Default for UpdateNonNullable<T> {
fn default() -> Self {
Self::Keep
}
}

impl<'de, T> Deserialize<'de> for UpdateNonNullable<T>
where
T: Deserialize<'de>,
Expand Down

0 comments on commit dec5f45

Please sign in to comment.