This commit is contained in:
Alex Emmet 2026-04-04 21:43:54 +02:00
commit 71ac8d97fa
12 changed files with 445 additions and 445 deletions

View file

@ -1,175 +0,0 @@
use ratatui::{
layout::{Alignment, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Paragraph},
};
use crate::terms::focus::Focus;
#[allow(mismatched_lifetime_syntaxes)]
pub fn checkbox(label: &str, checked: bool, active: bool, allowed: bool) -> Line {
let box_char = if checked { "[x]" } else { "[ ]" };
let (box_style, text_style) = if active {
if allowed {
(
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
)
} else {
(
Style::default().fg(Color::Gray),
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
)
}
} else {
(Style::default(), Style::default())
};
Line::from(vec![
Span::styled(box_char, box_style),
Span::raw(" "),
Span::styled(label, text_style),
])
}
pub fn draw_button(f: &mut ratatui::Frame, area: Rect, label: &str, style: Style) {
let p = Paragraph::new(Span::styled(label, style))
.alignment(Alignment::Center)
.block(Block::default().borders(Borders::ALL));
f.render_widget(p, area);
}
pub fn draw_buttons(
f: &mut ratatui::Frame,
area: Rect,
current_focus: Focus,
state: (bool, bool),
update_needed: bool,
downgrade_scenario: bool,
tos_or_privacy: bool,
) {
let cancel_text = if update_needed {
"[Q] Quit"
} else {
"[Q] Not now"
};
let continue_text = if downgrade_scenario {
"Downgrade"
} else {
"Continue"
};
let mut buttons = vec![
(cancel_text, Focus::Cancel),
(continue_text, Focus::Continue),
];
if tos_or_privacy {
buttons.push(("Continue with Tensamin Services", Focus::ContinueAll));
}
let padding = 2;
let min_widths: Vec<u16> = buttons
.iter()
.map(|(label, _)| label.len() as u16 + padding)
.collect();
let widths = compute_widths(area.width, &min_widths);
let mut x = area.x;
for ((label, focus), width) in buttons.iter().zip(widths) {
let chunk = Rect {
x,
y: area.y,
width,
height: area.height,
};
x += width;
let is_focused = current_focus == *focus;
let style = match focus {
Focus::Cancel => {
if is_focused {
Style::default()
.fg(Color::Black)
.bg(Color::Red)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::Red)
}
}
Focus::Continue => {
if is_focused && state.0 {
Style::default()
.fg(Color::Black)
.bg(Color::Green)
.add_modifier(Modifier::BOLD)
} else if state.0 {
Style::default().fg(Color::Green)
} else {
Style::default().fg(Color::DarkGray)
}
}
Focus::ContinueAll => {
if is_focused && state.1 {
Style::default()
.fg(Color::Black)
.bg(Color::Green)
.add_modifier(Modifier::BOLD)
} else if state.1 {
Style::default().fg(Color::Green)
} else {
Style::default().fg(Color::DarkGray)
}
}
_ => Style::default().fg(Color::DarkGray),
};
draw_button(f, chunk, label, style);
}
}
pub fn compute_widths(area_width: u16, min_widths: &[u16]) -> Vec<u16> {
let mut widths = vec![0; min_widths.len()];
let mut remaining: Vec<usize> = (0..min_widths.len()).collect();
let mut remaining_width = area_width;
while !remaining.is_empty() {
let count = remaining.len() as u16;
let equal = remaining_width / count;
let mut clamped = Vec::new();
for &i in &remaining {
if min_widths[i] > equal {
widths[i] = min_widths[i];
remaining_width -= min_widths[i];
clamped.push(i);
}
}
if clamped.is_empty() {
let mut remainder = remaining_width % count;
for &i in &remaining {
widths[i] = equal
+ if remainder > 0 {
remainder -= 1;
1
} else {
0
};
}
break;
}
remaining.retain(|i| !clamped.contains(i));
}
widths
}

View file

@ -1,73 +0,0 @@
use json::{JsonValue, object::Object};
use crate::{terms::terms_getter::Type, util::file_util::load_file};
#[derive(Clone, Debug, PartialEq, Eq)]
#[allow(unused)]
pub struct Doc {
version: String,
hash: String,
pub doc_type: Type,
timestamp: u64,
}
#[allow(dead_code)]
impl Doc {
pub fn new(version: String, hash: String, doc_type: Type, timestamp: u64) -> Doc {
Doc {
version,
hash,
doc_type,
timestamp,
}
}
pub fn equals_some(&self, other: &Option<Self>) -> bool {
if let Some(other) = other {
self.get_version() == other.get_version() && self.get_hash() == other.get_hash()
} else {
false
}
}
pub fn equals(&self, other: &Self) -> bool {
self.get_version() == other.get_version() && self.get_hash() == other.get_hash()
}
pub fn get_version(&self) -> String {
self.version.clone()
}
pub fn get_hash(&self) -> String {
self.hash.clone()
}
pub fn get_time(&self) -> u64 {
self.timestamp.clone()
}
pub fn get_content(&self) -> String {
load_file(
format!("docs/{}/", self.doc_type.to_str()).as_str(),
format!("{}.md", self.version).as_str(),
)
}
pub fn to_json(&self) -> JsonValue {
let mut json = JsonValue::new_object();
let _ = json.insert("version", self.version.clone());
let _ = json.insert("hash", self.hash.clone());
let _ = json.insert("unix", self.timestamp.clone());
json
}
pub fn from_json(doc_type: Type, json: Object) -> Option<Self> {
let hash = json.get("hash")?.as_str()?.to_string();
let version = json.get("version")?.as_str()?.to_string();
let timestamp = json.get("unix")?.as_u64()?;
Some(Doc {
version,
hash,
doc_type,
timestamp,
})
}
}

View file

@ -1,25 +0,0 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Focus {
Eula,
Tos,
Pp,
Cancel,
Continue,
ContinueAll,
}
impl Focus {
pub fn next(&mut self, order: &[Focus]) {
if let Some(pos) = order.iter().position(|&f| f == *self) {
let next_pos = (pos + 1) % order.len();
*self = order[next_pos];
}
}
pub fn prev(&mut self, order: &[Focus]) {
if let Some(pos) = order.iter().position(|&f| f == *self) {
let prev_pos = if pos == 0 { order.len() - 1 } else { pos - 1 };
*self = order[prev_pos];
}
}
}

View file

@ -1,4 +0,0 @@
pub mod buttons;
pub mod doc;
pub mod focus;
pub mod terms_getter;

View file

@ -1,105 +0,0 @@
use json::JsonValue::Object;
use crate::terms::doc::Doc;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Type {
EULA,
TOS,
PP,
}
impl Type {
pub fn to_str(&self) -> &str {
match self {
Self::EULA => "eula",
Self::TOS => "tos",
Self::PP => "privacy",
}
}
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 fn get_newest_link(terms_type: Type) -> String {
format!("https://legal.tensamin.net/{}/newest/", terms_type.to_str())
}
pub async fn get_current_docs() -> Option<(Doc, Doc, Doc)> {
let body = reqwest::get("https://legal.tensamin.net/api/current/")
.await
.ok()?
.text()
.await
.ok()?;
let json = json::parse(&body).ok()?;
if let Object(eula) = &json["eula"] {
if let Object(tos) = &json["tos"] {
if let Object(pp) = &json["pp"] {
Some((
Doc::from_json(Type::EULA, eula.clone())?,
Doc::from_json(Type::TOS, tos.clone())?,
Doc::from_json(Type::PP, pp.clone())?,
))
} else {
None
}
} else {
None
}
} else {
None
}
}
pub async fn get_newest_docs() -> Option<(Doc, Doc, Doc)> {
let body = reqwest::get("https://legal.tensamin.net/api/newest/")
.await
.ok()?
.text()
.await
.ok()?;
let json = json::parse(&body).ok()?;
if let Object(eula) = &json["eula"] {
if let Object(tos) = &json["tos"] {
if let Object(pp) = &json["pp"] {
Some((
Doc::from_json(Type::EULA, eula.clone())?,
Doc::from_json(Type::TOS, tos.clone())?,
Doc::from_json(Type::PP, pp.clone())?,
))
} else {
None
}
} else {
None
}
} else {
None
}
}
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)
}