WIP: basic wordlist rendering

This commit is contained in:
2026-08-02 18:04:04 +02:00
parent 96daa332a3
commit 3e5d3fa555
2 changed files with 99 additions and 71 deletions

View File

@@ -47,13 +47,13 @@ impl FileDatabase {
} }
pub trait WordSource { pub trait WordSource {
fn find_by_word(&self, word: &str) -> Result<Vec<Word>, ()>; fn get_all_words(&self) -> Vec<Word>;
fn find_by_word_and_lang(&self, word: &str, lang: &str) -> Result<Vec<Word>, ()>; fn find_by_word(&self, word: &str) -> Vec<Word>;
fn find_by_lang(&self, lang: &str) -> Vec<Word>;
fn find_by_word_and_lang(&self, word: &str, lang: &str) -> Vec<Word>;
fn insert_word(&self, word: Word) -> Result<(), ()>; fn insert_word(&self, word: Word) -> Result<(), ()>;
fn remove_word(&self, word_key: &str) -> Result<(), ()>; fn remove_word(&self, word_key: &str) -> Result<(), ()>;
fn edit_word(&self, word: Word) -> Result<Word, ()>; fn edit_word(&self, word: Word) -> Result<Word, ()>;
fn get_all_words(&self) -> Vec<Word>;
fn get_all_words_by_lang(&self, lang: &str) -> Vec<Word>;
fn normalize_word_key(word_key: &str) -> String; fn normalize_word_key(word_key: &str) -> String;
fn normalize_lang_key(word_key: &str) -> String; fn normalize_lang_key(word_key: &str) -> String;
} }
@@ -67,7 +67,31 @@ fn fuzzy_compare(a: &str, b: &str) -> bool {
} }
impl WordSource for FileDatabase { impl WordSource for FileDatabase {
fn find_by_word(&self, word: &str) -> Result<Vec<Word>, ()> { fn get_all_words(&self) -> Vec<Word> {
let statement = self
.connection
.prepare("select key, description, lang from word");
let mut word_list: Vec<Word> = vec![];
// TODO: make this pretty with functional programming
if let Ok(mut query) = statement {
let mut rows = query.query([]).unwrap();
while let Some(row) = rows.next().unwrap() {
let found_word: String = row.get(0).unwrap();
let found_word_description: String = row.get(1).unwrap();
let found_word_lang: String = row.get(2).unwrap();
word_list.push(Word {
key: found_word,
description: found_word_description,
lang: found_word_lang,
});
}
}
word_list
}
fn find_by_word(&self, word: &str) -> Vec<Word> {
let statement = self let statement = self
.connection .connection
.prepare("SELECT key, description, lang FROM word"); .prepare("SELECT key, description, lang FROM word");
@@ -87,13 +111,35 @@ impl WordSource for FileDatabase {
}); });
} }
} }
} else {
return Err(());
} }
Ok(word_list) word_list
} }
fn find_by_word_and_lang(&self, word: &str, lang: &str) -> Result<Vec<Word>, ()> { fn find_by_lang(&self, lang: &str) -> Vec<Word> {
let statement = self
.connection
.prepare("select key, description, lang from word where lang = ?1");
let mut word_list: Vec<Word> = vec![];
// TODO: make this pretty with functional programming
if let Ok(mut query) = statement {
let mut rows = query.query([lang]).unwrap();
while let Some(row) = rows.next().unwrap() {
let found_word: String = row.get(0).unwrap();
let found_word_description: String = row.get(1).unwrap();
let found_word_lang: String = row.get(2).unwrap();
word_list.push(Word {
key: found_word,
description: found_word_description,
lang: found_word_lang,
});
}
}
word_list
}
fn find_by_word_and_lang(&self, word: &str, lang: &str) -> Vec<Word> {
let statement = self let statement = self
.connection .connection
.prepare("SELECT key, description, lang FROM word where lang = ?1"); .prepare("SELECT key, description, lang FROM word where lang = ?1");
@@ -113,10 +159,8 @@ impl WordSource for FileDatabase {
}); });
} }
} }
} else {
return Err(());
} }
Ok(word_list) word_list
} }
fn insert_word(&self, word: Word) -> Result<(), ()> { fn insert_word(&self, word: Word) -> Result<(), ()> {
@@ -170,54 +214,6 @@ impl WordSource for FileDatabase {
} }
} }
fn get_all_words(&self) -> Vec<Word> {
let statement = self
.connection
.prepare("select key, description, lang from word");
let mut word_list: Vec<Word> = vec![];
// TODO: make this pretty with functional programming
if let Ok(mut query) = statement {
let mut rows = query.query([]).unwrap();
while let Some(row) = rows.next().unwrap() {
let found_word: String = row.get(0).unwrap();
let found_word_description: String = row.get(1).unwrap();
let found_word_lang: String = row.get(2).unwrap();
word_list.push(Word {
key: found_word,
description: found_word_description,
lang: found_word_lang,
});
}
}
word_list
}
fn get_all_words_by_lang(&self, lang: &str) -> Vec<Word> {
let statement = self
.connection
.prepare("select key, description, lang from word where lang = ?1");
let mut word_list: Vec<Word> = vec![];
// TODO: make this pretty with functional programming
if let Ok(mut query) = statement {
let mut rows = query.query([lang]).unwrap();
while let Some(row) = rows.next().unwrap() {
let found_word: String = row.get(0).unwrap();
let found_word_description: String = row.get(1).unwrap();
let found_word_lang: String = row.get(2).unwrap();
word_list.push(Word {
key: found_word,
description: found_word_description,
lang: found_word_lang,
});
}
}
word_list
}
fn normalize_word_key(word_key: &str) -> String { fn normalize_word_key(word_key: &str) -> String {
// TODO: real normalization, not just lowercase, but keep in mind it must be kept as the // TODO: real normalization, not just lowercase, but keep in mind it must be kept as the
// final and real word -> Maybe full refactor to include a word id // final and real word -> Maybe full refactor to include a word id

View File

@@ -1,4 +1,4 @@
use crate::modules::source::FileDatabase; use crate::modules::source::{FileDatabase, WordSource};
use crossterm::event::{ use crossterm::event::{
self, self,
Event::{self, Key}, Event::{self, Key},
@@ -10,7 +10,7 @@ use ratatui::{
text::Text, text::Text,
widgets::{Block, Borders, Padding, Paragraph}, widgets::{Block, Borders, Padding, Paragraph},
}; };
use std::io; use std::{io, ops::Index};
#[derive(PartialEq)] #[derive(PartialEq)]
pub enum UiState { pub enum UiState {
@@ -25,7 +25,7 @@ pub enum UiState {
pub struct App { pub struct App {
state: UiState, state: UiState,
file_database: FileDatabase, file_database: FileDatabase,
scroll_position: u32, scroll_position: usize,
word_filter: Option<String>, word_filter: Option<String>,
lang_filter: Option<String>, lang_filter: Option<String>,
exit: bool, exit: bool,
@@ -88,7 +88,8 @@ impl App {
} }
}; };
frame.render_widget( frame.render_widget(
Paragraph::new(word_filter).block(Block::new().title("Word").borders(Borders::ALL)), Paragraph::new(word_filter.clone())
.block(Block::new().title("Word").borders(Borders::ALL)),
filters_layout[0], filters_layout[0],
); );
@@ -104,15 +105,31 @@ impl App {
filters_layout[1], filters_layout[1],
); );
// FIXME: this is wrong, the list must reside in memory and only be updated when required,
// this makes a new call every frame, insane
let word_list = {
if let Some(w_filter) = &self.word_filter
&& let Some(l_filter) = &self.lang_filter
{
self.file_database
.find_by_word_and_lang(&word_filter, &l_filter)
} else if let Some(l_filter) = &self.lang_filter {
self.file_database.find_by_lang(&l_filter)
} else {
self.file_database.get_all_words()
}
};
frame.render_widget( frame.render_widget(
Paragraph::new("Word list").block(Block::new().borders(Borders::ALL).title("Entries")), Paragraph::new("Word list").block(Block::new().borders(Borders::ALL).title("Entries")),
words_layout[0], words_layout[0],
); );
let selected_word = word_list.into_iter().nth(self.scroll_position);
if let Some(w) = selected_word {
frame.render_widget( frame.render_widget(
Paragraph::new("Selected word description").block(Block::new().borders(Borders::ALL)), Paragraph::new(w.description).block(Block::new().borders(Borders::ALL)),
words_layout[1], words_layout[1],
); );
}
let help_text = Text::raw("(f)ilter | lan(g) | (d)elete | (e)dit | (a)dd | (q)uit ") let help_text = Text::raw("(f)ilter | lan(g) | (d)elete | (e)dit | (a)dd | (q)uit ")
.style(Style::new().fg(Color::DarkGray)); .style(Style::new().fg(Color::DarkGray));
@@ -137,10 +154,20 @@ impl App {
self.word_filter = Some(c.to_string()); self.word_filter = Some(c.to_string());
} }
} }
UiState::FILTERLANG => {
if let Some(mut prev_lang) = self.lang_filter.clone() {
prev_lang.push(c);
self.lang_filter = Some(prev_lang);
} else {
self.lang_filter = Some(c.to_string());
}
}
_ => { _ => {
// Quit
if c == 'q' || c == 'x' { if c == 'q' || c == 'x' {
return Err(io::Error::last_os_error()); // FIXME: this is not right return Err(io::Error::last_os_error()); // FIXME: this is not right
} }
// State change
if c == 'f' || c == '/' { if c == 'f' || c == '/' {
self.state = UiState::FILTERWORD; self.state = UiState::FILTERWORD;
} }
@@ -156,8 +183,13 @@ impl App {
if c == 'a' { if c == 'a' {
self.state = UiState::ADD; self.state = UiState::ADD;
} }
// TODO: remove, just for testing // Movement
self.lang_filter = Some(format!("{0} >> {1}", c, key.code)); if c == 'j' {
self.scroll_position -= 1;
}
if c == 'k' {
self.scroll_position += 1;
}
} }
} }
} }