| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
|
| 24 |
|
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
|
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
use makeover_layout as layout; |
| 44 |
use quasi_router::{ |
| 45 |
Action, Chrome, Message, Method, Node, Outcome, Params, Request, Response, Screen, Slot, |
| 46 |
}; |
| 47 |
use ratatui::buffer::Buffer; |
| 48 |
use ratatui::layout::Rect; |
| 49 |
|
| 50 |
use crate::focus::{Reach, Spot}; |
| 51 |
use crate::{Tui, View}; |
| 52 |
|
| 53 |
|
| 54 |
|
| 55 |
|
| 56 |
|
| 57 |
|
| 58 |
|
| 59 |
#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 60 |
pub enum Key { |
| 61 |
|
| 62 |
Char(char), |
| 63 |
|
| 64 |
Enter, |
| 65 |
|
| 66 |
Tab, |
| 67 |
|
| 68 |
BackTab, |
| 69 |
|
| 70 |
Backspace, |
| 71 |
|
| 72 |
Escape, |
| 73 |
|
| 74 |
Up, |
| 75 |
|
| 76 |
Down, |
| 77 |
|
| 78 |
PageUp, |
| 79 |
|
| 80 |
PageDown, |
| 81 |
|
| 82 |
Left, |
| 83 |
|
| 84 |
Right, |
| 85 |
} |
| 86 |
|
| 87 |
|
| 88 |
#[derive(Debug, Clone, PartialEq, Eq)] |
| 89 |
pub enum Step { |
| 90 |
|
| 91 |
Idle, |
| 92 |
|
| 93 |
Call(Request), |
| 94 |
|
| 95 |
|
| 96 |
Ask(String), |
| 97 |
|
| 98 |
Open(String), |
| 99 |
} |
| 100 |
|
| 101 |
|
| 102 |
|
| 103 |
|
| 104 |
|
| 105 |
|
| 106 |
|
| 107 |
|
| 108 |
#[derive(Debug, Clone)] |
| 109 |
struct Layer { |
| 110 |
screen: Screen, |
| 111 |
view: View, |
| 112 |
} |
| 113 |
|
| 114 |
|
| 115 |
#[derive(Debug, Clone)] |
| 116 |
pub struct Runtime { |
| 117 |
screen: Screen, |
| 118 |
view: View, |
| 119 |
|
| 120 |
|
| 121 |
chrome: Chrome, |
| 122 |
|
| 123 |
|
| 124 |
|
| 125 |
|
| 126 |
|
| 127 |
|
| 128 |
|
| 129 |
|
| 130 |
|
| 131 |
under: Vec<Layer>, |
| 132 |
|
| 133 |
|
| 134 |
|
| 135 |
|
| 136 |
|
| 137 |
|
| 138 |
history: Vec<Request>, |
| 139 |
|
| 140 |
here: Option<Request>, |
| 141 |
|
| 142 |
asked: Option<(Action, Params)>, |
| 143 |
|
| 144 |
saying: Option<Message>, |
| 145 |
} |
| 146 |
|
| 147 |
impl Runtime { |
| 148 |
|
| 149 |
#[must_use] |
| 150 |
pub fn new(screen: Screen) -> Self { |
| 151 |
let mut runtime = Self { |
| 152 |
screen, |
| 153 |
view: View::new(), |
| 154 |
chrome: Chrome::new(), |
| 155 |
under: Vec::new(), |
| 156 |
history: Vec::new(), |
| 157 |
here: None, |
| 158 |
asked: None, |
| 159 |
saying: None, |
| 160 |
}; |
| 161 |
runtime.view.seed(&runtime.screen); |
| 162 |
runtime |
| 163 |
} |
| 164 |
|
| 165 |
|
| 166 |
|
| 167 |
|
| 168 |
|
| 169 |
#[must_use] |
| 170 |
pub fn with_chrome(mut self, chrome: Chrome) -> Self { |
| 171 |
self.chrome = chrome; |
| 172 |
self |
| 173 |
} |
| 174 |
|
| 175 |
|
| 176 |
#[must_use] |
| 177 |
pub const fn overlaid(&self) -> bool { |
| 178 |
!self.under.is_empty() |
| 179 |
} |
| 180 |
|
| 181 |
|
| 182 |
|
| 183 |
|
| 184 |
|
| 185 |
#[must_use] |
| 186 |
pub const fn screen(&self) -> &Screen { |
| 187 |
&self.screen |
| 188 |
} |
| 189 |
|
| 190 |
|
| 191 |
#[must_use] |
| 192 |
pub const fn view(&self) -> &View { |
| 193 |
&self.view |
| 194 |
} |
| 195 |
|
| 196 |
|
| 197 |
#[must_use] |
| 198 |
pub fn reaches(&self) -> Vec<Reach> { |
| 199 |
crate::focus::reaches(&self.screen) |
| 200 |
} |
| 201 |
|
| 202 |
|
| 203 |
|
| 204 |
|
| 205 |
|
| 206 |
|
| 207 |
|
| 208 |
#[must_use] |
| 209 |
pub fn editing(&self) -> bool { |
| 210 |
self.focused().is_some_and(|spot| spot.field().is_some()) |
| 211 |
} |
| 212 |
|
| 213 |
|
| 214 |
#[must_use] |
| 215 |
pub const fn asking(&self) -> bool { |
| 216 |
self.asked.is_some() |
| 217 |
} |
| 218 |
|
| 219 |
|
| 220 |
pub fn draw(&self, tui: &Tui, area: Rect, buf: &mut Buffer) { |
| 221 |
|
| 222 |
|
| 223 |
|
| 224 |
for layer in &self.under { |
| 225 |
tui.screen(&layer.screen, &layer.view, area, buf); |
| 226 |
} |
| 227 |
let area = if self.under.is_empty() { |
| 228 |
area |
| 229 |
} else { |
| 230 |
let inset = Self::overlay_area(area); |
| 231 |
|
| 232 |
|
| 233 |
for y in inset.top()..inset.bottom() { |
| 234 |
for x in inset.left()..inset.right() { |
| 235 |
buf[(x, y)].reset(); |
| 236 |
} |
| 237 |
} |
| 238 |
inset |
| 239 |
}; |
| 240 |
tui.screen(&self.screen, &self.view, area, buf); |
| 241 |
} |
| 242 |
|
| 243 |
|
| 244 |
|
| 245 |
|
| 246 |
|
| 247 |
|
| 248 |
|
| 249 |
fn overlay_area(area: Rect) -> Rect { |
| 250 |
let pad_x = (area.width / 8) |
| 251 |
.max(1) |
| 252 |
.min(area.width.saturating_sub(2) / 2); |
| 253 |
let pad_y = (area.height / 8) |
| 254 |
.max(1) |
| 255 |
.min(area.height.saturating_sub(2) / 2); |
| 256 |
Rect { |
| 257 |
x: area.x + pad_x, |
| 258 |
y: area.y + pad_y, |
| 259 |
width: area.width.saturating_sub(pad_x * 2), |
| 260 |
height: area.height.saturating_sub(pad_y * 2), |
| 261 |
} |
| 262 |
} |
| 263 |
|
| 264 |
|
| 265 |
|
| 266 |
|
| 267 |
|
| 268 |
|
| 269 |
|
| 270 |
pub fn say(&mut self, text: impl Into<String>) { |
| 271 |
self.screen.notices.push(Node::Notice { |
| 272 |
kind: layout::Notice::Banner, |
| 273 |
tone: layout::Tone::Danger, |
| 274 |
text: text.into(), |
| 275 |
}); |
| 276 |
} |
| 277 |
|
| 278 |
|
| 279 |
#[must_use] |
| 280 |
pub fn focused(&self) -> Option<Spot> { |
| 281 |
let mut reaches = crate::focus::reaches(&self.screen); |
| 282 |
if self.view.focus() >= reaches.len() { |
| 283 |
return None; |
| 284 |
} |
| 285 |
Some(reaches.swap_remove(self.view.focus()).spot) |
| 286 |
} |
| 287 |
|
| 288 |
|
| 289 |
pub fn key(&mut self, key: Key) -> Step { |
| 290 |
|
| 291 |
|
| 292 |
|
| 293 |
if let Some((action, payload)) = self.asked.take() { |
| 294 |
return match key { |
| 295 |
|
| 296 |
|
| 297 |
|
| 298 |
|
| 299 |
Key::Char('y' | 'Y') | Key::Enter => Self::send(&action, payload), |
| 300 |
_ => Step::Idle, |
| 301 |
}; |
| 302 |
} |
| 303 |
|
| 304 |
|
| 305 |
|
| 306 |
|
| 307 |
|
| 308 |
|
| 309 |
|
| 310 |
|
| 311 |
if !self.editing() || !matches!(key, Key::Char(_)) { |
| 312 |
let pressed = Self::key_name(key); |
| 313 |
if let Some(binding) = pressed.as_deref().and_then(|name| self.chrome.bound(name)) { |
| 314 |
return Self::call(&binding.action.clone()); |
| 315 |
} |
| 316 |
} |
| 317 |
|
| 318 |
let reaches = crate::focus::reaches(&self.screen); |
| 319 |
let count = reaches.len(); |
| 320 |
let here = reaches |
| 321 |
.get(self.view.focus()) |
| 322 |
.map(|reach| reach.spot.clone()); |
| 323 |
|
| 324 |
match key { |
| 325 |
Key::Tab | Key::Down => { |
| 326 |
self.view.advance(1, count); |
| 327 |
Step::Idle |
| 328 |
} |
| 329 |
Key::BackTab | Key::Up => { |
| 330 |
self.view.advance(-1, count); |
| 331 |
Step::Idle |
| 332 |
} |
| 333 |
|
| 334 |
Key::PageDown | Key::PageUp => { |
| 335 |
|
| 336 |
|
| 337 |
|
| 338 |
|
| 339 |
if let Some(reach) = reaches.get(self.view.focus()) { |
| 340 |
let rows = if matches!(key, Key::PageDown) { |
| 341 |
10 |
| 342 |
} else { |
| 343 |
-10 |
| 344 |
}; |
| 345 |
self.view.scroll_by(&reach.region, rows); |
| 346 |
} |
| 347 |
Step::Idle |
| 348 |
} |
| 349 |
|
| 350 |
Key::Left | Key::Right => { |
| 351 |
|
| 352 |
|
| 353 |
|
| 354 |
|
| 355 |
|
| 356 |
|
| 357 |
if let Some(slot) = self.moving(&reaches) { |
| 358 |
let steps = if matches!(key, Key::Right) { 1 } else { -1 }; |
| 359 |
let slot = slot.clone(); |
| 360 |
self.view.show_by(&slot, steps); |
| 361 |
} |
| 362 |
Step::Idle |
| 363 |
} |
| 364 |
|
| 365 |
|
| 366 |
|
| 367 |
Key::Escape => { |
| 368 |
if self.dismiss() { |
| 369 |
Step::Idle |
| 370 |
} else { |
| 371 |
self.back() |
| 372 |
} |
| 373 |
} |
| 374 |
|
| 375 |
Key::Backspace => { |
| 376 |
if let Some(field) = here.as_ref().and_then(Spot::field) { |
| 377 |
self.view.backspace(field); |
| 378 |
} |
| 379 |
Step::Idle |
| 380 |
} |
| 381 |
|
| 382 |
Key::Enter => match here { |
| 383 |
Some(Spot::Act { |
| 384 |
action, |
| 385 |
confirm, |
| 386 |
over, |
| 387 |
.. |
| 388 |
}) => { |
| 389 |
let payload = self.gathering(over.as_deref()); |
| 390 |
match confirm { |
| 391 |
Some(prompt) => { |
| 392 |
self.asked = Some((action, payload)); |
| 393 |
Step::Ask(prompt) |
| 394 |
} |
| 395 |
None => Self::send(&action, payload), |
| 396 |
} |
| 397 |
} |
| 398 |
Some(Spot::Submit { action, names }) => { |
| 399 |
let payload = self.view.submission( |
| 400 |
&names, |
| 401 |
&reaches |
| 402 |
.iter() |
| 403 |
.map(|reach| reach.spot.clone()) |
| 404 |
.collect::<Vec<_>>(), |
| 405 |
); |
| 406 |
Self::send(&action, payload) |
| 407 |
} |
| 408 |
|
| 409 |
|
| 410 |
|
| 411 |
|
| 412 |
Some(Spot::Field(_)) | None => Step::Idle, |
| 413 |
Some(other) => match other.enters() { |
| 414 |
Some(action) => Self::call(&action.clone()), |
| 415 |
None => Step::Idle, |
| 416 |
}, |
| 417 |
}, |
| 418 |
|
| 419 |
Key::Char(' ') if !self.editing() => match here { |
| 420 |
|
| 421 |
|
| 422 |
|
| 423 |
|
| 424 |
Some(Spot::Row { |
| 425 |
toggle: Some(action), |
| 426 |
.. |
| 427 |
}) => Self::call(&action), |
| 428 |
|
| 429 |
|
| 430 |
|
| 431 |
|
| 432 |
|
| 433 |
|
| 434 |
|
| 435 |
|
| 436 |
|
| 437 |
|
| 438 |
Some(Spot::Row { |
| 439 |
ticked: Some(_), |
| 440 |
value: Some(value), |
| 441 |
.. |
| 442 |
}) if self.screen.selection.is_some() => { |
| 443 |
self.view.tick(&value); |
| 444 |
Step::Idle |
| 445 |
} |
| 446 |
_ => Step::Idle, |
| 447 |
}, |
| 448 |
|
| 449 |
Key::Char(ch) => { |
| 450 |
if let Some(field) = here.as_ref().and_then(Spot::field).cloned() { |
| 451 |
self.type_into(&field, ch); |
| 452 |
return self.after_typing(here.as_ref()); |
| 453 |
} |
| 454 |
|
| 455 |
|
| 456 |
|
| 457 |
|
| 458 |
|
| 459 |
let pressed = ch.to_string(); |
| 460 |
let claimed = reaches.iter().find_map(|reach| match &reach.spot { |
| 461 |
Spot::Act { |
| 462 |
action, |
| 463 |
key: Some(key), |
| 464 |
over, |
| 465 |
.. |
| 466 |
} if *key == pressed => Some((action.clone(), over.clone())), |
| 467 |
_ => None, |
| 468 |
}); |
| 469 |
match claimed { |
| 470 |
Some((action, over)) => { |
| 471 |
let payload = self.gathering(over.as_deref()); |
| 472 |
Self::send(&action, payload) |
| 473 |
} |
| 474 |
None => Step::Idle, |
| 475 |
} |
| 476 |
} |
| 477 |
} |
| 478 |
} |
| 479 |
|
| 480 |
|
| 481 |
|
| 482 |
|
| 483 |
|
| 484 |
|
| 485 |
|
| 486 |
|
| 487 |
pub fn apply(&mut self, request: &Request, response: Response) -> Option<Request> { |
| 488 |
let Response { |
| 489 |
outcome, |
| 490 |
notice, |
| 491 |
address, |
| 492 |
invalidates, |
| 493 |
} = response; |
| 494 |
self.saying = notice.or(self.saying.take()); |
| 495 |
|
| 496 |
match outcome { |
| 497 |
|
| 498 |
|
| 499 |
|
| 500 |
|
| 501 |
|
| 502 |
|
| 503 |
|
| 504 |
|
| 505 |
|
| 506 |
|
| 507 |
|
| 508 |
Outcome::Over(screen) => { |
| 509 |
let under = Layer { |
| 510 |
screen: std::mem::replace(&mut self.screen, screen), |
| 511 |
view: std::mem::replace(&mut self.view, View::new()), |
| 512 |
}; |
| 513 |
self.under.push(under); |
| 514 |
self.view.seed(&self.screen); |
| 515 |
self.announce(); |
| 516 |
None |
| 517 |
} |
| 518 |
|
| 519 |
|
| 520 |
|
| 521 |
|
| 522 |
Outcome::Screen(screen) => { |
| 523 |
self.under.clear(); |
| 524 |
self.remember(request, address.as_ref()); |
| 525 |
self.screen = screen; |
| 526 |
self.view.reset(); |
| 527 |
|
| 528 |
|
| 529 |
|
| 530 |
self.view.seed(&self.screen); |
| 531 |
self.announce(); |
| 532 |
None |
| 533 |
} |
| 534 |
Outcome::Fragment { region, node } => { |
| 535 |
|
| 536 |
|
| 537 |
|
| 538 |
|
| 539 |
|
| 540 |
|
| 541 |
|
| 542 |
|
| 543 |
|
| 544 |
|
| 545 |
let mut missing: Vec<String> = Vec::new(); |
| 546 |
if !self.screen.replace(®ion, node) { |
| 547 |
missing.push(region); |
| 548 |
} |
| 549 |
for stale in invalidates { |
| 550 |
if !self.screen.replace(&stale.region, stale.node) { |
| 551 |
missing.push(stale.region); |
| 552 |
} |
| 553 |
} |
| 554 |
if !missing.is_empty() { |
| 555 |
|
| 556 |
|
| 557 |
let named = missing |
| 558 |
.iter() |
| 559 |
.map(|region| format!("`{region}`")) |
| 560 |
.collect::<Vec<_>>() |
| 561 |
.join(", "); |
| 562 |
let subject = if missing.len() == 1 { "is" } else { "are" }; |
| 563 |
self.saying = Some(Message { |
| 564 |
kind: layout::Notice::Banner, |
| 565 |
tone: layout::Tone::Danger, |
| 566 |
text: format!("nothing on this screen {subject} called {named}"), |
| 567 |
undo: None, |
| 568 |
}); |
| 569 |
} |
| 570 |
self.view.prune(&self.screen); |
| 571 |
self.announce(); |
| 572 |
None |
| 573 |
} |
| 574 |
Outcome::Goto(action) => match Self::call(&action) { |
| 575 |
Step::Call(request) => Some(request), |
| 576 |
|
| 577 |
|
| 578 |
_ => None, |
| 579 |
}, |
| 580 |
} |
| 581 |
} |
| 582 |
|
| 583 |
|
| 584 |
|
| 585 |
|
| 586 |
|
| 587 |
|
| 588 |
|
| 589 |
|
| 590 |
fn key_name(key: Key) -> Option<String> { |
| 591 |
Some(match key { |
| 592 |
Key::Char(ch) => ch.to_string(), |
| 593 |
Key::Enter => "enter".into(), |
| 594 |
Key::Escape => "escape".into(), |
| 595 |
Key::Tab => "tab".into(), |
| 596 |
Key::BackTab => "backtab".into(), |
| 597 |
Key::Up => "up".into(), |
| 598 |
Key::Down => "down".into(), |
| 599 |
Key::PageUp => "pageup".into(), |
| 600 |
Key::PageDown => "pagedown".into(), |
| 601 |
Key::Backspace => "backspace".into(), |
| 602 |
Key::Left => "left".into(), |
| 603 |
Key::Right => "right".into(), |
| 604 |
}) |
| 605 |
} |
| 606 |
|
| 607 |
|
| 608 |
|
| 609 |
|
| 610 |
|
| 611 |
|
| 612 |
|
| 613 |
|
| 614 |
|
| 615 |
|
| 616 |
|
| 617 |
|
| 618 |
|
| 619 |
|
| 620 |
fn moving<'a>(&'a self, reaches: &[crate::focus::Reach]) -> Option<&'a Slot> { |
| 621 |
let here = reaches |
| 622 |
.get(self.view.focus()) |
| 623 |
.and_then(|reach| self.find(&reach.region)) |
| 624 |
.filter(|slot| slot.showing.selective()); |
| 625 |
here.or_else(|| self.screen.slots.iter().find_map(Self::selective)) |
| 626 |
} |
| 627 |
|
| 628 |
|
| 629 |
fn selective(slot: &Slot) -> Option<&Slot> { |
| 630 |
if slot.showing.selective() { |
| 631 |
return Some(slot); |
| 632 |
} |
| 633 |
slot.body.iter().find_map(|node| match node { |
| 634 |
Node::Region(inner) => Self::selective(inner), |
| 635 |
_ => None, |
| 636 |
}) |
| 637 |
} |
| 638 |
|
| 639 |
|
| 640 |
fn find(&self, region: &str) -> Option<&Slot> { |
| 641 |
self.screen.slots.iter().find_map(|slot| slot.find(region)) |
| 642 |
} |
| 643 |
|
| 644 |
|
| 645 |
|
| 646 |
|
| 647 |
|
| 648 |
|
| 649 |
|
| 650 |
fn dismiss(&mut self) -> bool { |
| 651 |
match self.under.pop() { |
| 652 |
Some(layer) => { |
| 653 |
self.screen = layer.screen; |
| 654 |
self.view = layer.view; |
| 655 |
true |
| 656 |
} |
| 657 |
None => false, |
| 658 |
} |
| 659 |
} |
| 660 |
|
| 661 |
fn back(&mut self) -> Step { |
| 662 |
match self.history.pop() { |
| 663 |
Some(request) => { |
| 664 |
self.here = Some(request.clone()); |
| 665 |
Step::Call(request) |
| 666 |
} |
| 667 |
None => Step::Idle, |
| 668 |
} |
| 669 |
} |
| 670 |
|
| 671 |
|
| 672 |
|
| 673 |
|
| 674 |
|
| 675 |
|
| 676 |
|
| 677 |
fn remember(&mut self, request: &Request, address: Option<&quasi_router::Address>) { |
| 678 |
let place = match address { |
| 679 |
Some(quasi_router::Address::Enters(_)) => true, |
| 680 |
Some(quasi_router::Address::Unchanged) => false, |
| 681 |
Some(quasi_router::Address::Replaces(_)) => { |
| 682 |
self.here = Some(request.clone()); |
| 683 |
return; |
| 684 |
} |
| 685 |
None => request.method == Method::Get, |
| 686 |
}; |
| 687 |
if place && let Some(previous) = self.here.replace(request.clone()) { |
| 688 |
self.history.push(previous); |
| 689 |
} |
| 690 |
} |
| 691 |
|
| 692 |
|
| 693 |
fn announce(&mut self) { |
| 694 |
|
| 695 |
|
| 696 |
|
| 697 |
if let Some(Message { |
| 698 |
kind, tone, text, .. |
| 699 |
}) = self.saying.take() |
| 700 |
{ |
| 701 |
self.screen.notices.push(Node::Notice { kind, tone, text }); |
| 702 |
} |
| 703 |
} |
| 704 |
|
| 705 |
|
| 706 |
fn type_into(&mut self, field: &crate::FieldSpot, ch: char) { |
| 707 |
if matches!(field.kind, layout::FieldKind::Checkbox) { |
| 708 |
|
| 709 |
|
| 710 |
|
| 711 |
let ticked = self.view.typed(field) == Node::SELECTED; |
| 712 |
let next = if ticked { |
| 713 |
String::new() |
| 714 |
} else { |
| 715 |
Node::SELECTED.to_string() |
| 716 |
}; |
| 717 |
self.view.set(&field.name, next); |
| 718 |
return; |
| 719 |
} |
| 720 |
|
| 721 |
|
| 722 |
|
| 723 |
|
| 724 |
if let Some(limit) = field.max_length |
| 725 |
&& self.view.typed(field).chars().count() >= limit as usize |
| 726 |
{ |
| 727 |
return; |
| 728 |
} |
| 729 |
self.view.push(field, ch); |
| 730 |
} |
| 731 |
|
| 732 |
|
| 733 |
fn after_typing(&mut self, here: Option<&Spot>) -> Step { |
| 734 |
match here.and_then(Spot::field).and_then(|field| { |
| 735 |
field |
| 736 |
.changes |
| 737 |
.clone() |
| 738 |
.map(|action| (action, field.name.clone())) |
| 739 |
}) { |
| 740 |
|
| 741 |
|
| 742 |
|
| 743 |
|
| 744 |
|
| 745 |
Some((action, name)) => { |
| 746 |
let value = self.view.edit(&name).unwrap_or_default().to_string(); |
| 747 |
let payload = Params::new().with(name, value); |
| 748 |
Self::send(&action, payload) |
| 749 |
} |
| 750 |
None => Step::Idle, |
| 751 |
} |
| 752 |
} |
| 753 |
|
| 754 |
|
| 755 |
|
| 756 |
|
| 757 |
|
| 758 |
|
| 759 |
|
| 760 |
|
| 761 |
|
| 762 |
|
| 763 |
|
| 764 |
|
| 765 |
|
| 766 |
|
| 767 |
fn gathering(&self, over: Option<&str>) -> Params { |
| 768 |
let mut payload = Params::new(); |
| 769 |
if over.is_some() { |
| 770 |
for value in self.view.ticks() { |
| 771 |
payload.insert(Node::TICKED.to_owned(), value.to_owned()); |
| 772 |
} |
| 773 |
} |
| 774 |
payload |
| 775 |
} |
| 776 |
|
| 777 |
|
| 778 |
fn call(action: &Action) -> Step { |
| 779 |
Self::send(action, Params::new()) |
| 780 |
} |
| 781 |
|
| 782 |
|
| 783 |
fn send(action: &Action, extra: Params) -> Step { |
| 784 |
let Some(path) = action.destination.route() else { |
| 785 |
return Step::Open(action.destination.as_str().to_string()); |
| 786 |
}; |
| 787 |
let mut payload = extra; |
| 788 |
payload.absorb(action.params.clone()); |
| 789 |
Step::Call(Request { |
| 790 |
method: action.method, |
| 791 |
path: path.to_string(), |
| 792 |
captures: Params::new(), |
| 793 |
payload, |
| 794 |
carried: action.carried.clone(), |
| 795 |
}) |
| 796 |
} |
| 797 |
} |
| 798 |
|