use std::marker::PhantomData;
use maud::Markup;
use crate::components::modal::modal_keyed;
use crate::components::swap::SwapKey;
use crate::components::{ButtonModalForm, button_modal_form};
use crate::web::{CreateModal, Htmx, modal_create_get_for_picker};
pub use crate::components::htmx::FK_DROPDOWN_ID_PREFIX;
fn targets_fk_search_dropdown(htmx: &Htmx) -> bool {
htmx.target_id
.as_deref()
.is_some_and(|id| id.starts_with(FK_DROPDOWN_ID_PREFIX))
}
#[derive(Debug, Clone)]
pub struct ModalHtml(Markup);
impl ModalHtml {
pub fn wrap<M: SwapKey>(inner: Markup) -> Self {
Self(modal_keyed::<M>("", inner))
}
pub fn into_inner(self) -> Markup {
self.0
}
}
pub struct TableFragmentHtml<K: SwapKey>(Markup, PhantomData<K>);
impl<K: SwapKey> TableFragmentHtml<K> {
pub fn new(inner: Markup) -> Self {
Self(inner, PhantomData)
}
pub fn into_inner(self) -> Markup {
self.0
}
}
pub trait RenderPickerSelect<K: SwapKey, M: SwapKey> {
fn render_table(&self) -> Markup;
fn render_modal(&self) -> ModalHtml {
ModalHtml::wrap::<M>(self.render_table())
}
}
pub trait PickerModal: SwapKey {
type Table: SwapKey;
}
#[macro_export]
macro_rules! impl_picker_modal {
($modal:ty, $table:ty) => {
impl $crate::picker::PickerModal for $modal {
type Table = $table;
}
};
}
pub fn respond_picker_select<K, M, P>(htmx: &Htmx, page: &P) -> Markup
where
K: SwapKey,
M: SwapKey,
P: RenderPickerSelect<K, M>,
{
if htmx.targets::<K>()
|| htmx.source_id.as_deref() == Some(K::ID)
|| targets_fk_search_dropdown(htmx)
{
page.render_table()
} else {
page.render_modal().into_inner()
}
}
pub fn picker_create_button<M: CreateModal>(
target_input: &str,
icon_name: Option<&str>,
classes: &str,
) -> maud::Markup {
let href = modal_create_get_for_picker::<M>(target_input);
button_modal_form(ButtonModalForm {
name: "",
href: &href,
form_post_url: "",
modal_uid: M::ID,
icon_name,
classes,
..Default::default()
})
}
#[cfg(test)]
mod tests {
use maud::html;
use super::*;
use crate::components::swap::SwapKey;
lariv_rs::swap_key!(TestPickerTableKey, "test-picker-table");
lariv_rs::swap_key!(TestPickerModalKey, "test-picker-modal");
struct DummyPicker;
impl RenderPickerSelect<TestPickerTableKey, TestPickerModalKey> for DummyPicker {
fn render_table(&self) -> Markup {
html! { p { "pick me" } }
}
}
#[test]
fn modal_wrap_contains_dialog() {
let m = ModalHtml::wrap::<TestPickerModalKey>(html! { "x" });
let s = m.into_inner().into_string();
assert!(s.contains("<dialog"));
assert!(s.contains("modal"));
}
#[test]
fn respond_picker_select_open_returns_modal() {
let htmx = Htmx::default();
let out =
respond_picker_select::<TestPickerTableKey, TestPickerModalKey, _>(&htmx, &DummyPicker);
assert!(out.into_string().contains("<dialog"));
}
#[test]
fn respond_picker_select_table_refresh_from_source_id() {
let mut htmx = Htmx::default();
htmx.request = true;
htmx.source_id = Some(TestPickerTableKey::ID.to_string());
let out =
respond_picker_select::<TestPickerTableKey, TestPickerModalKey, _>(&htmx, &DummyPicker);
let s = out.into_string();
assert!(s.contains("pick me"));
assert!(!s.contains("<dialog"));
}
#[test]
fn respond_picker_select_typeahead_dropdown_returns_table() {
let mut htmx = Htmx::default();
htmx.request = true;
htmx.target_id = Some(format!("{FK_DROPDOWN_ID_PREFIX}role_id"));
let out =
respond_picker_select::<TestPickerTableKey, TestPickerModalKey, _>(&htmx, &DummyPicker);
let s = out.into_string();
assert!(s.contains("pick me"));
assert!(!s.contains("<dialog"));
}
}