refactor(kanban): scope card handles to partials
Move kanban card actions onto the reusable card partial, use generated render helpers for board/page composition, and remove manual handle fields plus hidden shell-level action anchors from the composed path. req: component/003 req: view/001 req: examples/001
This commit is contained in:
@@ -69,7 +69,7 @@ mod tests {
|
|||||||
// req: examples/001 req: form/002 req: codegen/003
|
// req: examples/001 req: form/002 req: codegen/003
|
||||||
#[test]
|
#[test]
|
||||||
fn kanban_template_exports_form_and_card_handles() {
|
fn kanban_template_exports_form_and_card_handles() {
|
||||||
assert_ne!(ui::board::handles::create_card.id(), ui::board::handles::move_right.id());
|
assert_ne!(ui::board::handles::create_card.id(), ui::board_card::handles::move_right.id());
|
||||||
assert_eq!(ui::board::forms::create_card.field("title").resource, ui::board::forms::create_card.id());
|
assert_eq!(ui::board::forms::create_card.field("title").resource, ui::board::forms::create_card.id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-29
@@ -4,9 +4,11 @@ use axum::routing::get;
|
|||||||
use axum::Router;
|
use axum::Router;
|
||||||
use futures_util::{stream, StreamExt};
|
use futures_util::{stream, StreamExt};
|
||||||
use hemplate::Hemplate;
|
use hemplate::Hemplate;
|
||||||
use slhx::{Handle, IntoEffect, RenderSlotExt, SafeHtml};
|
use slhx::{IntoEffect, RenderSlotExt, SafeHtml};
|
||||||
use slhx_axum::{runtime_js, sse, DispatchRejection, EffectResponse, HandlerRegistry, InteractionForm, PageRequest};
|
use slhx_axum::{runtime_js, sse, DispatchRejection, EffectResponse, HandlerRegistry, InteractionForm, PageRequest};
|
||||||
use slhx_kanban_example::ui;
|
use slhx_kanban_example::ui;
|
||||||
|
use slhx_kanban_example::ui::board::{forms, handles, slots};
|
||||||
|
use slhx_kanban_example::ui::board_card::handles as card_handles;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
@@ -51,9 +53,6 @@ struct BoardColumn {
|
|||||||
struct BoardCard {
|
struct BoardCard {
|
||||||
id: u64,
|
id: u64,
|
||||||
title: String,
|
title: String,
|
||||||
left_handle: Handle<()>,
|
|
||||||
right_handle: Handle<()>,
|
|
||||||
delete_handle: Handle<()>,
|
|
||||||
left_disabled: bool,
|
left_disabled: bool,
|
||||||
right_disabled: bool,
|
right_disabled: bool,
|
||||||
}
|
}
|
||||||
@@ -131,13 +130,13 @@ async fn interact(
|
|||||||
// req: push/001 req: push/003 req: examples/001
|
// req: push/001 req: push/003 req: examples/001
|
||||||
async fn events(Query(params): Query<BTreeMap<String, String>>) -> impl IntoResponse {
|
async fn events(Query(params): Query<BTreeMap<String, String>>) -> impl IntoResponse {
|
||||||
if params.contains_key("once") {
|
if params.contains_key("once") {
|
||||||
let effect = ui::board::slots::presence.render(&Presence { count: 1 });
|
let effect = slots::presence.render(&Presence { count: 1 });
|
||||||
return sse(stream::iter([Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT))]).boxed());
|
return sse(stream::iter([Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT))]).boxed());
|
||||||
}
|
}
|
||||||
|
|
||||||
let batches = stream::unfold(1_u64, |count| async move {
|
let batches = stream::unfold(1_u64, |count| async move {
|
||||||
tokio::time::sleep(Duration::from_secs(4)).await;
|
tokio::time::sleep(Duration::from_secs(4)).await;
|
||||||
let effect = ui::board::slots::presence.render(&Presence { count });
|
let effect = slots::presence.render(&Presence { count });
|
||||||
Some((Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT)), count + 1))
|
Some((Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT)), count + 1))
|
||||||
})
|
})
|
||||||
.boxed();
|
.boxed();
|
||||||
@@ -146,7 +145,7 @@ async fn events(Query(params): Query<BTreeMap<String, String>>) -> impl IntoResp
|
|||||||
|
|
||||||
fn registry(state: Arc<AppState>) -> HandlerRegistry {
|
fn registry(state: Arc<AppState>) -> HandlerRegistry {
|
||||||
HandlerRegistry::new(ui::BUILD_FINGERPRINT)
|
HandlerRegistry::new(ui::BUILD_FINGERPRINT)
|
||||||
.register_handle(ui::board::handles::create_card, {
|
.register_handle(handles::create_card, {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move |form| {
|
move |form| {
|
||||||
// req: examples/001 req: form/002
|
// req: examples/001 req: form/002
|
||||||
@@ -161,7 +160,7 @@ fn registry(state: Arc<AppState>) -> HandlerRegistry {
|
|||||||
board_effects(&board, "Card added")
|
board_effects(&board, "Card added")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.register_handle(ui::board::handles::move_left, {
|
.register_handle(card_handles::move_left, {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move |form| {
|
move |form| {
|
||||||
// req: examples/001 req: list/003
|
// req: examples/001 req: list/003
|
||||||
@@ -172,7 +171,7 @@ fn registry(state: Arc<AppState>) -> HandlerRegistry {
|
|||||||
board_effects(&board, if moved { "Card moved left" } else { "Card not found" })
|
board_effects(&board, if moved { "Card moved left" } else { "Card not found" })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.register_handle(ui::board::handles::move_right, {
|
.register_handle(card_handles::move_right, {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move |form| {
|
move |form| {
|
||||||
// req: examples/001 req: list/003
|
// req: examples/001 req: list/003
|
||||||
@@ -183,7 +182,7 @@ fn registry(state: Arc<AppState>) -> HandlerRegistry {
|
|||||||
board_effects(&board, if moved { "Card moved right" } else { "Card not found" })
|
board_effects(&board, if moved { "Card moved right" } else { "Card not found" })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.register_handle(ui::board::handles::delete_card, {
|
.register_handle(card_handles::delete_card, {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move |form| {
|
move |form| {
|
||||||
// req: examples/001 req: list/003
|
// req: examples/001 req: list/003
|
||||||
@@ -199,9 +198,9 @@ fn registry(state: Arc<AppState>) -> HandlerRegistry {
|
|||||||
|
|
||||||
fn board_effects(board: &BoardState, notice: &'static str) -> impl IntoEffect {
|
fn board_effects(board: &BoardState, notice: &'static str) -> impl IntoEffect {
|
||||||
(
|
(
|
||||||
ui::board::slots::board.render(&board_view(board)),
|
slots::board.html(render_board(board)),
|
||||||
ui::board::slots::notice.text(notice),
|
slots::notice.text(notice),
|
||||||
ui::board::forms::create_card.clear("title"),
|
forms::create_card.clear("title"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,10 +226,11 @@ fn parse_column(value: Option<&str>) -> usize {
|
|||||||
fn page_html(board: &BoardState) -> String {
|
fn page_html(board: &BoardState) -> String {
|
||||||
// Explicit full-page composition boundary for already-rendered hemplate fragments.
|
// Explicit full-page composition boundary for already-rendered hemplate fragments.
|
||||||
// req: html_safety/002 req: view/001
|
// req: html_safety/002 req: view/001
|
||||||
ui::board::lower_html(render_template(&Board {
|
ui::board::render_html(&Board {
|
||||||
options: render_options(),
|
options: render_options(),
|
||||||
board: render_board(board),
|
board: render_board(board),
|
||||||
}))
|
})
|
||||||
|
.into_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shell(body: String) -> String {
|
fn shell(body: String) -> String {
|
||||||
@@ -262,7 +262,7 @@ fn shell(body: String) -> String {
|
|||||||
|
|
||||||
fn render_options() -> SafeHtml {
|
fn render_options() -> SafeHtml {
|
||||||
// req: html_safety/002 req: view/001
|
// req: html_safety/002 req: view/001
|
||||||
slhx::render_html(&ColumnOptions {
|
ui::render_html(&ColumnOptions {
|
||||||
options: COLUMNS
|
options: COLUMNS
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, title)| ColumnOption { id, title })
|
.map(|(id, title)| ColumnOption { id, title })
|
||||||
@@ -291,29 +291,18 @@ fn board_view(board: &BoardState) -> BoardColumns {
|
|||||||
|
|
||||||
fn render_board(board: &BoardState) -> SafeHtml {
|
fn render_board(board: &BoardState) -> SafeHtml {
|
||||||
// req: html_safety/002 req: view/001
|
// req: html_safety/002 req: view/001
|
||||||
slhx::render_html(&board_view(board))
|
ui::render_html(&board_view(board))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_card(card: &Card) -> BoardCard {
|
fn render_card(card: &Card) -> BoardCard {
|
||||||
BoardCard {
|
BoardCard {
|
||||||
id: card.id,
|
id: card.id,
|
||||||
title: card.title.clone(),
|
title: card.title.clone(),
|
||||||
left_handle: ui::board::handles::move_left,
|
|
||||||
right_handle: ui::board::handles::move_right,
|
|
||||||
delete_handle: ui::board::handles::delete_card,
|
|
||||||
left_disabled: card.column == 0,
|
left_disabled: card.column == 0,
|
||||||
right_disabled: card.column + 1 == COLUMNS.len(),
|
right_disabled: card.column + 1 == COLUMNS.len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_template(template: &impl Hemplate) -> String {
|
|
||||||
let mut html = String::new();
|
|
||||||
template
|
|
||||||
.render_into(&mut html)
|
|
||||||
.expect("kanban hemplate view renders");
|
|
||||||
html
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -334,7 +323,7 @@ mod tests {
|
|||||||
assert_eq!(document.select(&selector("section[data-slhx-root=\"kanban\"]")).count(), 1);
|
assert_eq!(document.select(&selector("section[data-slhx-root=\"kanban\"]")).count(), 1);
|
||||||
assert_eq!(document.select(&selector("select[name=\"column\"] > option")).count(), 3);
|
assert_eq!(document.select(&selector("select[name=\"column\"] > option")).count(), 3);
|
||||||
assert_eq!(document.select(&selector("[data-sid]")).count(), 3);
|
assert_eq!(document.select(&selector("[data-sid]")).count(), 3);
|
||||||
assert_eq!(document.select(&selector("[data-hid]")).count(), 4);
|
assert_eq!(document.select(&selector("[data-hid]")).count(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// req: html_safety/002 req: view/001 req: test/005
|
// req: html_safety/002 req: view/001 req: test/005
|
||||||
|
|||||||
@@ -12,7 +12,4 @@
|
|||||||
<div data-slhx-slot="board">{+= self.board =+}</div>
|
<div data-slhx-slot="board">{+= self.board =+}</div>
|
||||||
<aside data-slhx-slot="presence">Waiting for presence…</aside>
|
<aside data-slhx-slot="presence">Waiting for presence…</aside>
|
||||||
|
|
||||||
<button type="button" data-slhx-handle="move_left" hidden="hidden">Move left</button>
|
|
||||||
<button type="button" data-slhx-handle="move_right" hidden="hidden">Move right</button>
|
|
||||||
<button type="button" data-slhx-handle="delete_card" hidden="hidden">Delete</button>
|
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<article class="card" +data-key="self.id">
|
<article class="card" +data-key="self.id">
|
||||||
<strong>{+ self.title +}</strong>
|
<strong>{+ self.title +}</strong>
|
||||||
<menu>
|
<menu>
|
||||||
<button h-if="self.left_disabled" type="button" +data-hid="self.left_handle" +data-card-id="self.id" disabled="disabled">←</button>
|
<button h-if="self.left_disabled" type="button" data-slhx-handle="move_left" +data-card-id="self.id" disabled="disabled">←</button>
|
||||||
<button h-else type="button" +data-hid="self.left_handle" +data-card-id="self.id">←</button>
|
<button h-else type="button" data-slhx-handle="move_left" +data-card-id="self.id">←</button>
|
||||||
<button h-if="self.right_disabled" type="button" +data-hid="self.right_handle" +data-card-id="self.id" disabled="disabled">→</button>
|
<button h-if="self.right_disabled" type="button" data-slhx-handle="move_right" +data-card-id="self.id" disabled="disabled">→</button>
|
||||||
<button h-else type="button" +data-hid="self.right_handle" +data-card-id="self.id">→</button>
|
<button h-else type="button" data-slhx-handle="move_right" +data-card-id="self.id">→</button>
|
||||||
<button type="button" +data-hid="self.delete_handle" +data-card-id="self.id">Delete</button>
|
<button type="button" data-slhx-handle="delete_card" +data-card-id="self.id">Delete</button>
|
||||||
</menu>
|
</menu>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
Reference in New Issue
Block a user