feat(kanban): persist local reorder commands

req: local/001\nreq: local/002\nreq: local/003\nreq: local/004\nreq: sync/009
This commit is contained in:
slhx agent
2026-07-13 14:43:38 +02:00
parent 7eb65d77c8
commit 7298405e27
4 changed files with 420 additions and 18 deletions
+75 -10
View File
@@ -1,25 +1,90 @@
#[hemx::surface]
pub mod ui {}
#[cfg(feature = "client")]
#[derive(Clone, Debug, Eq, PartialEq)]
struct CardId(String);
#[cfg(feature = "client")]
struct ReorderCommand {
card: CardId,
input_kind: String,
}
#[cfg(feature = "client")]
struct CardReordered {
card: CardId,
input_kind: String,
}
#[cfg(feature = "client")]
struct BoardProjection {
first: CardId,
}
#[cfg(feature = "client")]
struct ProjectedReorder {
card: CardId,
before: Option<CardId>,
input_kind: String,
}
#[cfg(feature = "client")]
impl ReorderCommand {
fn from_client(event: hemx::wasm::ClientEvent) -> Self {
Self {
card: CardId(
event
.value
.filter(|card| !card.is_empty())
.unwrap_or_else(|| "1".into()),
),
input_kind: event.kind,
}
}
fn decide(self) -> CardReordered {
CardReordered {
card: self.card,
input_kind: self.input_kind,
}
}
}
#[cfg(feature = "client")]
impl BoardProjection {
fn restore(state: hemx::wasm::ClientState) -> Self {
Self {
first: CardId(state.encoded.split('|').next().unwrap_or("1").to_owned()),
}
}
fn apply(self, event: CardReordered) -> ProjectedReorder {
let before = (event.card != self.first).then_some(self.first);
ProjectedReorder {
card: event.card,
before,
input_kind: event.input_kind,
}
}
}
#[cfg(feature = "client")]
#[hemx::handler(client)]
pub fn reorder_card(
event: hemx::wasm::ClientEvent,
state: hemx::wasm::ClientState,
) -> impl hemx::IntoEffect {
let card = event.value.unwrap_or_else(|| "1".to_owned());
let order = state.encoded.split('|').collect::<Vec<_>>();
let move_effect = if card == order.first().copied().unwrap_or("1") {
ui::client_board::client_cards.move_to_end(card.clone())
} else {
ui::client_board::client_cards.move_before(
card.clone(),
order.first().copied().unwrap_or("1").to_owned(),
)
let projected =
BoardProjection::restore(state).apply(ReorderCommand::from_client(event).decide());
let card = projected.card.0;
let move_effect = match projected.before {
Some(before) => ui::client_board::client_cards.move_before(card.clone(), before.0),
None => ui::client_board::client_cards.move_to_end(card.clone()),
};
vec![
move_effect,
ui::client_board::client_notice.text(format!("Moved {card} with {}", event.kind)),
ui::client_board::client_notice.text(format!("Moved {card} with {}", projected.input_kind)),
]
}