mirror of
https://github.com/nitnelave/lldap.git
synced 2023-04-12 14:25:13 +00:00
app: Extract a Select component
This commit is contained in:
parent
37c6e8ef30
commit
5943df6443
@ -1,12 +1,14 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
components::user_details::{Group, User},
|
components::{
|
||||||
|
select::{Select, SelectOption, SelectOptionProps},
|
||||||
|
user_details::{Group, User},
|
||||||
|
},
|
||||||
infra::api::HostService,
|
infra::api::HostService,
|
||||||
};
|
};
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use graphql_client::GraphQLQuery;
|
use graphql_client::GraphQLQuery;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use yew::{
|
use yew::{
|
||||||
html::ChangeData,
|
|
||||||
prelude::*,
|
prelude::*,
|
||||||
services::{fetch::FetchTask, ConsoleService},
|
services::{fetch::FetchTask, ConsoleService},
|
||||||
};
|
};
|
||||||
@ -43,24 +45,20 @@ impl From<GroupListGroup> for Group {
|
|||||||
|
|
||||||
pub struct AddUserToGroupComponent {
|
pub struct AddUserToGroupComponent {
|
||||||
link: ComponentLink<Self>,
|
link: ComponentLink<Self>,
|
||||||
user: User,
|
props: Props,
|
||||||
/// The list of existing groups, initially not loaded.
|
/// The list of existing groups, initially not loaded.
|
||||||
group_list: Option<Vec<Group>>,
|
group_list: Option<Vec<Group>>,
|
||||||
/// Whether the "+" button has been clicked.
|
/// The currently selected group.
|
||||||
add_group: bool,
|
|
||||||
on_error: Callback<Error>,
|
|
||||||
on_user_added_to_group: Callback<Group>,
|
|
||||||
selected_group: Option<Group>,
|
selected_group: Option<Group>,
|
||||||
// Used to keep the request alive long enough.
|
// Used to keep the request alive long enough.
|
||||||
_task: Option<FetchTask>,
|
_task: Option<FetchTask>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Msg {
|
pub enum Msg {
|
||||||
AddGroupButtonClicked,
|
|
||||||
GroupListResponse(Result<get_group_list::ResponseData>),
|
GroupListResponse(Result<get_group_list::ResponseData>),
|
||||||
SubmitAddGroup,
|
SubmitAddGroup,
|
||||||
AddGroupResponse(Result<add_user_to_group::ResponseData>),
|
AddGroupResponse(Result<add_user_to_group::ResponseData>),
|
||||||
SelectionChanged(ChangeData),
|
SelectionChanged(Option<SelectOptionProps>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(yew::Properties, Clone, PartialEq)]
|
#[derive(yew::Properties, Clone, PartialEq)]
|
||||||
@ -91,7 +89,7 @@ impl AddUserToGroupComponent {
|
|||||||
};
|
};
|
||||||
self._task = HostService::graphql_query::<AddUserToGroup>(
|
self._task = HostService::graphql_query::<AddUserToGroup>(
|
||||||
add_user_to_group::Variables {
|
add_user_to_group::Variables {
|
||||||
user: self.user.id.clone(),
|
user: self.props.user.id.clone(),
|
||||||
group: group_id,
|
group: group_id,
|
||||||
},
|
},
|
||||||
self.link.callback(Msg::AddGroupResponse),
|
self.link.callback(Msg::AddGroupResponse),
|
||||||
@ -107,97 +105,62 @@ impl AddUserToGroupComponent {
|
|||||||
|
|
||||||
fn handle_msg(&mut self, msg: <Self as Component>::Message) -> Result<bool> {
|
fn handle_msg(&mut self, msg: <Self as Component>::Message) -> Result<bool> {
|
||||||
match msg {
|
match msg {
|
||||||
Msg::AddGroupButtonClicked => {
|
|
||||||
if self.group_list.is_none() {
|
|
||||||
self.get_group_list();
|
|
||||||
} else {
|
|
||||||
self.set_default_selection();
|
|
||||||
}
|
|
||||||
self.add_group = true;
|
|
||||||
}
|
|
||||||
Msg::GroupListResponse(response) => {
|
Msg::GroupListResponse(response) => {
|
||||||
self.group_list = Some(response?.groups.into_iter().map(Into::into).collect());
|
self.group_list = Some(response?.groups.into_iter().map(Into::into).collect());
|
||||||
self.set_default_selection();
|
|
||||||
}
|
}
|
||||||
Msg::SubmitAddGroup => return self.submit_add_group(),
|
Msg::SubmitAddGroup => return self.submit_add_group(),
|
||||||
Msg::AddGroupResponse(response) => {
|
Msg::AddGroupResponse(response) => {
|
||||||
response?;
|
response?;
|
||||||
// Adding the user to the group succeeded, we're not in the process of adding a
|
// Adding the user to the group succeeded, we're not in the process of adding a
|
||||||
// group anymore.
|
// group anymore.
|
||||||
self.add_group = false;
|
|
||||||
let group = self
|
let group = self
|
||||||
.selected_group
|
.selected_group
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("Could not get selected group")
|
.expect("Could not get selected group")
|
||||||
.clone();
|
.clone();
|
||||||
// Remove the group from the dropdown.
|
// Remove the group from the dropdown.
|
||||||
self.on_user_added_to_group.emit(group);
|
self.props.on_user_added_to_group.emit(group);
|
||||||
}
|
}
|
||||||
Msg::SelectionChanged(data) => match data {
|
Msg::SelectionChanged(option_props) => {
|
||||||
ChangeData::Select(e) => {
|
self.selected_group = option_props.map(|props| Group {
|
||||||
self.update_selection(e);
|
id: props.value.parse::<i64>().unwrap(),
|
||||||
|
display_name: props.text,
|
||||||
|
});
|
||||||
|
return Ok(false);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_selection(&mut self, e: web_sys::HtmlSelectElement) {
|
fn get_selectable_group_list(&self, group_list: &[Group]) -> Vec<Group> {
|
||||||
if e.selected_index() == -1 {
|
let user_groups = self.props.user.groups.iter().collect::<HashSet<_>>();
|
||||||
self.selected_group = None;
|
|
||||||
} else {
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
let option = e
|
|
||||||
.options()
|
|
||||||
.get_with_index(e.selected_index() as u32)
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<web_sys::HtmlOptionElement>()
|
|
||||||
.unwrap();
|
|
||||||
self.selected_group = Some(Group {
|
|
||||||
id: option.value().parse::<i64>().unwrap(),
|
|
||||||
display_name: option.text(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_selectable_group_list(&self, group_list: &Vec<Group>) -> Vec<Group> {
|
|
||||||
let user_groups = self.user.groups.iter().collect::<HashSet<_>>();
|
|
||||||
group_list
|
group_list
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|g| !user_groups.contains(g))
|
.filter(|g| !user_groups.contains(g))
|
||||||
.map(Clone::clone)
|
.map(Clone::clone)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_default_selection(&mut self) {
|
|
||||||
self.selected_group = (|| {
|
|
||||||
let groups = self.get_selectable_group_list(self.group_list.as_ref()?);
|
|
||||||
groups.into_iter().next()
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for AddUserToGroupComponent {
|
impl Component for AddUserToGroupComponent {
|
||||||
type Message = Msg;
|
type Message = Msg;
|
||||||
type Properties = Props;
|
type Properties = Props;
|
||||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||||
Self {
|
let mut res = Self {
|
||||||
link,
|
link,
|
||||||
user: props.user,
|
props,
|
||||||
group_list: None,
|
group_list: None,
|
||||||
add_group: false,
|
|
||||||
on_error: props.on_error,
|
|
||||||
on_user_added_to_group: props.on_user_added_to_group,
|
|
||||||
selected_group: None,
|
selected_group: None,
|
||||||
_task: None,
|
_task: None,
|
||||||
}
|
};
|
||||||
|
res.get_group_list();
|
||||||
|
res
|
||||||
}
|
}
|
||||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||||
match self.handle_msg(msg) {
|
match self.handle_msg(msg) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ConsoleService::error(&e.to_string());
|
ConsoleService::error(&e.to_string());
|
||||||
self.on_error.emit(e);
|
self.props.on_error.emit(e);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Ok(b) => b,
|
Ok(b) => b,
|
||||||
@ -205,11 +168,8 @@ impl Component for AddUserToGroupComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
||||||
if props.user.groups != self.user.groups {
|
if props.user.groups != self.props.user.groups {
|
||||||
self.user = props.user;
|
self.props.user = props.user;
|
||||||
if self.selected_group.is_none() {
|
|
||||||
self.set_default_selection();
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@ -217,39 +177,25 @@ impl Component for AddUserToGroupComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
if !self.add_group {
|
|
||||||
return html! {
|
|
||||||
<>
|
|
||||||
<td></td>
|
|
||||||
<td>
|
|
||||||
<button onclick=self.link.callback(
|
|
||||||
|_| Msg::AddGroupButtonClicked)>
|
|
||||||
{"+"}
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</>
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(group_list) = &self.group_list {
|
if let Some(group_list) = &self.group_list {
|
||||||
let to_add_group_list = self.get_selectable_group_list(&group_list);
|
let to_add_group_list = self.get_selectable_group_list(group_list);
|
||||||
|
#[allow(unused_braces)]
|
||||||
let make_select_option = |group: Group| {
|
let make_select_option = |group: Group| {
|
||||||
html! {
|
html_nested! {
|
||||||
<option value={group.id.to_string()}>{group.display_name}</option>
|
<SelectOption value=group.id.to_string() text=group.display_name key=group.id />
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<td>
|
<td>
|
||||||
<select name="groupToAdd" id="groupToAdd"
|
<Select on_selection_change=self.link.callback(Msg::SelectionChanged)>
|
||||||
onchange=self.link.callback(|e| Msg::SelectionChanged(e))>
|
|
||||||
{
|
{
|
||||||
to_add_group_list
|
to_add_group_list
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(make_select_option)
|
.map(make_select_option)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
</select>
|
</Select>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button onclick=self.link.callback(
|
<button onclick=self.link.callback(
|
||||||
|
@ -5,5 +5,6 @@ pub mod create_user;
|
|||||||
pub mod login;
|
pub mod login;
|
||||||
pub mod logout;
|
pub mod logout;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
|
pub mod select;
|
||||||
pub mod user_details;
|
pub mod user_details;
|
||||||
pub mod user_table;
|
pub mod user_table;
|
||||||
|
112
app/src/components/select.rs
Normal file
112
app/src/components/select.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use yew::{html::ChangeData, prelude::*};
|
||||||
|
|
||||||
|
pub struct Select {
|
||||||
|
link: ComponentLink<Self>,
|
||||||
|
props: SelectProps,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(yew::Properties, Clone, PartialEq, Debug)]
|
||||||
|
pub struct SelectProps {
|
||||||
|
pub children: ChildrenWithProps<SelectOption>,
|
||||||
|
pub on_selection_change: Callback<Option<SelectOptionProps>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SelectMsg {
|
||||||
|
OnSelectChange(ChangeData),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Select {
|
||||||
|
fn get_nth_child_props(&self, nth: i32) -> Option<SelectOptionProps> {
|
||||||
|
if nth == -1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.props
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.nth(nth as usize)
|
||||||
|
.map(|child| child.props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Select {
|
||||||
|
type Message = SelectMsg;
|
||||||
|
type Properties = SelectProps;
|
||||||
|
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||||
|
let res = Self { link, props };
|
||||||
|
res.props
|
||||||
|
.on_selection_change
|
||||||
|
.emit(res.get_nth_child_props(0));
|
||||||
|
res
|
||||||
|
}
|
||||||
|
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||||
|
match msg {
|
||||||
|
SelectMsg::OnSelectChange(data) => match data {
|
||||||
|
ChangeData::Select(e) => {
|
||||||
|
self.props
|
||||||
|
.on_selection_change
|
||||||
|
.emit(self.get_nth_child_props(e.selected_index()));
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
||||||
|
if self.props.children.len() != props.children.len() {
|
||||||
|
let was_empty = self.props.children.is_empty();
|
||||||
|
self.props = props;
|
||||||
|
if self.props.children.is_empty() || was_empty {
|
||||||
|
self.props
|
||||||
|
.on_selection_change
|
||||||
|
.emit(self.get_nth_child_props(0));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn view(&self) -> Html {
|
||||||
|
html! {
|
||||||
|
<select
|
||||||
|
onchange=self.link.callback(SelectMsg::OnSelectChange)>
|
||||||
|
{ self.props.children.clone() }
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SelectOption {
|
||||||
|
props: SelectOptionProps,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(yew::Properties, Clone, PartialEq)]
|
||||||
|
pub struct SelectOptionProps {
|
||||||
|
pub value: String,
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for SelectOption {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = SelectOptionProps;
|
||||||
|
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||||
|
Self { props }
|
||||||
|
}
|
||||||
|
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
||||||
|
if self.props != props {
|
||||||
|
self.props = props;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn view(&self) -> Html {
|
||||||
|
html! {
|
||||||
|
<option value=self.props.value.clone()>
|
||||||
|
{&self.props.text}
|
||||||
|
</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -279,7 +279,7 @@ impl UserDetails {
|
|||||||
let id = group.id;
|
let id = group.id;
|
||||||
let display_name = group.display_name.clone();
|
let display_name = group.display_name.clone();
|
||||||
html! {
|
html! {
|
||||||
<tr key="groupRow_".to_string() + &display_name.clone()>
|
<tr key="groupRow_".to_string() + &display_name>
|
||||||
<td>{&group.display_name}</td>
|
<td>{&group.display_name}</td>
|
||||||
{ if self.is_admin { html! {
|
{ if self.is_admin { html! {
|
||||||
<td><button onclick=self.link.callback(move |_| Msg::SubmitRemoveGroup(Group{id, display_name: display_name.clone()}))>{"-"}</button></td>
|
<td><button onclick=self.link.callback(move |_| Msg::SubmitRemoveGroup(Group{id, display_name: display_name.clone()}))>{"-"}</button></td>
|
||||||
|
Loading…
Reference in New Issue
Block a user