chore: updated TUI interface
4a904432
1 file(s) · +94 −17
| 2 | 2 | use crossterm::event::{self, Event, KeyCode, KeyModifiers}; |
|
| 3 | 3 | use ratatui::{ |
|
| 4 | 4 | DefaultTerminal, |
|
| 5 | - | layout::{Constraint, Layout}, |
|
| 5 | + | layout::{Alignment, Constraint, Layout}, |
|
| 6 | 6 | style::{Color, Modifier, Style}, |
|
| 7 | 7 | text::{Line, Span, Text}, |
|
| 8 | 8 | widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, Widget}, |
|
| 33 | 33 | focus: Focus, |
|
| 34 | 34 | content_scroll: u16, |
|
| 35 | 35 | show_help: bool, |
|
| 36 | + | confirm_delete: bool, |
|
| 36 | 37 | syntax_set: SyntaxSet, |
|
| 37 | 38 | theme: Theme, |
|
| 38 | 39 | create_name: String, |
|
| 60 | 61 | focus: Focus::List, |
|
| 61 | 62 | content_scroll: 0, |
|
| 62 | 63 | show_help: false, |
|
| 64 | + | confirm_delete: false, |
|
| 63 | 65 | syntax_set, |
|
| 64 | 66 | theme, |
|
| 65 | 67 | create_name: String::new(), |
|
| 439 | 441 | let form_layout = Layout::vertical([ |
|
| 440 | 442 | Constraint::Length(3), |
|
| 441 | 443 | Constraint::Min(1), |
|
| 442 | - | Constraint::Length(1), |
|
| 443 | 444 | ]) |
|
| 444 | 445 | .split(inner); |
|
| 445 | 446 | ||
| 495 | 496 | _ => {} |
|
| 496 | 497 | } |
|
| 497 | 498 | ||
| 498 | - | let hint = Paragraph::new(Line::from(vec![ |
|
| 499 | - | Span::styled("Tab", Style::default().fg(Color::Yellow)), |
|
| 500 | - | Span::raw(" switch field "), |
|
| 501 | - | Span::styled("Ctrl+S", Style::default().fg(Color::Yellow)), |
|
| 502 | - | Span::raw(" save "), |
|
| 503 | - | Span::styled("Esc", Style::default().fg(Color::Yellow)), |
|
| 504 | - | Span::raw(" cancel"), |
|
| 505 | - | ])); |
|
| 506 | - | frame.render_widget(hint, form_layout[2]); |
|
| 507 | 499 | } |
|
| 508 | 500 | _ => { |
|
| 509 | 501 | let highlighted = match app.selected_snippet() { |
|
| 524 | 516 | } |
|
| 525 | 517 | } |
|
| 526 | 518 | ||
| 519 | + | let hints = match app.focus { |
|
| 520 | + | Focus::List => Line::from(vec![ |
|
| 521 | + | Span::styled("j/k", Style::default().fg(Color::Yellow)), |
|
| 522 | + | Span::raw(": Navigate "), |
|
| 523 | + | Span::styled("Enter", Style::default().fg(Color::Yellow)), |
|
| 524 | + | Span::raw(": View "), |
|
| 525 | + | Span::styled("y", Style::default().fg(Color::Yellow)), |
|
| 526 | + | Span::raw(": Copy "), |
|
| 527 | + | Span::styled("d", Style::default().fg(Color::Yellow)), |
|
| 528 | + | Span::raw(": Delete "), |
|
| 529 | + | Span::styled("c", Style::default().fg(Color::Yellow)), |
|
| 530 | + | Span::raw(": Create "), |
|
| 531 | + | Span::styled("?", Style::default().fg(Color::Yellow)), |
|
| 532 | + | Span::raw(": Help "), |
|
| 533 | + | Span::styled("q", Style::default().fg(Color::Yellow)), |
|
| 534 | + | Span::raw(": Quit"), |
|
| 535 | + | ]), |
|
| 536 | + | Focus::Content => Line::from(vec![ |
|
| 537 | + | Span::styled("j/k", Style::default().fg(Color::Yellow)), |
|
| 538 | + | Span::raw(": Scroll "), |
|
| 539 | + | Span::styled("y", Style::default().fg(Color::Yellow)), |
|
| 540 | + | Span::raw(": Copy "), |
|
| 541 | + | Span::styled("Esc", Style::default().fg(Color::Yellow)), |
|
| 542 | + | Span::raw(": Back "), |
|
| 543 | + | Span::styled("?", Style::default().fg(Color::Yellow)), |
|
| 544 | + | Span::raw(": Help"), |
|
| 545 | + | ]), |
|
| 546 | + | Focus::CreateName | Focus::CreateContent => Line::from(vec![ |
|
| 547 | + | Span::styled("Tab", Style::default().fg(Color::Yellow)), |
|
| 548 | + | Span::raw(": Switch field "), |
|
| 549 | + | Span::styled("Ctrl+S", Style::default().fg(Color::Yellow)), |
|
| 550 | + | Span::raw(": Save "), |
|
| 551 | + | Span::styled("Esc", Style::default().fg(Color::Yellow)), |
|
| 552 | + | Span::raw(": Cancel"), |
|
| 553 | + | ]), |
|
| 554 | + | }; |
|
| 555 | + | frame.render_widget(Paragraph::new(hints), outer[1]); |
|
| 556 | + | ||
| 527 | 557 | if let Some((msg, _)) = &app.status_message { |
|
| 528 | - | let status = Paragraph::new(Text::raw(msg.as_str())) |
|
| 529 | - | .style(Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)); |
|
| 530 | - | frame.render_widget(status, outer[1]); |
|
| 558 | + | let area = frame.area(); |
|
| 559 | + | let msg_width = (msg.len() as u16 + 4).max(20).min(area.width.saturating_sub(4)); |
|
| 560 | + | let popup_area = ratatui::layout::Rect { |
|
| 561 | + | x: (area.width.saturating_sub(msg_width)) / 2, |
|
| 562 | + | y: (area.height.saturating_sub(3)) / 2, |
|
| 563 | + | width: msg_width, |
|
| 564 | + | height: 3, |
|
| 565 | + | }; |
|
| 566 | + | Clear.render(popup_area, frame.buffer_mut()); |
|
| 567 | + | let status_popup = Paragraph::new(Line::from(msg.as_str())) |
|
| 568 | + | .style(Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)) |
|
| 569 | + | .alignment(Alignment::Center) |
|
| 570 | + | .block( |
|
| 571 | + | Block::default() |
|
| 572 | + | .borders(Borders::ALL) |
|
| 573 | + | .border_style(Style::default().fg(Color::Green)), |
|
| 574 | + | ); |
|
| 575 | + | frame.render_widget(status_popup, popup_area); |
|
| 576 | + | } |
|
| 577 | + | ||
| 578 | + | if app.confirm_delete { |
|
| 579 | + | let delete_msg = match app.selected_snippet() { |
|
| 580 | + | Some(s) => format!("Delete {}? (y/n)", s.name), |
|
| 581 | + | None => "Delete snippet? (y/n)".to_string(), |
|
| 582 | + | }; |
|
| 583 | + | let area = frame.area(); |
|
| 584 | + | let msg_width = (delete_msg.len() as u16 + 4).max(24).min(area.width.saturating_sub(4)); |
|
| 585 | + | let popup_area = ratatui::layout::Rect { |
|
| 586 | + | x: (area.width.saturating_sub(msg_width)) / 2, |
|
| 587 | + | y: (area.height.saturating_sub(3)) / 2, |
|
| 588 | + | width: msg_width, |
|
| 589 | + | height: 3, |
|
| 590 | + | }; |
|
| 591 | + | Clear.render(popup_area, frame.buffer_mut()); |
|
| 592 | + | let confirm_popup = Paragraph::new(Line::from(delete_msg)) |
|
| 593 | + | .style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)) |
|
| 594 | + | .alignment(Alignment::Center) |
|
| 595 | + | .block( |
|
| 596 | + | Block::default() |
|
| 597 | + | .borders(Borders::ALL) |
|
| 598 | + | .border_style(Style::default().fg(Color::Red)), |
|
| 599 | + | ); |
|
| 600 | + | frame.render_widget(confirm_popup, popup_area); |
|
| 531 | 601 | } |
|
| 532 | 602 | ||
| 533 | 603 | if app.show_help { |
|
| 534 | 604 | let area = frame.area(); |
|
| 535 | - | let popup_width = 44u16.min(area.width.saturating_sub(4)); |
|
| 536 | - | let popup_height = 20u16.min(area.height.saturating_sub(4)); |
|
| 605 | + | let popup_width = 34u16.min(area.width.saturating_sub(4)); |
|
| 606 | + | let popup_height = 17u16.min(area.height.saturating_sub(4)); |
|
| 537 | 607 | let popup_area = ratatui::layout::Rect { |
|
| 538 | 608 | x: (area.width.saturating_sub(popup_width)) / 2, |
|
| 539 | 609 | y: (area.height.saturating_sub(popup_height)) / 2, |
|
| 681 | 751 | if let Event::Key(key) = event::read()? { |
|
| 682 | 752 | if app.show_help { |
|
| 683 | 753 | app.show_help = false; |
|
| 754 | + | } else if app.status_message.is_some() { |
|
| 755 | + | app.status_message = None; |
|
| 756 | + | } else if app.confirm_delete { |
|
| 757 | + | if key.code == KeyCode::Char('y') { |
|
| 758 | + | app.delete_selected(backend); |
|
| 759 | + | } |
|
| 760 | + | app.confirm_delete = false; |
|
| 684 | 761 | } else { |
|
| 685 | 762 | match app.focus { |
|
| 686 | 763 | Focus::List => match key.code { |
|
| 689 | 766 | KeyCode::Char('k') | KeyCode::Up => app.move_up(), |
|
| 690 | 767 | KeyCode::Char('y') => app.copy_selected(), |
|
| 691 | 768 | KeyCode::Char('Y') => app.copy_link(), |
|
| 692 | - | KeyCode::Char('d') => app.delete_selected(backend), |
|
| 769 | + | KeyCode::Char('d') => app.confirm_delete = true, |
|
| 693 | 770 | KeyCode::Char('c') => app.start_create(), |
|
| 694 | 771 | KeyCode::Char('o') => app.open_in_browser(), |
|
| 695 | 772 | KeyCode::Char('r') if app.is_remote => app.refresh(backend), |
|