app: update yew to 0.19
This is a massive change to all the components, since the interface changed. There are opportunities to greatly simplify some components by turning them into functional_components, but this work has tried to stay as mechanical as possible.
This commit is contained in:
committed by
nitnelave
parent
8d44717588
commit
b2cfc0ed03
@@ -9,7 +9,7 @@ use crate::{
|
||||
logout::LogoutButton,
|
||||
reset_password_step1::ResetPasswordStep1Form,
|
||||
reset_password_step2::ResetPasswordStep2Form,
|
||||
router::{AppRoute, Link, NavButton},
|
||||
router::{AppRoute, Link, Redirect},
|
||||
user_details::UserDetails,
|
||||
user_table::UserTable,
|
||||
},
|
||||
@@ -17,21 +17,31 @@ use crate::{
|
||||
};
|
||||
|
||||
use gloo_console::error;
|
||||
use yew::{prelude::*, services::fetch::FetchTask};
|
||||
use yew::{
|
||||
function_component,
|
||||
html::Scope,
|
||||
prelude::{html, Component, Html},
|
||||
Context,
|
||||
};
|
||||
use yew_router::{
|
||||
agent::{RouteAgentDispatcher, RouteRequest},
|
||||
route::Route,
|
||||
router::Router,
|
||||
service::RouteService,
|
||||
prelude::{History, Location},
|
||||
scope_ext::RouterScopeExt,
|
||||
BrowserRouter, Switch,
|
||||
};
|
||||
|
||||
#[function_component(AppContainer)]
|
||||
pub fn app_container() -> Html {
|
||||
html! {
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
}
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
link: ComponentLink<Self>,
|
||||
user_info: Option<(String, bool)>,
|
||||
redirect_to: Option<AppRoute>,
|
||||
route_dispatcher: RouteAgentDispatcher,
|
||||
password_reset_enabled: Option<bool>,
|
||||
task: Option<FetchTask>,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
@@ -44,9 +54,8 @@ impl Component for App {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
let mut app = Self {
|
||||
link,
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
let app = Self {
|
||||
user_info: get_cookie("user_id")
|
||||
.unwrap_or_else(|e| {
|
||||
error!(&e.to_string());
|
||||
@@ -60,48 +69,40 @@ impl Component for App {
|
||||
None
|
||||
})
|
||||
}),
|
||||
redirect_to: Self::get_redirect_route(),
|
||||
route_dispatcher: RouteAgentDispatcher::new(),
|
||||
redirect_to: Self::get_redirect_route(ctx),
|
||||
password_reset_enabled: None,
|
||||
task: None,
|
||||
};
|
||||
app.task = Some(
|
||||
HostService::probe_password_reset(
|
||||
app.link.callback_once(Msg::PasswordResetProbeFinished),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
app.apply_initial_redirections();
|
||||
ctx.link().send_future(async move {
|
||||
Msg::PasswordResetProbeFinished(HostService::probe_password_reset().await)
|
||||
});
|
||||
app.apply_initial_redirections(ctx);
|
||||
app
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
let history = ctx.link().history().unwrap();
|
||||
match msg {
|
||||
Msg::Login((user_name, is_admin)) => {
|
||||
self.user_info = Some((user_name.clone(), is_admin));
|
||||
self.route_dispatcher
|
||||
.send(RouteRequest::ChangeRoute(Route::from(
|
||||
self.redirect_to.take().unwrap_or_else(|| {
|
||||
if is_admin {
|
||||
AppRoute::ListUsers
|
||||
} else {
|
||||
AppRoute::UserDetails(user_name.clone())
|
||||
}
|
||||
}),
|
||||
)));
|
||||
history.push(self.redirect_to.take().unwrap_or_else(|| {
|
||||
if is_admin {
|
||||
AppRoute::ListUsers
|
||||
} else {
|
||||
AppRoute::UserDetails {
|
||||
user_id: user_name.clone(),
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
Msg::Logout => {
|
||||
self.user_info = None;
|
||||
self.redirect_to = None;
|
||||
self.route_dispatcher
|
||||
.send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login)));
|
||||
history.push(AppRoute::Login);
|
||||
}
|
||||
Msg::PasswordResetProbeFinished(Ok(enabled)) => {
|
||||
self.task = None;
|
||||
self.password_reset_enabled = Some(enabled);
|
||||
}
|
||||
Msg::PasswordResetProbeFinished(Err(err)) => {
|
||||
self.task = None;
|
||||
self.password_reset_enabled = Some(false);
|
||||
error!(&format!(
|
||||
"Could not probe for password reset support: {err:#}"
|
||||
@@ -111,24 +112,20 @@ impl Component for App {
|
||||
true
|
||||
}
|
||||
|
||||
fn change(&mut self, _: Self::Properties) -> ShouldRender {
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
let link = self.link.clone();
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let link = ctx.link().clone();
|
||||
let is_admin = self.is_admin();
|
||||
let password_reset_enabled = self.password_reset_enabled;
|
||||
html! {
|
||||
<div>
|
||||
{self.view_banner()}
|
||||
{self.view_banner(ctx)}
|
||||
<div class="container py-3 bg-kug">
|
||||
<div class="row justify-content-center" style="padding-bottom: 80px;">
|
||||
<div class="py-3" style="max-width: 1000px">
|
||||
<Router<AppRoute>
|
||||
render={Router::render(move |s| Self::dispatch_route(s, &link, is_admin, password_reset_enabled))}
|
||||
<main class="py-3" style="max-width: 1000px">
|
||||
<Switch<AppRoute>
|
||||
render={Switch::render(move |routes| Self::dispatch_route(routes, &link, is_admin, password_reset_enabled))}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
{self.view_footer()}
|
||||
</div>
|
||||
@@ -138,59 +135,50 @@ impl Component for App {
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn get_redirect_route() -> Option<AppRoute> {
|
||||
let route_service = RouteService::<()>::new();
|
||||
let current_route = route_service.get_path();
|
||||
if current_route.is_empty()
|
||||
|| current_route == "/"
|
||||
|| current_route.contains("login")
|
||||
|| current_route.contains("reset-password")
|
||||
{
|
||||
None
|
||||
} else {
|
||||
use yew_router::Switch;
|
||||
AppRoute::from_route_part::<()>(current_route, None).0
|
||||
}
|
||||
// Get the page to land on after logging in, defaulting to the index.
|
||||
fn get_redirect_route(ctx: &Context<Self>) -> Option<AppRoute> {
|
||||
let route = ctx.link().history().unwrap().location().route::<AppRoute>();
|
||||
route.filter(|route| {
|
||||
!matches!(
|
||||
route,
|
||||
AppRoute::Index
|
||||
| AppRoute::Login
|
||||
| AppRoute::StartResetPassword
|
||||
| AppRoute::FinishResetPassword { token: _ }
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_initial_redirections(&mut self) {
|
||||
let route_service = RouteService::<()>::new();
|
||||
let current_route = route_service.get_path();
|
||||
if current_route.contains("reset-password") {
|
||||
if self.password_reset_enabled == Some(false) {
|
||||
self.route_dispatcher
|
||||
.send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
match &self.user_info {
|
||||
None => {
|
||||
self.route_dispatcher
|
||||
.send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login)));
|
||||
}
|
||||
Some((user_name, is_admin)) => match &self.redirect_to {
|
||||
Some(url) => {
|
||||
self.route_dispatcher
|
||||
.send(RouteRequest::ReplaceRoute(Route::from(url.clone())));
|
||||
fn apply_initial_redirections(&self, ctx: &Context<Self>) {
|
||||
let history = ctx.link().history().unwrap();
|
||||
let route = history.location().route::<AppRoute>();
|
||||
let redirection = match (route, &self.user_info, &self.redirect_to) {
|
||||
(
|
||||
Some(AppRoute::StartResetPassword | AppRoute::FinishResetPassword { token: _ }),
|
||||
_,
|
||||
_,
|
||||
) if self.password_reset_enabled == Some(false) => Some(AppRoute::Login),
|
||||
(None, _, _) | (_, None, _) => Some(AppRoute::Login),
|
||||
// User is logged in, a URL was given, don't redirect.
|
||||
(_, Some(_), Some(_)) => None,
|
||||
(_, Some((user_name, is_admin)), None) => {
|
||||
if *is_admin {
|
||||
Some(AppRoute::ListUsers)
|
||||
} else {
|
||||
Some(AppRoute::UserDetails {
|
||||
user_id: user_name.clone(),
|
||||
})
|
||||
}
|
||||
None => {
|
||||
if *is_admin {
|
||||
self.route_dispatcher
|
||||
.send(RouteRequest::ReplaceRoute(Route::from(AppRoute::ListUsers)));
|
||||
} else {
|
||||
self.route_dispatcher
|
||||
.send(RouteRequest::ReplaceRoute(Route::from(
|
||||
AppRoute::UserDetails(user_name.clone()),
|
||||
)));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
if let Some(redirect_to) = redirection {
|
||||
history.push(redirect_to);
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_route(
|
||||
switch: AppRoute,
|
||||
link: &ComponentLink<Self>,
|
||||
switch: &AppRoute,
|
||||
link: &Scope<Self>,
|
||||
is_admin: bool,
|
||||
password_reset_enabled: Option<bool>,
|
||||
) -> Html {
|
||||
@@ -204,10 +192,10 @@ impl App {
|
||||
AppRoute::Index | AppRoute::ListUsers => html! {
|
||||
<div>
|
||||
<UserTable />
|
||||
<NavButton classes="btn btn-primary" route={AppRoute::CreateUser}>
|
||||
<Link classes="btn btn-primary" to={AppRoute::CreateUser}>
|
||||
<i class="bi-person-plus me-2"></i>
|
||||
{"Create a user"}
|
||||
</NavButton>
|
||||
</Link>
|
||||
</div>
|
||||
},
|
||||
AppRoute::CreateGroup => html! {
|
||||
@@ -216,41 +204,40 @@ impl App {
|
||||
AppRoute::ListGroups => html! {
|
||||
<div>
|
||||
<GroupTable />
|
||||
<NavButton classes="btn btn-primary" route={AppRoute::CreateGroup}>
|
||||
<Link classes="btn btn-primary" to={AppRoute::CreateGroup}>
|
||||
<i class="bi-plus-circle me-2"></i>
|
||||
{"Create a group"}
|
||||
</NavButton>
|
||||
</Link>
|
||||
</div>
|
||||
},
|
||||
AppRoute::GroupDetails(group_id) => html! {
|
||||
<GroupDetails group_id={group_id} />
|
||||
AppRoute::GroupDetails { group_id } => html! {
|
||||
<GroupDetails group_id={*group_id} />
|
||||
},
|
||||
AppRoute::UserDetails(username) => html! {
|
||||
<UserDetails username={username} is_admin={is_admin} />
|
||||
AppRoute::UserDetails { user_id } => html! {
|
||||
<UserDetails username={user_id.clone()} is_admin={is_admin} />
|
||||
},
|
||||
AppRoute::ChangePassword(username) => html! {
|
||||
<ChangePasswordForm username={username} is_admin={is_admin} />
|
||||
AppRoute::ChangePassword { user_id } => html! {
|
||||
<ChangePasswordForm username={user_id.clone()} is_admin={is_admin} />
|
||||
},
|
||||
AppRoute::StartResetPassword => match password_reset_enabled {
|
||||
Some(true) => html! { <ResetPasswordStep1Form /> },
|
||||
Some(false) => {
|
||||
App::dispatch_route(AppRoute::Login, link, is_admin, password_reset_enabled)
|
||||
html! { <Redirect to={AppRoute::Login}/> }
|
||||
}
|
||||
|
||||
None => html! {},
|
||||
},
|
||||
AppRoute::FinishResetPassword(token) => match password_reset_enabled {
|
||||
Some(true) => html! { <ResetPasswordStep2Form token={token} /> },
|
||||
AppRoute::FinishResetPassword { token } => match password_reset_enabled {
|
||||
Some(true) => html! { <ResetPasswordStep2Form token={token.clone()} /> },
|
||||
Some(false) => {
|
||||
App::dispatch_route(AppRoute::Login, link, is_admin, password_reset_enabled)
|
||||
html! { <Redirect to={AppRoute::Login}/> }
|
||||
}
|
||||
None => html! {},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn view_banner(&self) -> Html {
|
||||
let link = &self.link;
|
||||
fn view_banner(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<header class="p-2 mb-3 border-bottom">
|
||||
<div class="container">
|
||||
@@ -265,7 +252,7 @@ impl App {
|
||||
<li>
|
||||
<Link
|
||||
classes="nav-link px-2 link-dark h6"
|
||||
route={AppRoute::ListUsers}>
|
||||
to={AppRoute::ListUsers}>
|
||||
<i class="bi-people me-2"></i>
|
||||
{"Users"}
|
||||
</Link>
|
||||
@@ -273,7 +260,7 @@ impl App {
|
||||
<li>
|
||||
<Link
|
||||
classes="nav-link px-2 link-dark h6"
|
||||
route={AppRoute::ListGroups}>
|
||||
to={AppRoute::ListGroups}>
|
||||
<i class="bi-collection me-2"></i>
|
||||
{"Groups"}
|
||||
</Link>
|
||||
@@ -281,55 +268,59 @@ impl App {
|
||||
</>
|
||||
} } else { html!{} } }
|
||||
</ul>
|
||||
|
||||
{
|
||||
if let Some((user_id, _)) = &self.user_info {
|
||||
html! {
|
||||
<div class="dropdown text-end">
|
||||
<a href="#"
|
||||
class="d-block link-dark text-decoration-none dropdown-toggle"
|
||||
id="dropdownUser"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="32"
|
||||
fill="currentColor"
|
||||
class="bi bi-person-circle"
|
||||
viewBox="0 0 16 16">
|
||||
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
||||
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
||||
</svg>
|
||||
<span class="ms-2">
|
||||
{user_id}
|
||||
</span>
|
||||
</a>
|
||||
<ul
|
||||
class="dropdown-menu text-small dropdown-menu-lg-end"
|
||||
aria-labelledby="dropdownUser1"
|
||||
style="">
|
||||
<li>
|
||||
<Link
|
||||
classes="dropdown-item"
|
||||
route={AppRoute::UserDetails(user_id.clone())}>
|
||||
{"View details"}
|
||||
</Link>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider" /></li>
|
||||
<li>
|
||||
<LogoutButton on_logged_out={link.callback(|_| Msg::Logout)} />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
} else { html!{} }
|
||||
}
|
||||
{ self.view_user_menu(ctx) }
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
}
|
||||
}
|
||||
|
||||
fn view_user_menu(&self, ctx: &Context<Self>) -> Html {
|
||||
if let Some((user_id, _)) = &self.user_info {
|
||||
let link = ctx.link();
|
||||
html! {
|
||||
<div class="dropdown text-end">
|
||||
<a href="#"
|
||||
class="d-block link-dark text-decoration-none dropdown-toggle"
|
||||
id="dropdownUser"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="32"
|
||||
fill="currentColor"
|
||||
class="bi bi-person-circle"
|
||||
viewBox="0 0 16 16">
|
||||
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
||||
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
||||
</svg>
|
||||
<span class="ms-2">
|
||||
{user_id}
|
||||
</span>
|
||||
</a>
|
||||
<ul
|
||||
class="dropdown-menu text-small dropdown-menu-lg-end"
|
||||
aria-labelledby="dropdownUser1"
|
||||
style="">
|
||||
<li>
|
||||
<Link
|
||||
classes="dropdown-item"
|
||||
to={AppRoute::UserDetails{ user_id: user_id.clone() }}>
|
||||
{"View details"}
|
||||
</Link>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider" /></li>
|
||||
<li>
|
||||
<LogoutButton on_logged_out={link.callback(|_| Msg::Logout)} />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
}
|
||||
}
|
||||
|
||||
fn view_footer(&self) -> Html {
|
||||
html! {
|
||||
<footer class="text-center text-muted fixed-bottom bg-light py-2">
|
||||
|
||||
Reference in New Issue
Block a user