node_launchpad/components/
help.rs

1use super::header::SelectedMenuItem;
2use color_eyre::eyre::Result;
3use ratatui::{
4    layout::{Constraint, Direction, Layout, Rect},
5    style::{Style, Stylize},
6    text::Span,
7    widgets::{Block, Borders, Padding},
8    Frame,
9};
10use tokio::sync::mpsc::UnboundedSender;
11
12use super::Component;
13use crate::{
14    action::Action,
15    components::header::Header,
16    mode::{InputMode, Scene},
17    style::{COOL_GREY, GHOST_WHITE, VIVID_SKY_BLUE},
18    widgets::hyperlink::Hyperlink,
19};
20
21#[derive(Clone)]
22pub struct Help {
23    pub active: bool,
24    pub action_tx: Option<UnboundedSender<Action>>,
25}
26
27impl Help {
28    pub async fn new() -> Result<Self> {
29        Ok(Self {
30            active: false,
31            action_tx: None,
32        })
33    }
34}
35
36impl Component for Help {
37    fn init(&mut self, _area: Rect) -> Result<()> {
38        Ok(())
39    }
40
41    fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()> {
42        if !self.active {
43            return Ok(());
44        }
45        // We define a layout, top and down box.
46        let layout = Layout::default()
47            .direction(Direction::Vertical)
48            .constraints(vec![Constraint::Length(1), Constraint::Length(9)])
49            .split(area);
50
51        // ==== Header =====
52        let header = Header::new();
53        f.render_stateful_widget(header, layout[0], &mut SelectedMenuItem::Help);
54
55        // ---- Get Help & Support ----
56        // Links
57
58        // Create a new layout as a table, so we can render hyperlinks
59        let columns_layout = Layout::default()
60            .direction(Direction::Horizontal)
61            .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
62            .split(layout[1]);
63
64        let padded_area_left = Rect {
65            x: columns_layout[0].x + 2,
66            y: columns_layout[0].y + 2,
67            width: columns_layout[0].width - 2,
68            height: columns_layout[0].height - 2,
69        };
70
71        let left_column = Layout::default()
72            .direction(Direction::Vertical)
73            .constraints(vec![
74                Constraint::Max(1),
75                Constraint::Max(2),
76                Constraint::Max(1),
77                Constraint::Max(2),
78            ])
79            .split(padded_area_left);
80
81        let padded_area_right = Rect {
82            x: columns_layout[1].x + 2,
83            y: columns_layout[1].y + 2,
84            width: columns_layout[1].width - 2,
85            height: columns_layout[1].height - 2,
86        };
87        let right_column = Layout::default()
88            .direction(Direction::Vertical)
89            .constraints(vec![
90                Constraint::Max(1),
91                Constraint::Max(2),
92                Constraint::Max(1),
93                Constraint::Max(2),
94            ])
95            .split(padded_area_right);
96
97        let quickstart_guide_link = Hyperlink::new(
98            Span::styled(
99                "autonomi.com/getstarted",
100                Style::default().fg(VIVID_SKY_BLUE).underlined(),
101            ),
102            "https://autonomi.com/getstarted",
103        );
104        let terms_and_conditions_link = Hyperlink::new(
105            Span::styled(
106                "autonomi.com/terms",
107                Style::default().fg(VIVID_SKY_BLUE).underlined(),
108            ),
109            "https://autonomi.com/terms",
110        );
111        let get_direct_support_link = Hyperlink::new(
112            Span::styled(
113                "autonomi.com/support",
114                Style::default().fg(VIVID_SKY_BLUE).underlined(),
115            ),
116            "https://autonomi.com/support",
117        );
118        let download_latest_link = Hyperlink::new(
119            Span::styled(
120                "autonomi.com/downloads",
121                Style::default().fg(VIVID_SKY_BLUE).underlined(),
122            ),
123            "https://autonomi.com/downloads",
124        );
125
126        let block = Block::new()
127            .borders(Borders::ALL)
128            .border_style(Style::default().fg(COOL_GREY))
129            .padding(Padding::uniform(1))
130            .title(" Get Help & Support ")
131            .bold()
132            .title_style(Style::default().bold().fg(GHOST_WHITE));
133
134        // Render hyperlinks in the new area
135        f.render_widget(
136            Span::styled(
137                "Read the quick start guides:",
138                Style::default().fg(GHOST_WHITE),
139            ),
140            left_column[0],
141        );
142        f.render_widget_ref(quickstart_guide_link, left_column[1]);
143        f.render_widget(
144            Span::styled("Get Direct Support:", Style::default().fg(GHOST_WHITE)),
145            left_column[2],
146        );
147        f.render_widget_ref(get_direct_support_link, left_column[3]);
148        f.render_widget(
149            Span::styled(
150                "Download the latest launchpad:",
151                Style::default().fg(GHOST_WHITE),
152            ),
153            right_column[0],
154        );
155        f.render_widget_ref(download_latest_link, right_column[1]);
156        f.render_widget(
157            Span::styled("Terms & Conditions:", Style::default().fg(GHOST_WHITE)),
158            right_column[2],
159        );
160        f.render_widget_ref(terms_and_conditions_link, right_column[3]);
161
162        f.render_widget(block, layout[1]);
163
164        Ok(())
165    }
166
167    fn update(&mut self, action: Action) -> Result<Option<Action>> {
168        if let Action::SwitchScene(scene) = action {
169            if let Scene::Help = scene {
170                self.active = true;
171                // make sure we're in navigation mode
172                return Ok(Some(Action::SwitchInputMode(InputMode::Navigation)));
173            } else {
174                self.active = false;
175            }
176        }
177        Ok(None)
178    }
179}