[Add] Markdown viewer & Document loading
Todo: Track accepted Version & Hash of accepted document
This commit is contained in:
parent
4239e903c1
commit
53a057c6e1
8 changed files with 591 additions and 32 deletions
|
|
@ -1 +0,0 @@
|
|||
pub mod terms_checker;
|
||||
|
|
@ -9,17 +9,16 @@ use tokio::time::{Duration, sleep};
|
|||
mod auth;
|
||||
mod communities;
|
||||
mod data;
|
||||
mod eula;
|
||||
mod gui;
|
||||
mod langu;
|
||||
mod omikron;
|
||||
mod server;
|
||||
mod terms;
|
||||
mod users;
|
||||
mod util;
|
||||
|
||||
use crate::communities::community_manager;
|
||||
use crate::communities::interactables::registry;
|
||||
use crate::eula::terms_checker;
|
||||
use crate::gui::app_state::AppState;
|
||||
use crate::gui::input_handler;
|
||||
use crate::gui::log_panel;
|
||||
|
|
@ -29,6 +28,7 @@ use crate::langu::language_creator;
|
|||
use crate::langu::language_manager::format;
|
||||
use crate::omikron::omikron_connection::{OMIKRON_CONNECTION, OmikronConnection};
|
||||
use crate::server::server::start;
|
||||
use crate::terms::terms_checker;
|
||||
use crate::users::user_manager;
|
||||
use crate::util::config_util::CONFIG;
|
||||
use crate::util::file_util::has_dir;
|
||||
|
|
@ -48,7 +48,7 @@ async fn main() {
|
|||
*SHUTDOWN.write().await = false;
|
||||
|
||||
// EULA
|
||||
let (tos, pp) = terms_checker::ConsentManager::check();
|
||||
let (tos, pp) = terms_checker::ConsentManager::check().await;
|
||||
if !tos {
|
||||
println!("You need to accept our End User Licence Agreement before launching!");
|
||||
println!("You can find this at 'agreements'!");
|
||||
|
|
@ -58,6 +58,7 @@ async fn main() {
|
|||
println!(
|
||||
"Please accept our Privacy Policy & Terms of Serivce before using Tensamin Services!"
|
||||
);
|
||||
println!("In future releases this will be optional!");
|
||||
println!("You can find this at 'agreements'!");
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
427
src/terms/md_viewer.rs
Normal file
427
src/terms/md_viewer.rs
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
use crossterm::event::{self, Event, KeyCode};
|
||||
use ratatui::{
|
||||
DefaultTerminal,
|
||||
prelude::*,
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Paragraph, Wrap},
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FileViewer {
|
||||
title: String,
|
||||
text: Vec<DisplayLine>,
|
||||
scroll: u16,
|
||||
scroll_x: u16,
|
||||
}
|
||||
|
||||
impl FileViewer {
|
||||
pub fn new(title: String, content: &str) -> Self {
|
||||
Self {
|
||||
title,
|
||||
text: parse_document(content.to_owned()),
|
||||
scroll: 0,
|
||||
scroll_x: 0,
|
||||
}
|
||||
}
|
||||
pub fn force_popup(mut self, mut terminal: DefaultTerminal) -> DefaultTerminal {
|
||||
loop {
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let area = f.area();
|
||||
self.draw(f, area);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
if event::poll(Duration::from_millis(100)).unwrap() {
|
||||
let ev = event::read().unwrap();
|
||||
self.handle_event(&ev);
|
||||
|
||||
if matches!(ev, Event::Key(k) if k.code == KeyCode::Char('q')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
terminal
|
||||
}
|
||||
fn draw(&self, f: &mut Frame, area: Rect) {
|
||||
use ratatui::text::Text;
|
||||
|
||||
let mut rendered_lines = Vec::new();
|
||||
|
||||
for display_line in &self.text {
|
||||
if display_line.scrollable {
|
||||
let content: String = display_line
|
||||
.line
|
||||
.spans
|
||||
.iter()
|
||||
.map(|s| s.content.clone())
|
||||
.collect();
|
||||
|
||||
let start = self.scroll_x as usize;
|
||||
let width = area.width as usize - 2;
|
||||
|
||||
let visible = if start < content.chars().count() {
|
||||
content.chars().skip(start).take(width).collect()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let mut chars: Vec<char> = visible.chars().collect();
|
||||
|
||||
if start > 0 && !chars.is_empty() {
|
||||
chars[0] = '<';
|
||||
}
|
||||
|
||||
if start + width < content.chars().count() && !chars.is_empty() {
|
||||
let last = chars.len() - 1;
|
||||
chars[last] = '>';
|
||||
}
|
||||
|
||||
let visible: String = chars.into_iter().collect();
|
||||
|
||||
rendered_lines.push(Line::from(Span::styled(
|
||||
visible,
|
||||
display_line
|
||||
.line
|
||||
.spans
|
||||
.first()
|
||||
.map(|s| s.style)
|
||||
.unwrap_or_default(),
|
||||
)));
|
||||
} else {
|
||||
rendered_lines.push(display_line.line.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let paragraph = Paragraph::new(Text::from(rendered_lines))
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(self.title.as_str()),
|
||||
)
|
||||
.wrap(Wrap { trim: false })
|
||||
.scroll((self.scroll, 0));
|
||||
|
||||
f.render_widget(paragraph, area);
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, event: &Event) {
|
||||
if let Event::Key(key) = event {
|
||||
match key.code {
|
||||
KeyCode::Down => self.scroll = self.scroll.saturating_add(1),
|
||||
KeyCode::Up => self.scroll = self.scroll.saturating_sub(1),
|
||||
KeyCode::PageDown => self.scroll = self.scroll.saturating_add(10),
|
||||
KeyCode::PageUp => self.scroll = self.scroll.saturating_sub(10),
|
||||
KeyCode::Right => self.scroll_x = self.scroll_x.saturating_add(2),
|
||||
KeyCode::Left => self.scroll_x = self.scroll_x.saturating_sub(2),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn parse_document(input: String) -> Vec<DisplayLine> {
|
||||
let mut lines_vec = Vec::new();
|
||||
let mut in_code_block = false;
|
||||
let liness: Vec<String> = input.lines().map(String::from).collect();
|
||||
let mut i = 0;
|
||||
|
||||
while i < liness.len() {
|
||||
let raw = &liness[i];
|
||||
|
||||
if raw.trim().starts_with("```") {
|
||||
in_code_block = !in_code_block;
|
||||
let code: String = if raw.trim().replace("```", "").is_empty() {
|
||||
"──".to_string()
|
||||
} else {
|
||||
raw.trim().replace("```", "")
|
||||
};
|
||||
lines_vec.push(DisplayLine {
|
||||
line: Line::from(Span::styled(
|
||||
format!("────────{}────────", code),
|
||||
Style::default().fg(Color::DarkGray),
|
||||
)),
|
||||
scrollable: false,
|
||||
});
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if in_code_block {
|
||||
lines_vec.push(DisplayLine {
|
||||
line: Line::from(Span::styled(
|
||||
raw.to_string(),
|
||||
Style::default().fg(Color::Yellow),
|
||||
)),
|
||||
scrollable: false,
|
||||
});
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if raw.starts_with("### ") {
|
||||
lines_vec.push(DisplayLine {
|
||||
line: Line::from(Span::styled(
|
||||
raw.trim_start_matches("### ").to_string(),
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
scrollable: false,
|
||||
});
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if raw.starts_with("## ") {
|
||||
lines_vec.push(DisplayLine {
|
||||
line: Line::from(Span::styled(
|
||||
raw.trim_start_matches("## ").to_string(),
|
||||
Style::default()
|
||||
.fg(Color::LightCyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
scrollable: false,
|
||||
});
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if raw.starts_with("# ") {
|
||||
lines_vec.push(DisplayLine {
|
||||
line: Line::from(Span::styled(
|
||||
raw.trim_start_matches("# ").to_string(),
|
||||
Style::default()
|
||||
.fg(Color::Gray)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
scrollable: false,
|
||||
});
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if raw.trim_start().starts_with("- ") {
|
||||
let indent = raw.chars().take_while(|c| *c == ' ').count();
|
||||
lines_vec.push(DisplayLine {
|
||||
line: Line::from(Span::raw(format!(
|
||||
"{}• {}",
|
||||
" ".repeat(indent),
|
||||
raw.trim_start_matches("- ")
|
||||
))),
|
||||
scrollable: false,
|
||||
});
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if raw.trim().starts_with('|') && raw.contains('|') {
|
||||
let mut table_lines = vec![raw.clone()];
|
||||
let mut j = i + 1;
|
||||
while j < liness.len() && liness[j].trim().starts_with('|') {
|
||||
table_lines.push(liness[j].clone());
|
||||
j += 1;
|
||||
}
|
||||
|
||||
let table = parse_table(&table_lines.iter().map(|s| s.as_str()).collect::<Vec<_>>());
|
||||
lines_vec.extend(table_to_lines(table));
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
|
||||
lines_vec.push(DisplayLine {
|
||||
line: Line::from(parse_inline(raw.as_str())),
|
||||
scrollable: false,
|
||||
});
|
||||
i += 1;
|
||||
}
|
||||
|
||||
lines_vec
|
||||
}
|
||||
|
||||
fn parse_inline(input: &str) -> Vec<Span<'static>> {
|
||||
let mut spans = Vec::new();
|
||||
let mut buf = String::new();
|
||||
|
||||
let mut bold = false;
|
||||
let mut underline = false;
|
||||
let mut code = false;
|
||||
|
||||
let mut chars = input.chars().peekable();
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
let toggle = match c {
|
||||
'*' if chars.peek() == Some(&'*') => {
|
||||
chars.next();
|
||||
Some("bold")
|
||||
}
|
||||
'_' if chars.peek() == Some(&'_') => {
|
||||
chars.next();
|
||||
Some("underline")
|
||||
}
|
||||
'`' => Some("code"),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(kind) = toggle {
|
||||
flush_span(&mut spans, &mut buf, current_style(bold, underline, code));
|
||||
|
||||
match kind {
|
||||
"bold" => bold = !bold,
|
||||
"underline" => underline = !underline,
|
||||
"code" => code = !code,
|
||||
_ => {}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
buf.push(c);
|
||||
}
|
||||
|
||||
flush_span(&mut spans, &mut buf, current_style(bold, underline, code));
|
||||
spans
|
||||
}
|
||||
|
||||
fn current_style(bold: bool, underline: bool, code: bool) -> Style {
|
||||
let mut style = Style::default();
|
||||
|
||||
if bold {
|
||||
style = style.add_modifier(Modifier::BOLD);
|
||||
}
|
||||
if underline {
|
||||
style = style.add_modifier(Modifier::UNDERLINED);
|
||||
}
|
||||
if code {
|
||||
style = style.fg(Color::Yellow);
|
||||
}
|
||||
|
||||
style
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct DisplayLine {
|
||||
line: Line<'static>,
|
||||
scrollable: bool,
|
||||
}
|
||||
|
||||
fn table_to_lines(table: Vec<Vec<String>>) -> Vec<DisplayLine> {
|
||||
if table.len() < 2 {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let header = &table[0];
|
||||
|
||||
let widths: Vec<usize> = header
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, h)| {
|
||||
let h_len = h.chars().count().max(1);
|
||||
if i == 0 {
|
||||
table
|
||||
.iter()
|
||||
.map(|row| row.get(i).map(|c| c.chars().count()).unwrap_or(0))
|
||||
.max()
|
||||
.unwrap_or(h_len)
|
||||
} else {
|
||||
let max = (3 * h_len) as usize;
|
||||
max.max(h_len)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut lines = Vec::new();
|
||||
|
||||
for (row_idx, row) in table.iter().enumerate() {
|
||||
if row_idx == 1 {
|
||||
let divider = widths
|
||||
.iter()
|
||||
.map(|w| "─".repeat(*w))
|
||||
.collect::<Vec<_>>()
|
||||
.join("─┼─");
|
||||
|
||||
lines.push(DisplayLine {
|
||||
line: Line::from(Span::styled(divider, Style::default().fg(Color::DarkGray))),
|
||||
scrollable: true,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
let wrapped_cells: Vec<Vec<String>> = row
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, cell)| wrap_cell(cell, widths[i]))
|
||||
.collect();
|
||||
|
||||
let row_height = wrapped_cells.iter().map(|c| c.len()).max().unwrap_or(1);
|
||||
|
||||
for line_idx in 0..row_height {
|
||||
let mut line = String::new();
|
||||
|
||||
for (i, cell) in wrapped_cells.iter().enumerate() {
|
||||
let content = cell.get(line_idx).map(String::as_str).unwrap_or("");
|
||||
line.push_str(&format!("{:width$}", content, width = widths[i]));
|
||||
if i < wrapped_cells.len() - 1 {
|
||||
line.push_str(" │ ");
|
||||
}
|
||||
}
|
||||
|
||||
let style = if row_idx == 0 {
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(Color::Green)
|
||||
};
|
||||
|
||||
lines.push(DisplayLine {
|
||||
line: Line::from(Span::styled(line, style)),
|
||||
scrollable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lines
|
||||
}
|
||||
fn wrap_cell(cell: &str, width: usize) -> Vec<String> {
|
||||
if width == 0 {
|
||||
return vec![String::new()];
|
||||
}
|
||||
|
||||
let mut out = Vec::new();
|
||||
let mut chars = cell.chars();
|
||||
|
||||
loop {
|
||||
let line: String = chars.by_ref().take(width).collect();
|
||||
if line.is_empty() {
|
||||
break;
|
||||
}
|
||||
out.push(line);
|
||||
}
|
||||
|
||||
if out.is_empty() {
|
||||
out.push(String::new());
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn flush_span(spans: &mut Vec<Span>, buf: &mut String, style: Style) {
|
||||
if !buf.is_empty() {
|
||||
spans.push(Span::styled(buf.clone(), style));
|
||||
buf.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_table(lines: &[&str]) -> Vec<Vec<String>> {
|
||||
let mut table = Vec::new();
|
||||
|
||||
for &line in lines {
|
||||
if !line.starts_with('|') || !line.contains('|') {
|
||||
break;
|
||||
}
|
||||
let row: Vec<String> = line
|
||||
.trim_matches('|')
|
||||
.split('|')
|
||||
.map(|s| s.trim().to_string())
|
||||
.collect();
|
||||
table.push(row);
|
||||
}
|
||||
|
||||
table
|
||||
}
|
||||
3
src/terms/mod.rs
Normal file
3
src/terms/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub mod md_viewer;
|
||||
pub mod terms_checker;
|
||||
pub mod terms_getter;
|
||||
|
|
@ -1,4 +1,10 @@
|
|||
use crate::util::file_util::{load_file, save_file};
|
||||
use crate::{
|
||||
terms::{
|
||||
md_viewer::FileViewer,
|
||||
terms_getter::{Type, get_link, get_terms},
|
||||
},
|
||||
util::file_util::{load_file, save_file},
|
||||
};
|
||||
use crossterm::event::{self, Event, KeyCode};
|
||||
use ratatui::{
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
|
|
@ -11,14 +17,14 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|||
|
||||
pub struct ConsentManager;
|
||||
impl ConsentManager {
|
||||
pub fn check() -> (bool, bool) {
|
||||
pub async fn check() -> (bool, bool) {
|
||||
let file = load_file("", "agreements");
|
||||
let existing = ConsentUiState::from_str(&file).sanitize();
|
||||
|
||||
let final_state = if existing.eula {
|
||||
existing
|
||||
} else {
|
||||
let choice = run_consent_ui();
|
||||
let choice = run_consent_ui().await;
|
||||
let state = match choice {
|
||||
UserChoice::Deny => ConsentUiState::denied(),
|
||||
UserChoice::AcceptEULA => ConsentUiState {
|
||||
|
|
@ -92,11 +98,11 @@ impl ConsentUiState {
|
|||
.unwrap()
|
||||
.as_secs();
|
||||
format!(
|
||||
"\"EULA=true\" indicates that you read and accepted the End User Licence agreement. You can find our EULA at https://docs.tensamin.net/legal/eula/\
|
||||
"\"EULA=true\" indicates that you read and accepted the End User Licence agreement. You can find our EULA at https://legal.tensamin.net/eula/\
|
||||
\nEULA={}\
|
||||
\n\"PrivacyPolicy=true\" indicates that you read and accepted the Privacy Policy. You can find our Privacy Policy at https://docs.tensamin.net/legal/privacy-policy/\
|
||||
\n\"PrivacyPolicy=true\" indicates that you read and accepted the Privacy Policy. You can find our Privacy Policy at https://legal.tensamin.net/privacy-policy/\
|
||||
\nPrivacyPolicy={}\
|
||||
\n\"ToS=true\" indicates that you read and accepted the Terms of Service. You can find our Terms of Service at https://docs.tensamin.net/legal/terms-of-service/\
|
||||
\n\"ToS=true\" indicates that you read and accepted the Terms of Service. You can find our Terms of Service at https://legal.tensamin.net/terms-of-service/\
|
||||
\nToS={}\
|
||||
\nThis file reflects the current consent state used by the application.\
|
||||
\nIt may be regenerated or overwritten by the application.\
|
||||
|
|
@ -165,7 +171,7 @@ impl ConsentUiState {
|
|||
}
|
||||
}
|
||||
|
||||
fn run_consent_ui() -> UserChoice {
|
||||
async fn run_consent_ui() -> UserChoice {
|
||||
let mut terminal = ratatui::init();
|
||||
|
||||
let mut state = ConsentUiState {
|
||||
|
|
@ -176,6 +182,7 @@ fn run_consent_ui() -> UserChoice {
|
|||
};
|
||||
|
||||
let result = loop {
|
||||
let mut too_small = false;
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let mut needed_height = 5;
|
||||
|
|
@ -206,47 +213,52 @@ fn run_consent_ui() -> UserChoice {
|
|||
width: content_width,
|
||||
height: content_height,
|
||||
});
|
||||
let eula_text = if size.width < 76 {
|
||||
"EULA ¹ (https://docs.tensamin.net/legal/eula/)"
|
||||
let eula_text = if size.width < 70 {
|
||||
"EULA ¹ (https://legal.tensamin.net/eula/)"
|
||||
} else {
|
||||
"End User Licence Agreement ¹ (https://docs.tensamin.net/legal/eula/)"
|
||||
"End User Licence Agreement ¹ (https://legal.tensamin.net/eula/)"
|
||||
};
|
||||
let tos_text = if size.width < 76 {
|
||||
"ToS ² (https://docs.tensamin.net/legal/terms-of-service/)"
|
||||
let tos_text = if size.width < 72 {
|
||||
"ToS ² (https://legal.tensamin.net/terms-of-service/)"
|
||||
} else {
|
||||
"Terms of Service ² (https://docs.tensamin.net/legal/terms-of-service/)"
|
||||
"Terms of Service ² (https://legal.tensamin.net/terms-of-service/)"
|
||||
};
|
||||
let pp_text = if size.width < 76 {
|
||||
"PP ² (https://docs.tensamin.net/legal/privacy-policy/)"
|
||||
let pp_text = if size.width < 68 {
|
||||
"PP ² (https://legal.tensamin.net/privacy-policy/)"
|
||||
} else {
|
||||
"Privacy Policy ² (https://docs.tensamin.net/legal/privacy-policy/)"
|
||||
"Privacy Policy ² (https://legal.tensamin.net/privacy-policy/)"
|
||||
};
|
||||
|
||||
let (mut optional_lines, agree_lines): (Vec<i16>, Vec<&str>) =
|
||||
if size.width > 132 {
|
||||
(
|
||||
vec![7, 3, 4],
|
||||
vec![7, 3, 7, 4],
|
||||
vec![
|
||||
"",
|
||||
"By selecting Continue, you confirm that you have read and agree to the End User License Agreement and applicable Terms of Service.",
|
||||
"",
|
||||
"Tensamin services require acceptance of the Terms of Service and Privacy Policy.",
|
||||
"",
|
||||
"While having a document selected press O to view in this UI or press L to open as a link.",
|
||||
]
|
||||
)
|
||||
} else if size.width > 68 {
|
||||
(
|
||||
vec![8, 3, 4],
|
||||
vec![8, 3, 8, 4],
|
||||
vec![
|
||||
"",
|
||||
"By selecting Continue, you confirm that you have read and agree",
|
||||
"to the End User License Agreement and applicable Terms of Service.",
|
||||
"",
|
||||
"Tensamin services require acceptance of the ToS and Privacy Policy.",
|
||||
"",
|
||||
"While having a document selected press O to view in this UI or",
|
||||
"press L to open as a link.",
|
||||
]
|
||||
)
|
||||
} else {
|
||||
(
|
||||
vec![9, 3, 4],
|
||||
vec![9, 3, 10, 4],
|
||||
vec![
|
||||
"",
|
||||
"By selecting Continue, you confirm that you have",
|
||||
|
|
@ -254,7 +266,10 @@ fn run_consent_ui() -> UserChoice {
|
|||
"and applicable Terms of Service.",
|
||||
"",
|
||||
"Tensamin services require acceptance of the",
|
||||
"Terms of Service and Privacy Policy."
|
||||
"Terms of Service and Privacy Policy.",
|
||||
"",
|
||||
"While having a document selected press O to view",
|
||||
"in this UI or press L to open as a link.",
|
||||
]
|
||||
)
|
||||
};
|
||||
|
|
@ -281,33 +296,33 @@ fn run_consent_ui() -> UserChoice {
|
|||
|
||||
|
||||
|
||||
if size.width < 63 || size.height < needed_height as u16 {
|
||||
if size.width < 60 || size.height < needed_height as u16 {
|
||||
let width_style = if size.width > 76 {
|
||||
Style::default().fg(Color::Green)
|
||||
} else if size.width >= 63 {
|
||||
} else if size.width >= 60 {
|
||||
Style::default().fg(Color::Yellow)
|
||||
} else {
|
||||
Style::default().fg(Color::Red)
|
||||
};
|
||||
|
||||
let height_style = if size.height < needed_height as u16 {
|
||||
let height_style = if size.height < 12 {
|
||||
Style::default().fg(Color::Red)
|
||||
} else if size.height < 17 {
|
||||
Style::default().fg(Color::Yellow)
|
||||
} else {
|
||||
} else if size.height > 19 {
|
||||
Style::default().fg(Color::Green)
|
||||
} else {
|
||||
Style::default().fg(Color::Yellow)
|
||||
};
|
||||
|
||||
let warning_text = Text::from(vec![
|
||||
Line::from(vec![
|
||||
Span::raw("Width: "),
|
||||
Span::styled(format!("{}", size.width), width_style),
|
||||
Span::raw(" / 63"),
|
||||
Span::raw(" / 60"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::raw("Height: "),
|
||||
Span::styled(format!("{}", size.height), height_style),
|
||||
Span::raw(format!(" / {}", needed_height)),
|
||||
Span::raw(format!(" / 12")),
|
||||
]),
|
||||
]);
|
||||
|
||||
|
|
@ -319,6 +334,7 @@ fn run_consent_ui() -> UserChoice {
|
|||
.title("UI Too Small (Q to Quit)"),
|
||||
);
|
||||
|
||||
too_small = true;
|
||||
f.render_widget(warning, size);
|
||||
return;
|
||||
}
|
||||
|
|
@ -333,11 +349,47 @@ fn run_consent_ui() -> UserChoice {
|
|||
|
||||
if event::poll(Duration::from_millis(200)).unwrap() {
|
||||
if let Event::Key(key) = event::read().unwrap() {
|
||||
if too_small {
|
||||
if matches!(key.code, KeyCode::Char('q') | KeyCode::Char('Q')) {
|
||||
break UserChoice::Deny;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
match key.code {
|
||||
KeyCode::Esc => break UserChoice::Deny,
|
||||
KeyCode::Up => state.prev(),
|
||||
KeyCode::Down | KeyCode::Tab => state.next(),
|
||||
KeyCode::Char('q') | KeyCode::Char('Q') => break UserChoice::Deny,
|
||||
KeyCode::Char('o') | KeyCode::Char('O') => {
|
||||
let terms_type = match state.focus {
|
||||
Focus::Eula => Type::EULA,
|
||||
Focus::Tos => Type::TOS,
|
||||
Focus::Pp => Type::PP,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if let Some(eula) = get_terms(terms_type.clone()).await {
|
||||
terminal = FileViewer::new(terms_type.to_string(), &eula)
|
||||
.force_popup(terminal);
|
||||
} else {
|
||||
terminal = FileViewer::new(
|
||||
terms_type.to_string(),
|
||||
"### A loading error occured",
|
||||
)
|
||||
.force_popup(terminal);
|
||||
}
|
||||
}
|
||||
KeyCode::Char('l') | KeyCode::Char('L') => {
|
||||
let terms_type = match state.focus {
|
||||
Focus::Eula => Type::EULA,
|
||||
Focus::Tos => Type::TOS,
|
||||
Focus::Pp => Type::PP,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let _ = open::that(get_link(terms_type));
|
||||
}
|
||||
KeyCode::Char(' ') => match state.focus {
|
||||
Focus::Eula => {
|
||||
state.eula = !state.eula;
|
||||
39
src/terms/terms_getter.rs
Normal file
39
src/terms/terms_getter.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#[derive(Clone)]
|
||||
pub enum Type {
|
||||
EULA,
|
||||
TOS,
|
||||
PP,
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn to_str(&self) -> &str {
|
||||
match self {
|
||||
Self::EULA => "eula",
|
||||
Self::TOS => "tos",
|
||||
Self::PP => "pp",
|
||||
}
|
||||
}
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
Self::EULA => "End User License Agreement".to_string(),
|
||||
Self::TOS => "Terms of Service".to_string(),
|
||||
Self::PP => "Privacy Policy".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_link(terms_type: Type) -> String {
|
||||
format!("https://legal.tensamin.net/{}/", terms_type.to_str())
|
||||
}
|
||||
pub async fn get_terms(terms_type: Type) -> Option<String> {
|
||||
let body = reqwest::get(format!(
|
||||
"https://legal.tensamin.net/api/text/{}/",
|
||||
terms_type.to_str()
|
||||
))
|
||||
.await
|
||||
.ok()?
|
||||
.text()
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
Some(body)
|
||||
}
|
||||
Loading…
Reference in a new issue