318 lines
12 KiB
Rust
318 lines
12 KiB
Rust
use leptos::*;
|
|
use leptos_meta::*;
|
|
use leptos_router::*;
|
|
|
|
#[server(GetLinksAction, "/api", "GetJson")]
|
|
#[tracing::instrument]
|
|
pub async fn get_links() -> Result<Vec<crate::models::Link>, ServerFnError> {
|
|
crate::models::Link::get_all().await.map_err(|x| {
|
|
let err = format!("Error while posting a link: {x:?}");
|
|
tracing::error!("{err}");
|
|
ServerFnError::ServerError("Could not post a link, try again later".into())
|
|
})
|
|
}
|
|
|
|
#[server(LinkAction, "/api")]
|
|
pub async fn add_value(name: String, link: String, icon: String) -> Result<(), ServerFnError> {
|
|
crate::models::Link::insert(name, link, icon)
|
|
.await
|
|
.map(|_| ())
|
|
.map_err(|x| {
|
|
let err = format!("Error while posting a comment: {x:?}");
|
|
tracing::error!("{err}");
|
|
ServerFnError::ServerError("Could not post a comment, try again later".into())
|
|
})
|
|
}
|
|
|
|
#[component]
|
|
pub fn Links() -> impl IntoView {
|
|
let (show_form, set_show_form) = create_signal(false);
|
|
let (edit_link, set_edit_link) = create_signal::<Option<crate::models::Link>>(None);
|
|
let (edit, set_edit) = create_signal(false);
|
|
|
|
let link_action = create_server_action::<LinkAction>();
|
|
let update_action = create_server_action::<UpdateLinkAction>();
|
|
|
|
let result = link_action.version();
|
|
let update_result = update_action.version();
|
|
|
|
let links = create_resource(
|
|
move || (result.get(), update_result.get()),
|
|
move |_| async move {
|
|
set_show_form.set(false);
|
|
set_edit_link.set(None);
|
|
get_links().await
|
|
},
|
|
);
|
|
|
|
view! {
|
|
<Title text="Liens"/>
|
|
<ul class="flex gap-5 m-5 flex-wrap justify-center">
|
|
<Suspense fallback=move || view! {<p>"Loading Comments from the article"</p> }>
|
|
<ErrorBoundary fallback=|_| {
|
|
view! { <p class="error-messages text-xs-center">"Something went wrong."</p>}
|
|
}>
|
|
{move || links.get().map(move |x| x.map(move |c| {
|
|
view! {
|
|
<For each=move || c.clone().into_iter().enumerate()
|
|
key=|(i, _)| *i
|
|
children=move |(_, link)| {
|
|
let link = create_rw_signal(link);
|
|
view!{
|
|
<Link link edit set_edit_link set_show_form links />
|
|
}
|
|
}/>
|
|
}
|
|
}))}
|
|
</ErrorBoundary>
|
|
</Suspense>
|
|
</ul>
|
|
|
|
<div class="flex justify-end gap-5 mx-5">
|
|
<button on:click=move |_| {set_edit.set(!edit.get())} class="bg-prim-light hover:bg-prim-lightest rounded-full px-5 py-3 text-second-dark hover:text-third transition-colors">"Modifier"</button>
|
|
<button on:click=move |_| {
|
|
set_edit_link.set(None);
|
|
set_show_form.set(true);
|
|
} class="bg-prim-light hover:bg-prim-lightest rounded-full px-5 py-3 text-second-dark hover:text-third transition-colors">"Ajouter un lien +"</button>
|
|
</div>
|
|
|
|
{move || {
|
|
show_form.get().then(|| {
|
|
view! { <LinkFormModal
|
|
link=edit_link.get()
|
|
set_show=set_show_form
|
|
add_action=link_action
|
|
update_action=update_action
|
|
/> }
|
|
})
|
|
}}
|
|
}
|
|
}
|
|
|
|
#[server(MoveLinkAction, "/api")]
|
|
pub async fn change_position(link_id: String, direction: String) -> Result<(), ServerFnError> {
|
|
crate::models::Link::move_position(link_id, direction)
|
|
.await
|
|
.map(|_| ())
|
|
.map_err(|x| {
|
|
let err = format!("Error while posting a comment: {x:?}");
|
|
tracing::error!("{err}");
|
|
ServerFnError::ServerError("Could not post a comment, try again later".into())
|
|
})
|
|
}
|
|
|
|
#[component]
|
|
fn Link<T: 'static + Clone, S: 'static>(
|
|
link: RwSignal<crate::models::Link>,
|
|
edit: ReadSignal<bool>,
|
|
set_edit_link: WriteSignal<Option<crate::models::Link>>,
|
|
set_show_form: WriteSignal<bool>,
|
|
links: Resource<T, S>,
|
|
) -> impl IntoView {
|
|
view! {
|
|
<li class="mx-auto w-44 lg:w-60">
|
|
<a class="bg-prim-light w-full hover:bg-prim-lightest border-b hover:border-third border-transparent text-xl rounded-lg text-center hover:text-third transition-colors px-5 py-4 inline-block"
|
|
target="_blank"
|
|
href={move || link.with(|x| x.link.to_string())}>
|
|
<div class="flex justify-center mb-2">
|
|
<img src={move || link.with(|x| x.icon.to_string())}
|
|
alt={move || link.with(|x| x.name.to_string())}
|
|
class="block size-32 lg:size-54 object-cover overflow-hidden" />
|
|
</div>
|
|
|
|
<span>{move || link.with(|x| x.name.to_string())}</span>
|
|
</a>
|
|
{move || {
|
|
edit.get().then(|| {
|
|
view! { <div class="bg-third border border-transparent rounded-b-lg flex justify-between">
|
|
<MoveButton id=link.with(|x| x.id.to_string()) value="prev".to_string() label="<".to_string() links=links />
|
|
<button class="block px-2 py-1 flex-1 hover:bg-third-light"
|
|
on:click=move |_| {
|
|
set_edit_link.set(Some(link.get()));
|
|
set_show_form.set(true);
|
|
}
|
|
>"Editer"</button>
|
|
<DeleteButton id=link.with(|x| x.id.to_string()) links=links />
|
|
<MoveButton id=link.with(|x| x.id.to_string()) value="next".to_string() label=">".to_string() links=links />
|
|
</div> }
|
|
})
|
|
}}
|
|
</li>
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
fn MoveButton<T: 'static + Clone, S: 'static>(
|
|
id: String,
|
|
value: String,
|
|
label: String,
|
|
links: Resource<T, S>,
|
|
) -> impl IntoView {
|
|
let move_link = create_server_action::<MoveLinkAction>();
|
|
|
|
view! {
|
|
<ActionForm action=move_link on:submit= move |ev| {
|
|
let Ok(_) = MoveLinkAction::from_event(&ev) else {
|
|
return ev.prevent_default();
|
|
};
|
|
links.refetch();
|
|
}>
|
|
<input name="link_id" type="hidden" value=id />
|
|
<input name="direction" type="hidden" value=value />
|
|
<button class="block w-12 px-2 py-1 hover:bg-third-light rounded-br-lg">{label}</button>
|
|
</ActionForm>
|
|
}
|
|
}
|
|
|
|
#[server(DeleteLinkAction, "/api")]
|
|
pub async fn delete_link(link_id: String) -> Result<(), ServerFnError> {
|
|
crate::models::Link::destroy(link_id)
|
|
.await
|
|
.map(|_| ())
|
|
.map_err(|x| {
|
|
let err = format!("Error while posting a comment: {x:?}");
|
|
tracing::error!("{err}");
|
|
ServerFnError::ServerError("Could not post a comment, try again later".into())
|
|
})
|
|
}
|
|
|
|
#[component]
|
|
fn DeleteButton<T: 'static + Clone, S: 'static>(
|
|
id: String,
|
|
links: Resource<T, S>,
|
|
) -> impl IntoView {
|
|
let delete_link = create_server_action::<DeleteLinkAction>();
|
|
|
|
view! {
|
|
<ActionForm action=delete_link on:submit= move |ev| {
|
|
let Ok(_) = DeleteLinkAction::from_event(&ev) else {
|
|
return ev.prevent_default();
|
|
};
|
|
links.refetch();
|
|
}>
|
|
<input name="link_id" type="hidden" value=id />
|
|
<button type="submit" class="block px-2 py-1 flex-1 hover:bg-third-light">"Supp."</button>
|
|
</ActionForm>
|
|
}
|
|
}
|
|
|
|
|
|
#[server(UpdateLinkAction, "/api")]
|
|
pub async fn update_link(id: String, name: String, link: String, icon: String) -> Result<(), ServerFnError> {
|
|
crate::models::Link::update(id, name, link, icon)
|
|
.await
|
|
.map(|_| ())
|
|
.map_err(|x| {
|
|
let err = format!("Error while updating a link: {x:?}");
|
|
tracing::error!("{err}");
|
|
ServerFnError::ServerError("Could not update the link, try again later".into())
|
|
})
|
|
}
|
|
|
|
#[component]
|
|
fn LinkFormModal(
|
|
link: Option<crate::models::Link>,
|
|
set_show: WriteSignal<bool>,
|
|
add_action: Action<LinkAction, Result<(), ServerFnError>>,
|
|
update_action: Action<UpdateLinkAction, Result<(), ServerFnError>>,
|
|
) -> impl IntoView {
|
|
let is_edit = link.is_some();
|
|
let (name, set_name) = create_signal(link.as_ref().map(|x| x.name.clone()).unwrap_or_default());
|
|
let (link_url, set_link_url) = create_signal(link.as_ref().map(|x| x.link.clone()).unwrap_or_default());
|
|
let (icon, set_icon) = create_signal(link.as_ref().map(|x| x.icon.clone()).unwrap_or_default());
|
|
let link_id = link.as_ref().map(|x| x.id.to_string());
|
|
|
|
view! {
|
|
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
<div class="bg-prim-light p-6 rounded-lg w-96">
|
|
<h2 class="pb-6 text-2xl text-center">
|
|
{if is_edit { "Édition du lien" } else { "Ajout d'un lien" }}
|
|
</h2>
|
|
|
|
{if is_edit {
|
|
view! {
|
|
<ActionForm action=update_action>
|
|
<input name="id" type="hidden" value={link_id.unwrap_or_default()} />
|
|
<LinkFormFields
|
|
name=name
|
|
set_name=set_name
|
|
link_url=link_url
|
|
set_link_url=set_link_url
|
|
icon=icon
|
|
set_icon=set_icon
|
|
/>
|
|
<FormButtons set_show=set_show />
|
|
</ActionForm>
|
|
}.into_view()
|
|
} else {
|
|
view! {
|
|
<ActionForm action=add_action>
|
|
<LinkFormFields
|
|
name=name
|
|
set_name=set_name
|
|
link_url=link_url
|
|
set_link_url=set_link_url
|
|
icon=icon
|
|
set_icon=set_icon
|
|
/>
|
|
<FormButtons set_show=set_show />
|
|
</ActionForm>
|
|
}.into_view()
|
|
}}
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
fn LinkFormFields(
|
|
name: ReadSignal<String>,
|
|
set_name: WriteSignal<String>,
|
|
link_url: ReadSignal<String>,
|
|
set_link_url: WriteSignal<String>,
|
|
icon: ReadSignal<String>,
|
|
set_icon: WriteSignal<String>,
|
|
) -> impl IntoView {
|
|
view! {
|
|
<div>
|
|
<label class="block mt-3 mb-1">"Nom"</label>
|
|
<input type="text"
|
|
name="name"
|
|
prop:value=move || name.get()
|
|
on:input=move |ev| set_name.set(event_target_value(&ev))
|
|
class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-full" />
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block mt-3 mb-1">"Lien"</label>
|
|
<input type="url"
|
|
name="link"
|
|
prop:value=move || link_url.get()
|
|
on:input=move |ev| set_link_url.set(event_target_value(&ev))
|
|
class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-full" />
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block mt-3 mb-1">"Icône"</label>
|
|
<input type="url"
|
|
name="icon"
|
|
placeholder="http..."
|
|
prop:value=move || icon.get()
|
|
on:input=move |ev| set_icon.set(event_target_value(&ev))
|
|
class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-full" />
|
|
</div>
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
fn FormButtons(set_show: WriteSignal<bool>) -> impl IntoView {
|
|
view! {
|
|
<div class="flex gap-2 mt-5">
|
|
<button type="button"
|
|
on:click=move |_| set_show.set(false)
|
|
class="bg-prim hover:bg-prim-light rounded-lg transition-colors px-2 py-1 flex-1">"Annuler"</button>
|
|
<button type="submit"
|
|
class="bg-third hover:bg-third-light rounded-lg transition-colors px-2 py-1 flex-1">"Valider"</button>
|
|
</div>
|
|
}
|
|
} |