diff --git a/src/data_provider/words.rs b/src/data_provider/words.rs index b1f25a8..75f049f 100644 --- a/src/data_provider/words.rs +++ b/src/data_provider/words.rs @@ -1,9 +1,9 @@ -use litemap::LiteMap; use crate::data::entities::WordData; use crate::data::repositories::{DataRepository, DataRepositoryInner}; +use crate::data::Id; use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet}; +use litemap::LiteMap; use rusqlite::{Error, Params, Row}; -use crate::data::{Id, SmallId}; pub type WordRepository = DataRepository; pub type WordRepositoryInner = DataRepositoryInner; @@ -61,7 +61,7 @@ impl TableAccessQuerySet for WordQuerySet { } fn additional_to_string(map: &LiteMap) -> String { - let mut parts = vec![]; + let mut parts = Vec::with_capacity(map.len() * 2); for (id, value) in map { parts.push(id.to_string()); parts.push(value.clone()); @@ -69,7 +69,20 @@ fn additional_to_string(map: &LiteMap) -> String { parts.join("") } +fn additional_from_string_temp(data: String) -> LiteMap { + + let pre_map : LiteMap = serde_json::from_str(data.as_str()).unwrap(); + LiteMap::from(pre_map.iter().map(|(k, v)| (match k.as_str() { + "reading" => Id(1), + "description" => Id(2), + "context" => Id(3), + &_ => Id(0) }, v.to_string())).collect()) +} + fn additional_from_string(data: String) -> LiteMap { + if data.is_empty() { + return LiteMap::new() + } let parts = data.split('').collect::>(); let chunks = parts.chunks(2).collect::>(); let mut map = LiteMap::with_capacity(parts.len()); diff --git a/src/pages/import.rs b/src/pages/import.rs index 6633340..7284b2c 100644 --- a/src/pages/import.rs +++ b/src/pages/import.rs @@ -1,16 +1,17 @@ use crate::app::AppState; -use crate::data::entities::WordData; +use crate::data::Id; use crate::data::entities::WordGroup; +use crate::data::entities::{CustomField, WordData}; use crate::data::repositories::{BatchedRepository, Repository}; use crate::data_provider::import::{ImportData, ImportGroup, get_words_of_group, load_groups}; use crate::fs::*; -use crate::jl_button; use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::notification::NotificationData::OpenFileError; use crate::notification::{MaybeError, NotificationData}; use crate::pages::import::ImportMessage::*; use crate::push_error; use crate::styling::*; +use crate::{jl_button, push_error_task}; use iced::widget::button::danger; use iced::widget::container::success; use iced::widget::{ @@ -25,6 +26,7 @@ use log::info; use parking_lot::Mutex; use rfd::AsyncFileDialog; use rusqlite::Connection; +use std::collections::HashMap; use std::fs::{File, OpenOptions}; use std::io::BufReader; use std::path::PathBuf; @@ -428,7 +430,19 @@ impl ImportState { let separator = self.separator.clone(); let skip_empty = self.skip_empty; let group = self.import_data.as_ref().unwrap().0[self.selected_index].clone(); + let map_indices = Self::get_mapping_indices(&group); let progress = self.progress.clone().unwrap(); + if let Err(err) = Self::ensure_fields(&state, &map_indices) { + return push_error_task!(err); + } + let fields: HashMap = HashMap::from_iter( + state + .custom_fields + .read() + .iter() + .map(|f| (f.identifier.clone(), f.id)), + ); + info!("{}", group.name); Task::perform( async move { @@ -439,7 +453,6 @@ impl ImportState { let import_list = get_words_of_group(&connection, group.id)?; let mut words_list = Vec::with_capacity(import_list.len()); - let map_indices = Self::get_mapping_indices(&group); let group_handle; { let group_entity = WordGroup { @@ -474,7 +487,7 @@ impl ImportState { word.tags = collected_string; } &_ => { - word.additional.insert((*dest).clone(), collected_string); + word.additional.insert(fields[dest], collected_string); } } } @@ -554,4 +567,29 @@ impl ImportState { |_| RootMessage::Import(NextProgress), ) } + + fn ensure_fields( + state: &AppState, + import_group: &Vec<(String, Vec)>, + ) -> Result<(), NotificationData> { + let mut fields_repo = state.custom_fields.write(); + let mut current_fields: Vec = fields_repo + .iter() + .map(|field| field.identifier.clone()) + .collect(); + current_fields.append(&mut vec!["key".to_string(), "value".to_string(), "tags".to_string()]); + for (field, _) in import_group { + if current_fields.contains(field) { + continue; + } + + fields_repo.create_now(CustomField { + id: Default::default(), + name: field.clone(), + identifier: field.clone(), + })?; + } + + Ok(()) + } } diff --git a/src/pages/repetition.rs b/src/pages/repetition.rs index fea90c2..369c41c 100644 --- a/src/pages/repetition.rs +++ b/src/pages/repetition.rs @@ -262,19 +262,19 @@ impl RepetitionState { } fn draw_reading(&self, word: &WordData) -> Element<'_, RepetitionMessage> { - match word.additional.get("reading") { + match word.additional.get(&Id(1)) { None => space().into(), Some(reading) => text!("{}", reading).size(24).into(), } } fn draw_context(&self, word: &WordData) -> Element<'_, RepetitionMessage> { - match word.additional.get("context") { + match word.additional.get(&Id(3)) { None => space().into(), Some(context) => text!("{}", context).size(24).into(), } } fn draw_description(&self, word: &WordData) -> Element<'_, RepetitionMessage> { - match word.additional.get("description") { + match word.additional.get(&Id(2)) { None => space().into(), Some(context) => text!("{}", context).size(24).into(), } diff --git a/src/pages/word.rs b/src/pages/word.rs index 0f7c699..3793721 100644 --- a/src/pages/word.rs +++ b/src/pages/word.rs @@ -1,5 +1,6 @@ use crate::app::AppState; -use crate::data::entities::{EntityHandle, WordData}; +use crate::data::Id; +use crate::data::entities::{CustomField, EntityHandle, WordData}; use crate::data::repositories::Repository; use crate::pages::word::WordMessage::*; use crate::styling::*; @@ -7,15 +8,17 @@ use iced::widget::button::danger; use iced::widget::space::horizontal; use iced::widget::{button, column, row, rule, scrollable, text, text_input}; use iced::{Element, Fill}; +use iced_core::Padding; pub struct WordEditor { state: AppState, handle: EntityHandle, on_delete: Option, on_edit: Option, + fields: Vec, } -impl WordEditor { +impl WordEditor { pub(crate) fn update(&mut self, message: WordMessage) -> Option { let dictionary = &mut self.state.dictionary.write(); let word = dictionary.get_mut(&mut self.handle).unwrap(); @@ -37,7 +40,7 @@ impl WordEditor { word.additional.insert(key, "".to_string()); } RemoveAdditional(key) => { - word.additional.remove(key.as_str()); + word.additional.remove(&key); } } self.on_edit.take() @@ -45,36 +48,25 @@ impl WordEditor { pub(crate) fn view(&self) -> Element<'_, WordMessage> { let mut fast_add = row![]; - let word = self.state.dictionary.read().get_static(self.handle).unwrap(); - if !word.additional.contains_key("reading") { - fast_add = fast_add.push( - button("Чтение") - .style(button::text) - .on_press(AddAdditional("reading".to_string())), - ); - } - if !word.additional.contains_key("description") { - fast_add = fast_add.push( - button("Описание") - .style(button::text) - .on_press(AddAdditional("description".to_string())), - ); - } - - if !word.additional.contains_key("context") { - fast_add = fast_add.push( - button("В контексте") - .style(button::text) - .on_press(AddAdditional("context".to_string())), - ); + let word = self + .state + .dictionary + .read() + .get_static(self.handle) + .unwrap(); + for field in &self.fields { + if !word.additional.contains_key(&field.id) { + fast_add = fast_add.push( + button(field.name.as_str()) + .style(button::text) + .on_press(AddAdditional(field.id)), + ); + } } let mut col = iced::widget::column![ - column![ - text!("Ключ"), - text_input("key", &word.key).on_input(SetKey), - ] - .spacing(QUARTER_SPACING), + column![text!("Ключ"), text_input("key", &word.key).on_input(SetKey),] + .spacing(QUARTER_SPACING), column![ text!("Значение"), text_input("value", &word.value).on_input(SetValue), @@ -86,33 +78,46 @@ impl WordEditor { ] .spacing(QUARTER_SPACING), rule::horizontal(2), - scrollable(fast_add), + scrollable(fast_add.padding(Padding{ + + + top: 0.0 , right: 0.0 , bottom: 4.0 , left: 0.0 , })).horizontal(), ]; for more in &word.additional { col = col.push(self.get_view_for_more(more)); } - column![ - col.spacing(DEFAULT_SPACING / 2.0).width(Fill).height(Fill), - row![ - horizontal().width(Fill), - button("Удалить").style(danger).on_press(Delete), - ] - .spacing(DEFAULT_SPACING) + column![ + col.spacing(DEFAULT_SPACING / 2.0).width(Fill).height(Fill), + row![ + horizontal().width(Fill), + button("Удалить").style(danger).on_press(Delete), ] - .spacing(DEFAULT_SPACING).into() - - - + .spacing(DEFAULT_SPACING) + ] + .spacing(DEFAULT_SPACING) + .into() } } impl WordEditor { pub fn new(handle: EntityHandle, state: AppState) -> Self { - Self { state, handle, on_delete: None, on_edit: None } + let fields = state + .custom_fields + .read() + .iter() + .cloned() + .collect::>(); + Self { + state, + handle, + on_delete: None, + on_edit: None, + fields, + } } - + pub fn on_delete(mut self, message: Message) -> Self { self.on_delete = Some(message); self @@ -124,38 +129,22 @@ impl WordEditor { } impl WordEditor { - fn get_view_for_more(&self, value: (&String, &String)) -> Element<'_, WordMessage> { - match value.0.as_str() { - "reading" => self.reading_field(value.1), - "description" => self.description_field(value.1), - "context" => self.context_field(value.1), - _ => self.additional_field(value.1, value.0.clone(), value.0.clone()), + fn get_view_for_more(&self, value: (&Id, &String)) -> Element<'_, WordMessage> { + let field = self.fields.iter().find(|f| f.id == *value.0).unwrap(); + match value.0 { + _ => self.additional_field(value.1, field.name.as_str(), value.0.clone()), } } - fn reading_field(&self, value: &str) -> Element<'_, WordMessage> { - self.additional_field(value, "Чтение слова".to_string(), "reading".to_string()) - } - - fn description_field(&self, value: &str) -> Element<'_, WordMessage> { - self.additional_field(value, "Описание".to_string(), "description".to_string()) - } - - fn context_field(&self, value: &str) -> Element<'_, WordMessage> { - self.additional_field(value, "В контексте".to_string(), "context".to_string()) - } - fn additional_field(&self, value: &str, name: String, id: String) -> Element<'_, WordMessage> { + fn additional_field(&self, value: &str, name: &str, id: Id) -> Element<'_, WordMessage> { column![ text!("{}", name), row![ - text_input(id.clone().as_str(), value) - .on_input({ - let value = id.clone(); - move |string| SetAdditional(value.clone(), string) - }) + text_input(name, value) + .on_input(move |string| SetAdditional(id, string)) .width(Fill), button("-") - .on_press(RemoveAdditional(id.clone())) + .on_press(RemoveAdditional(id)) .style(danger), ] .spacing(QUARTER_SPACING), @@ -171,7 +160,7 @@ pub enum WordMessage { SetTags(String), SetKey(String), SetValue(String), - AddAdditional(String), - RemoveAdditional(String), - SetAdditional(String, String), + AddAdditional(Id), + RemoveAdditional(Id), + SetAdditional(Id, String), }