Skip to content

Commit d9d2c5c

Browse files
authored
Feat/better results (#15)
* Feat: display proper markdown * Cleanup and looks better imo * Feat: more cleanup * Feat: loading works * Feat: result page * Unused imports
1 parent 108d61e commit d9d2c5c

File tree

8 files changed

+440
-247
lines changed

8 files changed

+440
-247
lines changed

src/cmd/submit.rs

Lines changed: 148 additions & 209 deletions
Large diffs are not rendered by default.

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod cmd;
22
mod models;
33
mod service;
44
mod utils;
5+
mod views;
56

67
use crate::cmd::Cli;
78
use clap::Parser;

src/models/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ impl SubmissionModeItem {
4343
}
4444
}
4545

46-
#[derive(Clone, Copy, Debug, PartialEq)]
47-
pub enum ModelState {
46+
#[derive(Clone, Copy, Debug, PartialEq, Default)]
47+
pub enum AppState {
48+
#[default]
4849
LeaderboardSelection,
4950
GpuSelection,
5051
SubmissionModeSelection,

src/service/mod.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,8 @@ pub async fn submit_solution<P: AsRef<Path>>(
175175
"status" => (),
176176
"result" => {
177177
let result_val: Value = serde_json::from_str(data)?;
178-
let pretty_result = match result_val.get("results") {
179-
Some(result_obj) => serde_json::to_string_pretty(result_obj)?,
180-
None => {
181-
return Err(anyhow!(
182-
"Invalid 'result' event structure: missing 'results' field"
183-
))
184-
}
185-
};
186-
return Ok(pretty_result);
178+
let reports = result_val.get("reports").unwrap();
179+
return Ok(reports.to_string());
187180
}
188181
"error" => {
189182
let error_val: Value = serde_json::from_str(data)?;

src/utils/mod.rs

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -48,33 +48,68 @@ pub fn get_popcorn_directives<P: AsRef<Path>>(filepath: P) -> Result<(PopcornDir
4848
))
4949
}
5050

51-
pub fn display_ascii_art() {
51+
pub fn get_ascii_art() -> String {
5252
let art = r#"
53-
_ __ _ ______ _
54-
| | / / | | | ___ \ | |
55-
| |/ / ___ _ __ _ __ ___ | | | |_/ / ___ _| |_
56-
| \ / _ \ '__| '_ \ / _ \| | | ___ \ / _ \| | __|
57-
| |\ \ __/ | | | | | __/| | | |_/ /| (_) | | |_
58-
\_| \_/\___|_| |_| |_|\___|_/ \____/ \___/|_|\__|
59-
60-
POPCORN CLI - GPU MODE
61-
62-
┌───────────────────────────────────────┐
63-
│ ┌─────┐ ┌─────┐ ┌─────┐ │
64-
│ │ooOoo│ │ooOoo│ │ooOoo│ │▒
65-
│ │oOOOo│ │oOOOo│ │oOOOo│ │▒
66-
│ │ooOoo│ │ooOoo│ │ooOoo│ ┌────────┐ │▒
67-
│ └─────┘ └─────┘ └─────┘ │████████│ │▒
68-
│ │████████│ │▒
69-
│ ┌────────────────────────┐ │████████│ │▒
70-
│ │ │ │████████│ │▒
71-
│ │ POPCORN GPU COMPUTE │ └────────┘ │▒
72-
│ │ │ │▒
73-
│ └────────────────────────┘ │▒
74-
│ │▒
75-
└───────────────────────────────────────┘▒
76-
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
77-
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
78-
"#;
53+
_ __ _ ______ _
54+
| | / / | | | ___ \ | |
55+
| |/ / ___ _ __ _ __ ___ | | | |_/ / ___ _| |_
56+
| \ / _ \ '__| '_ \ / _ \| | | ___ \ / _ \| | __|
57+
| |\ \ __/ | | | | | __/| | | |_/ /| (_) | | |_
58+
\_| \_/\___|_| |_| |_|\___|_/ \____/ \___/|_|\__|
59+
60+
POPCORN CLI - GPU MODE
61+
62+
┌───────────────────────────────────────┐
63+
│ ┌─────┐ ┌─────┐ ┌─────┐ │
64+
│ │ooOoo│ │ooOoo│ │ooOoo│ │▒
65+
│ │oOOOo│ │oOOOo│ │oOOOo│ │▒
66+
│ │ooOoo│ │ooOoo│ │ooOoo│ ┌────────┐ │▒
67+
│ └─────┘ └─────┘ └─────┘ │████████│ │▒
68+
│ │████████│ │▒
69+
│ ┌────────────────────────┐ │████████│ │▒
70+
│ │ │ │████████│ │▒
71+
│ │ POPCORN GPU COMPUTE │ └────────┘ │▒
72+
│ │ │ │▒
73+
│ └────────────────────────┘ │▒
74+
│ │▒
75+
└───────────────────────────────────────┘▒
76+
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
77+
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
78+
"#;
79+
80+
art.to_string()
81+
}
82+
83+
84+
pub fn display_ascii_art() {
85+
let art = get_ascii_art();
7986
println!("{}", art);
8087
}
88+
89+
pub fn custom_wrap(initial_text: String, remaining_text: String, available_width: usize) -> Vec<String> {
90+
let mut lines = vec![initial_text];
91+
let mut current_line = String::with_capacity(available_width);
92+
for word in remaining_text.split_whitespace() {
93+
if word.len() > available_width {
94+
if !current_line.is_empty() {
95+
lines.push(current_line.clone());
96+
current_line.clear();
97+
}
98+
lines.push(word.to_string());
99+
} else if current_line.is_empty() {
100+
current_line.push_str(word);
101+
} else if current_line.len() + word.len() + 1 <= available_width {
102+
current_line.push(' ');
103+
current_line.push_str(word);
104+
} else {
105+
lines.push(current_line.clone());
106+
current_line.clear();
107+
current_line.push_str(word);
108+
}
109+
}
110+
111+
if !current_line.is_empty() {
112+
lines.push(current_line);
113+
}
114+
lines
115+
}

src/views/loading_page.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use ratatui::{
2+
buffer::Buffer,
3+
layout::{Alignment, Layout, Rect},
4+
style::{Color, Stylize},
5+
widgets::{Block, Gauge, Padding, Paragraph, StatefulWidget, Widget},
6+
};
7+
8+
#[derive(Debug, Default, Clone)]
9+
pub struct LoadingPageState {
10+
pub loop_count: u16,
11+
pub progress_column: u16,
12+
pub progress_bar: f64,
13+
}
14+
15+
#[derive(Default, Debug, PartialEq, Eq, Clone)]
16+
pub struct LoadingPage {
17+
header_area: Rect,
18+
gauge_area: Rect,
19+
footer_area: Rect,
20+
}
21+
22+
const GAUGE_COLOR: Color = ratatui::style::palette::tailwind::RED.c800;
23+
24+
impl StatefulWidget for &LoadingPage {
25+
type State = LoadingPageState;
26+
27+
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
28+
use ratatui::layout::Constraint::Percentage;
29+
30+
let layout = Layout::vertical([Percentage(45), Percentage(10), Percentage(45)]);
31+
32+
let [_, gauge_area, footer_area] = layout.areas(area);
33+
34+
render_gauge(gauge_area, buf, state);
35+
render_footer(footer_area, buf, state);
36+
}
37+
}
38+
39+
fn render_gauge(area: Rect, buf: &mut Buffer, state: &mut LoadingPageState) {
40+
let blk = Block::default().padding(Padding::horizontal(20));
41+
Gauge::default()
42+
.block(blk)
43+
.gauge_style(GAUGE_COLOR)
44+
.ratio(state.progress_bar / 100.0)
45+
.render(area, buf);
46+
}
47+
48+
fn get_footer_text(state: &LoadingPageState) -> String {
49+
let percentage = state.progress_bar;
50+
51+
if state.loop_count > 0 {
52+
return "Did you know we have zero idea how long this will take?".to_string();
53+
}
54+
55+
if percentage > 75.0 {
56+
return "Almost there!".to_string();
57+
} else if percentage > 35.0 {
58+
return "Crunching numbers...".to_string();
59+
} else {
60+
return "This is taking a while, huh?".to_string();
61+
}
62+
}
63+
64+
fn render_footer(area: Rect, buf: &mut Buffer, state: &LoadingPageState) {
65+
let blk = Block::default().padding(Padding::vertical(1));
66+
let text = Paragraph::new(get_footer_text(state))
67+
.alignment(Alignment::Center)
68+
.fg(Color::White)
69+
.bold()
70+
.block(blk);
71+
72+
text.render(area, buf);
73+
}

src/views/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod result_page;
2+
pub mod loading_page;

src/views/result_page.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use crate::utils;
2+
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
3+
use ratatui::{
4+
layout::{Alignment, Constraint, Layout, Margin, Rect},
5+
prelude::Buffer,
6+
style::{Color, Style},
7+
symbols::scrollbar,
8+
widgets::{Block, BorderType, Paragraph, Scrollbar, ScrollbarState, StatefulWidget, Widget},
9+
};
10+
11+
#[derive(Default, Debug)]
12+
pub struct ResultPageState {
13+
pub vertical_scroll: u16,
14+
pub vertical_scroll_state: ScrollbarState,
15+
pub horizontal_scroll: u16,
16+
pub horizontal_scroll_state: ScrollbarState,
17+
pub ack: bool,
18+
}
19+
20+
#[derive(Default, Debug)]
21+
pub struct ResultPage {
22+
result_text: Paragraph<'static>,
23+
}
24+
25+
impl ResultPage {
26+
pub fn new(result_text: String, state: &mut ResultPageState) -> Self {
27+
let max_width = result_text
28+
.lines()
29+
.map(|line| line.len())
30+
.max()
31+
.unwrap_or(0);
32+
33+
let num_lines = result_text.lines().count();
34+
35+
state.vertical_scroll_state = state
36+
.vertical_scroll_state
37+
.content_length(num_lines);
38+
39+
state.horizontal_scroll_state = state.horizontal_scroll_state.content_length(max_width);
40+
41+
Self {
42+
result_text: Paragraph::new(result_text),
43+
}
44+
}
45+
46+
fn render_left(&self, buf: &mut Buffer, left: Rect) {
47+
let left_block = Block::bordered()
48+
.border_type(BorderType::Plain)
49+
.border_style(Style::default().fg(Color::Yellow));
50+
51+
let left_text = Paragraph::new(utils::get_ascii_art());
52+
53+
left_text.block(left_block).render(left, buf);
54+
}
55+
56+
fn render_right(&self, buf: &mut Buffer, right: Rect, state: &mut ResultPageState) {
57+
let right_block = Block::bordered()
58+
.border_type(BorderType::Plain)
59+
.border_style(Style::default().fg(Color::Yellow))
60+
.title_bottom("Press q to quit...")
61+
.title_style(Style::default().fg(Color::Red))
62+
.title_alignment(Alignment::Right);
63+
64+
let result_text = self
65+
.result_text
66+
.clone()
67+
.block(right_block)
68+
.scroll((state.vertical_scroll as u16, state.horizontal_scroll as u16));
69+
result_text.render(right, buf);
70+
}
71+
72+
pub fn handle_key_event(&mut self, state: &mut ResultPageState) {
73+
if event::poll(std::time::Duration::from_millis(50)).unwrap() {
74+
if let Event::Key(key) = event::read().unwrap() {
75+
if key.kind != KeyEventKind::Press {
76+
return;
77+
}
78+
if key.code == KeyCode::Char('q') {
79+
state.ack = true;
80+
}
81+
82+
match key.code {
83+
KeyCode::Char('j') | KeyCode::Down => {
84+
state.vertical_scroll = state.vertical_scroll.saturating_add(1);
85+
state.vertical_scroll_state = state
86+
.vertical_scroll_state
87+
.position(state.vertical_scroll as usize);
88+
}
89+
KeyCode::Char('k') | KeyCode::Up => {
90+
state.vertical_scroll = state.vertical_scroll.saturating_sub(1);
91+
state.vertical_scroll_state = state
92+
.vertical_scroll_state
93+
.position(state.vertical_scroll as usize);
94+
}
95+
KeyCode::Char('h') | KeyCode::Left => {
96+
state.horizontal_scroll = state.horizontal_scroll.saturating_sub(1);
97+
state.horizontal_scroll_state = state
98+
.horizontal_scroll_state
99+
.position(state.horizontal_scroll as usize);
100+
}
101+
KeyCode::Char('l') | KeyCode::Right => {
102+
state.horizontal_scroll = state.horizontal_scroll.saturating_add(1);
103+
state.horizontal_scroll_state = state
104+
.horizontal_scroll_state
105+
.position(state.horizontal_scroll as usize);
106+
}
107+
_ => {}
108+
}
109+
}
110+
}
111+
}
112+
}
113+
114+
impl StatefulWidget for &ResultPage {
115+
type State = ResultPageState;
116+
117+
fn render(self, area: Rect, buf: &mut Buffer, state: &mut ResultPageState) {
118+
let layout = Layout::horizontal([Constraint::Percentage(45), Constraint::Percentage(55)]);
119+
let [left, right] = layout.areas(area);
120+
121+
self.render_left(buf, left);
122+
self.render_right(buf, right, state);
123+
124+
let vertical_scrollbar =
125+
Scrollbar::new(ratatui::widgets::ScrollbarOrientation::VerticalLeft)
126+
.symbols(scrollbar::VERTICAL);
127+
128+
let horizontal_scrollbar =
129+
Scrollbar::new(ratatui::widgets::ScrollbarOrientation::HorizontalBottom)
130+
.symbols(scrollbar::HORIZONTAL);
131+
132+
vertical_scrollbar.render(
133+
right.inner(&Margin {
134+
vertical: 1,
135+
horizontal: 0,
136+
}),
137+
buf,
138+
&mut state.vertical_scroll_state,
139+
);
140+
horizontal_scrollbar.render(
141+
right.inner(&Margin {
142+
vertical: 0,
143+
horizontal: 1,
144+
}),
145+
buf,
146+
&mut state.horizontal_scroll_state,
147+
);
148+
}
149+
}

0 commit comments

Comments
 (0)