From 6e673fa7d2df8c9028ebe5d61576bcfd44c691fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Sun, 9 Feb 2025 22:38:14 +0100 Subject: [PATCH] Fixed various specialized editors in icbsetup. --- crates/icboard/src/bbs/mod.rs | 2 +- crates/icbsetup/src/create/mod.rs | 2 +- crates/icbsetup/src/editors/areas.rs | 308 ++++++++++++++++ crates/icbsetup/src/editors/bullettins.rs | 11 +- crates/icbsetup/src/editors/dirs.rs | 342 +++++++++--------- crates/icbsetup/src/editors/door.rs | 235 ++++++------ crates/icbsetup/src/editors/languages.rs | 246 +++++++------ crates/icbsetup/src/editors/messages.rs | 266 -------------- crates/icbsetup/src/editors/mod.rs | 2 +- crates/icbsetup/src/editors/sec_editor.rs | 299 ++++++++------- crates/icbsetup/src/editors/surveys.rs | 157 ++++---- .../general/conferences/conference_editor.rs | 12 +- crates/icbsetup/src/tabs/general/mod.rs | 2 +- .../src/icy_board/doors/mod.rs | 10 +- .../src/icy_board/file_directory.rs | 21 +- .../src/icy_board/language.rs | 24 +- .../src/icy_board/message_area.rs | 4 +- .../src/icy_board/sec_levels.rs | 18 +- .../src/icy_board/security_expr.rs | 2 +- .../user_commands/pcb/lang_set_language.rs | 6 +- .../icy_board_engine/src/icy_board/surveys.rs | 4 +- .../vm/statements/predefined_procedures.rs | 2 +- .../icy_board_tui/i18n/en/icy_board_tui.ftl | 224 ++++++++++++ crates/icy_board_tui/src/config_menu.rs | 11 + 24 files changed, 1315 insertions(+), 895 deletions(-) create mode 100644 crates/icbsetup/src/editors/areas.rs delete mode 100644 crates/icbsetup/src/editors/messages.rs diff --git a/crates/icboard/src/bbs/mod.rs b/crates/icboard/src/bbs/mod.rs index 6e5098db..1872cc04 100644 --- a/crates/icboard/src/bbs/mod.rs +++ b/crates/icboard/src/bbs/mod.rs @@ -62,7 +62,7 @@ pub async fn await_telnet_connections(con: Telnet, board: Arc>, _bbs: Arc>) -> Res<()> { let addr = if ssh.address.is_empty() { format!("0.0.0.0:{}", ssh.port) diff --git a/crates/icbsetup/src/create/mod.rs b/crates/icbsetup/src/create/mod.rs index f41120d2..aaaaa563 100644 --- a/crates/icbsetup/src/create/mod.rs +++ b/crates/icbsetup/src/create/mod.rs @@ -217,7 +217,7 @@ impl IcyBoardCreator { self.logger.start_action("Write default language definition file".to_string()); config.paths.language_file = PathBuf::from("config/languages.toml"); let mut lang = SupportedLanguages::default(); - lang.languages.push(Language { + lang.push(Language { description: "English".to_string(), yes_char: 'Y', no_char: 'N', diff --git a/crates/icbsetup/src/editors/areas.rs b/crates/icbsetup/src/editors/areas.rs new file mode 100644 index 00000000..819ec07e --- /dev/null +++ b/crates/icbsetup/src/editors/areas.rs @@ -0,0 +1,308 @@ +use std::{ + collections::HashMap, + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use crossterm::event::{KeyCode, KeyEvent}; +use icy_board_engine::{ + icy_board::{ + message_area::{AreaList, MessageArea}, + security_expr::SecurityExpression, + IcyBoard, IcyBoardSerializer, + }, + Res, +}; +use icy_board_tui::{ + config_menu::{ConfigEntry, ConfigMenu, ConfigMenuState, ListItem, ListValue}, + get_text, get_text_args, + insert_table::InsertTable, + save_changes_dialog::SaveChangesDialog, + tab_page::{Page, PageMessage}, + theme::get_tui_theme, + BORDER_SET, +}; +use ratatui::{ + layout::{Alignment, Margin, Rect}, + text::{Line, Span}, + widgets::{block::Title, Block, BorderType, Borders, Clear, Padding, ScrollbarState, TableState, Widget}, + Frame, +}; + +pub struct MessageAreasEditor<'a> { + path: std::path::PathBuf, + + insert_table: InsertTable<'a>, + area_list_orig: AreaList, + area_list: Arc>, + + edit_config_state: ConfigMenuState, + edit_config: Option>)>>, + save_dialog: Option, +} + +impl<'a> MessageAreasEditor<'a> { + pub(crate) fn new(path: &std::path::PathBuf) -> Res { + let area_list_orig = if path.exists() { AreaList::load(&path)? } else { AreaList::default() }; + let area_list = Arc::new(Mutex::new(area_list_orig.clone())); + let scroll_state = ScrollbarState::default().content_length(area_list_orig.len()); + let content_length = area_list_orig.len(); + let dl2 = area_list.clone(); + let insert_table = InsertTable { + scroll_state, + table_state: TableState::default().with_selected(0), + headers: vec![ + "".to_string(), + format!("{:<20}", get_text("dirs_table_name_header")), + get_text("dirs_table_path_header"), + ], + get_content: Box::new(move |_table, i, j| { + if *i >= dl2.lock().unwrap().len() { + return Line::from("".to_string()); + } + match j { + 0 => Line::from(format!("{})", *i + 1)), + 1 => Line::from(format!("{}", dl2.lock().unwrap()[*i].name)), + 2 => Line::from(format!("{}", dl2.lock().unwrap()[*i].filename.display())), + _ => Line::from("".to_string()), + } + }), + content_length, + }; + + Ok(Self { + path: path.clone(), + insert_table, + area_list, + area_list_orig, + edit_config: None, + edit_config_state: ConfigMenuState::default(), + save_dialog: None, + }) + } + + fn display_insert_table(&mut self, frame: &mut Frame, area: &Rect) { + let sel = self.insert_table.table_state.selected(); + self.insert_table.render_table(frame, *area); + self.insert_table.table_state.select(sel); + } + + fn move_up(&mut self) { + if let Some(selected) = self.insert_table.table_state.selected() { + if selected > 0 { + let mut levels = self.area_list.lock().unwrap(); + levels.swap(selected, selected - 1); + self.insert_table.table_state.select(Some(selected - 1)); + } + } + } + + fn move_down(&mut self) { + if let Some(selected) = self.insert_table.table_state.selected() { + if selected + 1 < self.area_list.lock().unwrap().len() { + let mut levels = self.area_list.lock().unwrap(); + levels.swap(selected, selected + 1); + self.insert_table.table_state.select(Some(selected + 1)); + } + } + } +} + +impl<'a> Page for MessageAreasEditor<'a> { + fn render(&mut self, frame: &mut Frame, area: Rect) { + Clear.render(area, frame.buffer_mut()); + let conference_name = crate::tabs::conferences::get_cur_conference_name(); + let title = get_text_args("area_editor_title", HashMap::from([("conference".to_string(), conference_name)])); + + let block = Block::new() + .title_alignment(Alignment::Center) + .title(Title::from(Span::from(title).style(get_tui_theme().dialog_box_title))) + .style(get_tui_theme().dialog_box) + .padding(Padding::new(2, 2, 1, 1)) + .borders(Borders::ALL) + .border_set(BORDER_SET) + .title_bottom(Span::styled(get_text("icb_setup_key_conf_list_help"), get_tui_theme().key_binding)); + block.render(area, frame.buffer_mut()); + let area = area.inner(Margin { horizontal: 1, vertical: 1 }); + self.display_insert_table(frame, &area); + + if let Some(edit_config) = &mut self.edit_config { + let area = area.inner(Margin { vertical: 3, horizontal: 3 }); + Clear.render(area, frame.buffer_mut()); + let block = Block::new() + .title_alignment(Alignment::Center) + .title(Title::from( + Span::from(get_text("area_editor_edit_title")).style(get_tui_theme().dialog_box_title), + )) + .style(get_tui_theme().dialog_box) + .padding(Padding::new(2, 2, 1, 1)) + .borders(Borders::ALL) + .border_type(BorderType::Double); + // let area = footer.inner(&Margin { vertical: 15, horizontal: 5 }); + block.render(area, frame.buffer_mut()); + edit_config.render(area.inner(Margin { vertical: 1, horizontal: 1 }), frame, &mut self.edit_config_state); + + edit_config + .get_item(self.edit_config_state.selected) + .unwrap() + .text_field_state + .set_cursor_position(frame); + } + if let Some(save_changes) = &self.save_dialog { + save_changes.render(frame, area); + } + } + + fn handle_key_press(&mut self, key: KeyEvent) -> PageMessage { + if self.save_dialog.is_some() { + let res = self.save_dialog.as_mut().unwrap().handle_key_press(key); + return match res { + icy_board_tui::save_changes_dialog::SaveChangesMessage::Cancel => { + self.save_dialog = None; + PageMessage::None + } + icy_board_tui::save_changes_dialog::SaveChangesMessage::Close => PageMessage::Close, + icy_board_tui::save_changes_dialog::SaveChangesMessage::Save => { + if let Some(parent) = self.path.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent).unwrap(); + } + } + self.area_list.lock().unwrap().save(&self.path).unwrap(); + PageMessage::Close + } + icy_board_tui::save_changes_dialog::SaveChangesMessage::None => PageMessage::None, + }; + } + + if let Some(edit_config) = &mut self.edit_config { + match key.code { + KeyCode::Esc => { + self.edit_config = None; + return PageMessage::None; + } + _ => { + edit_config.handle_key_press(key, &mut self.edit_config_state); + } + } + return PageMessage::None; + } + + match key.code { + KeyCode::Esc => { + if self.area_list_orig == self.area_list.lock().unwrap().clone() { + return PageMessage::Close; + } + self.save_dialog = Some(SaveChangesDialog::new()); + return PageMessage::None; + } + _ => match key.code { + KeyCode::PageUp => self.move_up(), + KeyCode::PageDown => self.move_down(), + + KeyCode::Insert => { + self.area_list.lock().unwrap().push(MessageArea::default()); + self.insert_table.content_length += 1; + } + KeyCode::Delete => { + if let Some(selected_item) = self.insert_table.table_state.selected() { + if selected_item < self.area_list.lock().unwrap().len() { + self.area_list.lock().unwrap().remove(selected_item); + self.insert_table.content_length -= 1; + } + } + } + + KeyCode::Enter => { + self.edit_config_state = ConfigMenuState::default(); + + if let Some(selected_item) = self.insert_table.table_state.selected() { + let cmd = self.area_list.lock().unwrap(); + let Some(item) = cmd.get(selected_item) else { + return PageMessage::None; + }; + self.edit_config = Some(ConfigMenu { + obj: (selected_item, self.area_list.clone()), + entry: vec![ + ConfigEntry::Item( + ListItem::new(get_text("area_editor_name"), ListValue::Text(25, item.name.to_string())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + list.lock().unwrap()[*i].name = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("area_editor_file"), ListValue::Path(item.filename.clone())) + .with_label_width(16) + .with_update_path_value(&|(i, list): &(usize, Arc>), value: PathBuf| { + list.lock().unwrap()[*i].filename = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("area_editor_is_readonly"), ListValue::Bool(item.is_read_only)) + .with_label_width(16) + .with_update_bool_value(&|(i, list): &(usize, Arc>), value: bool| { + list.lock().unwrap()[*i].is_read_only = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("area_editor_allow_aliases"), ListValue::Bool(item.allow_aliases)) + .with_label_width(16) + .with_update_bool_value(&|(i, list): &(usize, Arc>), value: bool| { + list.lock().unwrap()[*i].allow_aliases = value; + }), + ), + ConfigEntry::Item( + ListItem::new( + get_text("area_editor_list_sec"), + ListValue::Security(item.req_level_to_list.clone(), item.req_level_to_list.to_string()), + ) + .with_label_width(16) + .with_update_sec_value( + &|(i, list): &(usize, Arc>), value: SecurityExpression| { + list.lock().unwrap()[*i].req_level_to_list = value; + }, + ), + ), + ConfigEntry::Item( + ListItem::new( + get_text("area_editor_enter_sec"), + ListValue::Security(item.req_level_to_enter.clone(), item.req_level_to_enter.to_string()), + ) + .with_label_width(16) + .with_update_sec_value( + &|(i, list): &(usize, Arc>), value: SecurityExpression| { + list.lock().unwrap()[*i].req_level_to_enter = value; + }, + ), + ), + ConfigEntry::Item( + ListItem::new( + get_text("area_editor_attach_sec"), + ListValue::Security(item.req_level_to_save_attach.clone(), item.req_level_to_save_attach.to_string()), + ) + .with_label_width(16) + .with_update_sec_value( + &|(i, list): &(usize, Arc>), value: SecurityExpression| { + list.lock().unwrap()[*i].req_level_to_save_attach = value; + }, + ), + ), + ], + }); + } else { + self.insert_table.handle_key_press(key).unwrap(); + } + } + _ => { + self.insert_table.handle_key_press(key).unwrap(); + } + }, + } + PageMessage::None + } +} + +pub fn edit_areas(_board: (usize, Arc>), path: PathBuf) -> PageMessage { + PageMessage::OpenSubPage(Box::new(MessageAreasEditor::new(&path).unwrap())) +} diff --git a/crates/icbsetup/src/editors/bullettins.rs b/crates/icbsetup/src/editors/bullettins.rs index 25c6bf06..3d5ab728 100644 --- a/crates/icbsetup/src/editors/bullettins.rs +++ b/crates/icbsetup/src/editors/bullettins.rs @@ -15,6 +15,7 @@ use icy_board_engine::{ }; use icy_board_tui::{ config_menu::{ConfigEntry, ConfigMenu, ConfigMenuState, ListItem, ListValue}, + get_text, insert_table::InsertTable, save_changes_dialog::SaveChangesDialog, tab_page::{Page, PageMessage}, @@ -118,13 +119,14 @@ impl<'a> Page for BullettinsEditor<'a> { .style(get_tui_theme().dialog_box) .padding(Padding::new(2, 2, 1, 1)) .borders(Borders::ALL) - .border_type(BorderType::Double); + .border_set(icy_board_tui::BORDER_SET) + .title_bottom(Span::styled(get_text("icb_setup_key_conf_list_help"), get_tui_theme().key_binding)); block.render(area, frame.buffer_mut()); let area = area.inner(Margin { horizontal: 1, vertical: 1 }); self.display_insert_table(frame, &area); if let Some(edit_config) = &mut self.edit_config { - let area = area.inner(Margin { vertical: 2, horizontal: 3 }); + let area = area.inner(Margin { vertical: 8, horizontal: 3 }); Clear.render(area, frame.buffer_mut()); let block = Block::new() .title_alignment(Alignment::Center) @@ -158,6 +160,11 @@ impl<'a> Page for BullettinsEditor<'a> { } icy_board_tui::save_changes_dialog::SaveChangesMessage::Close => PageMessage::Close, icy_board_tui::save_changes_dialog::SaveChangesMessage::Save => { + if let Some(parent) = self.path.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent).unwrap(); + } + } self.blt_list.save(&self.path).unwrap(); PageMessage::Close } diff --git a/crates/icbsetup/src/editors/dirs.rs b/crates/icbsetup/src/editors/dirs.rs index add6d0bc..fc973baa 100644 --- a/crates/icbsetup/src/editors/dirs.rs +++ b/crates/icbsetup/src/editors/dirs.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, path::PathBuf, sync::{Arc, Mutex}, }; @@ -7,13 +8,17 @@ use crossterm::event::{KeyCode, KeyEvent}; use icy_board_engine::{ icy_board::{ file_directory::{DirectoryList, FileDirectory, SortDirection, SortOrder}, + security_expr::SecurityExpression, + user_base::Password, IcyBoard, IcyBoardSerializer, }, Res, }; use icy_board_tui::{ config_menu::{ComboBox, ComboBoxValue, ConfigEntry, ConfigMenu, ConfigMenuState, ListItem, ListValue}, + get_text, get_text_args, insert_table::InsertTable, + save_changes_dialog::SaveChangesDialog, tab_page::{Page, PageMessage}, theme::get_tui_theme, }; @@ -29,26 +34,32 @@ pub struct DirsEditor<'a> { insert_table: InsertTable<'a>, dir_list: Arc>, + dir_list_orig: DirectoryList, edit_config_state: ConfigMenuState, - edit_config: Option>, + edit_config: Option>)>>, + save_dialog: Option, } impl<'a> DirsEditor<'a> { pub(crate) fn new(path: &std::path::PathBuf) -> Res { - let surveys = if path.exists() { + let dir_list_orig = if path.exists() { DirectoryList::load(&path)? } else { DirectoryList::default() }; - let dir_list = Arc::new(Mutex::new(surveys.clone())); - let scroll_state = ScrollbarState::default().content_length(surveys.len()); - let content_length = surveys.len(); + let dir_list = Arc::new(Mutex::new(dir_list_orig.clone())); + let scroll_state = ScrollbarState::default().content_length(dir_list_orig.len()); + let content_length = dir_list_orig.len(); let dl2 = dir_list.clone(); let insert_table = InsertTable { scroll_state, table_state: TableState::default().with_selected(0), - headers: vec!["".to_string(), "Name ".to_string(), "Path".to_string()], + headers: vec![ + "".to_string(), + format!("{:<20}", get_text("dirs_table_name_header")), + get_text("dirs_table_path_header"), + ], get_content: Box::new(move |_table, i, j| { if *i >= dl2.lock().unwrap().len() { return Line::from("".to_string()); @@ -67,8 +78,10 @@ impl<'a> DirsEditor<'a> { path: path.clone(), insert_table, dir_list, + dir_list_orig, edit_config: None, edit_config_state: ConfigMenuState::default(), + save_dialog: None, }) } @@ -102,23 +115,30 @@ impl<'a> DirsEditor<'a> { impl<'a> Page for DirsEditor<'a> { fn render(&mut self, frame: &mut Frame, area: Rect) { Clear.render(area, frame.buffer_mut()); + let conference_name = crate::tabs::conferences::get_cur_conference_name(); + let title = get_text_args("dirs_editor_title", HashMap::from([("conference".to_string(), conference_name)])); + let block = Block::new() .title_alignment(Alignment::Center) - .title(Title::from(Span::from(" File Directories ").style(get_tui_theme().dialog_box_title))) + .title(Title::from(Span::from(title).style(get_tui_theme().dialog_box_title))) .style(get_tui_theme().dialog_box) .padding(Padding::new(2, 2, 1, 1)) .borders(Borders::ALL) - .border_type(BorderType::Double); + .border_set(icy_board_tui::BORDER_SET) + .title_bottom(Span::styled(get_text("icb_setup_key_conf_list_help"), get_tui_theme().key_binding)); + block.render(area, frame.buffer_mut()); let area = area.inner(Margin { horizontal: 1, vertical: 1 }); self.display_insert_table(frame, &area); if let Some(edit_config) = &mut self.edit_config { - let area = area.inner(Margin { vertical: 3, horizontal: 3 }); + let area = area.inner(Margin { vertical: 5, horizontal: 3 }); Clear.render(area, frame.buffer_mut()); let block = Block::new() .title_alignment(Alignment::Center) - .title(Title::from(Span::from(" Edit Directory ").style(get_tui_theme().dialog_box_title))) + .title(Title::from( + Span::from(get_text("dirs_edit_directory_title")).style(get_tui_theme().dialog_box_title), + )) .style(get_tui_theme().dialog_box) .padding(Padding::new(2, 2, 1, 1)) .borders(Borders::ALL) @@ -132,105 +152,40 @@ impl<'a> Page for DirsEditor<'a> { .unwrap() .text_field_state .set_cursor_position(frame); + if let Some(save_changes) = &self.save_dialog { + save_changes.render(frame, area); + } + } + if let Some(save_changes) = &self.save_dialog { + save_changes.render(frame, area); } } fn handle_key_press(&mut self, key: KeyEvent) -> PageMessage { + if self.save_dialog.is_some() { + let res = self.save_dialog.as_mut().unwrap().handle_key_press(key); + return match res { + icy_board_tui::save_changes_dialog::SaveChangesMessage::Cancel => { + self.save_dialog = None; + PageMessage::None + } + icy_board_tui::save_changes_dialog::SaveChangesMessage::Close => PageMessage::Close, + icy_board_tui::save_changes_dialog::SaveChangesMessage::Save => { + if let Some(parent) = self.path.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent).unwrap(); + } + } + self.dir_list.lock().unwrap().save(&self.path).unwrap(); + PageMessage::Close + } + icy_board_tui::save_changes_dialog::SaveChangesMessage::None => PageMessage::None, + }; + } + if let Some(edit_config) = &mut self.edit_config { match key.code { KeyCode::Esc => { - /* - let Some(selected_item) = self.insert_table.table_state.selected() else { - return true; - }; - for item in edit_config.iter() { - match item.id.as_str() { - "name" => { - if let ListValue::Text(_, text) = &item.value { - self.dir_list.lock().unwrap()[selected_item].name = text.to_string(); - } - } - - "path" => { - if let ListValue::Path(path) = &item.value { - self.dir_list.lock().unwrap()[selected_item].path = path.clone(); - } - } - - "password" => { - if let ListValue::Text(_, text) = &item.value { - self.dir_list.lock().unwrap()[selected_item].password = Password::PlainText(text.to_string()); - } - } - - "sort_order" => { - if let ListValue::ComboBox(combo) = &item.value { - let value = &combo.cur_value.value; - if let Ok(sort_order) = SortOrder::from_str(value) { - self.dir_list.lock().unwrap()[selected_item].sort_order = sort_order; - } - } - } - - "sort_direction" => { - if let ListValue::Bool(value) = &item.value { - self.dir_list.lock().unwrap()[selected_item].sort_direction = - if *value { SortDirection::Ascending } else { SortDirection::Descending }; - } - } - - "has_new_files" => { - if let ListValue::Bool(value) = &item.value { - self.dir_list.lock().unwrap()[selected_item].has_new_files = *value; - } - } - - "is_readonly" => { - if let ListValue::Bool(value) = &item.value { - self.dir_list.lock().unwrap()[selected_item].is_readonly = *value; - } - } - - "is_free" => { - if let ListValue::Bool(value) = &item.value { - self.dir_list.lock().unwrap()[selected_item].is_free = *value; - } - } - - "allow_ul_pwd" => { - if let ListValue::Bool(value) = &item.value { - self.dir_list.lock().unwrap()[selected_item].allow_ul_pwd = *value; - } - } - - "list_security" => { - if let ListValue::Text(_, text) = &item.value { - if let Ok(expr) = SecurityExpression::from_str(text) { - self.dir_list.lock().unwrap()[selected_item].list_security = expr; - } - } - } - - "download_security" => { - if let ListValue::Text(_, text) = &item.value { - if let Ok(expr) = SecurityExpression::from_str(text) { - self.dir_list.lock().unwrap()[selected_item].download_security = expr; - } - } - } - - "upload_security" => { - if let ListValue::Text(_, text) = &item.value { - if let Ok(expr) = SecurityExpression::from_str(text) { - self.dir_list.lock().unwrap()[selected_item].upload_security = expr; - } - } - } - _ => { - panic!("Unknown item: {}", item.id); - } - } - }*/ self.edit_config = None; return PageMessage::None; } @@ -243,89 +198,130 @@ impl<'a> Page for DirsEditor<'a> { match key.code { KeyCode::Esc => { - if let Some(parent) = self.path.parent() { - if !parent.exists() { - std::fs::create_dir_all(parent).unwrap(); - } + if self.dir_list_orig == self.dir_list.lock().unwrap().clone() { + return PageMessage::Close; } - self.dir_list.lock().unwrap().save(&self.path).unwrap(); - return PageMessage::Close; + self.save_dialog = Some(SaveChangesDialog::new()); + return PageMessage::None; } - _ => match key.code { - KeyCode::Char('1') => self.move_up(), - KeyCode::Char('2') => self.move_down(), + KeyCode::PageUp => self.move_up(), + KeyCode::PageDown => self.move_down(), - KeyCode::Insert => { - self.dir_list.lock().unwrap().push(FileDirectory::default()); - self.insert_table.content_length += 1; - } - KeyCode::Delete => { - if let Some(selected_item) = self.insert_table.table_state.selected() { - if selected_item < self.dir_list.lock().unwrap().len() { - self.dir_list.lock().unwrap().remove(selected_item); - self.insert_table.content_length -= 1; - } + KeyCode::Insert => { + self.dir_list.lock().unwrap().push(FileDirectory::default()); + self.insert_table.content_length += 1; + } + KeyCode::Delete => { + if let Some(selected_item) = self.insert_table.table_state.selected() { + if selected_item < self.dir_list.lock().unwrap().len() { + self.dir_list.lock().unwrap().remove(selected_item); + self.insert_table.content_length -= 1; } } + } - KeyCode::Enter => { - self.edit_config_state = ConfigMenuState::default(); + KeyCode::Enter => { + self.edit_config_state = ConfigMenuState::default(); - if let Some(selected_item) = self.insert_table.table_state.selected() { - let cmd = self.dir_list.lock().unwrap(); - let Some(item) = cmd.get(selected_item) else { - return PageMessage::None; - }; - self.edit_config = Some(ConfigMenu { - obj: 0, + if let Some(selected_item) = self.insert_table.table_state.selected() { + let cmd = self.dir_list.lock().unwrap(); + let Some(item) = cmd.get(selected_item) else { + return PageMessage::None; + }; + self.edit_config = Some(ConfigMenu { + obj: (selected_item, self.dir_list.clone()), - entry: vec![ - ConfigEntry::Item(ListItem::new("Name".to_string(), ListValue::Text(25, item.name.to_string())).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Path".to_string(), ListValue::Path(item.path.clone())).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Password".to_string(), ListValue::Text(25, item.password.to_string())).with_label_width(16)), - ConfigEntry::Item( - ListItem::new( - "Sort".to_string(), - ListValue::ComboBox(ComboBox { - cur_value: ComboBoxValue::new(format!("{:?}", item.sort_order), format!("{:?}", item.sort_order)), - first: 0, - scroll_state: ScrollbarState::default(), - values: SortOrder::iter() - .map(|x| ComboBoxValue::new(format!("{:?}", x), format!("{:?}", x))) - .collect::>(), - }), - ) - .with_label_width(16), - ), - ConfigEntry::Item( - ListItem::new("Sort ascending".to_string(), ListValue::Bool(item.sort_direction == SortDirection::Ascending)) - .with_label_width(16), + entry: vec![ + ConfigEntry::Item( + ListItem::new(get_text("dirs_edit_name"), ListValue::Text(25, item.name.to_string())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + list.lock().unwrap()[*i].name = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("dirs_edit_path"), ListValue::Path(item.path.clone())) + .with_label_width(16) + .with_update_path_value(&|(i, list): &(usize, Arc>), value: PathBuf| { + list.lock().unwrap()[*i].path = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("dirs_edit_password"), ListValue::Text(12, item.password.to_string())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + list.lock().unwrap()[*i].password = Password::PlainText(value); + }), + ), + ConfigEntry::Item( + ListItem::new( + get_text("dirs_edit_sort"), + ListValue::ComboBox(ComboBox { + cur_value: ComboBoxValue::new(format!("{:?}", item.sort_order), format!("{:?}", item.sort_order)), + first: 0, + scroll_state: ScrollbarState::default(), + values: SortOrder::iter() + .map(|x| ComboBoxValue::new(format!("{:?}", x), format!("{:?}", x))) + .collect::>(), + }), + ) + .with_label_width(16), + ), + ConfigEntry::Item( + ListItem::new(get_text("dirs_edit_sort_asc"), ListValue::Bool(item.sort_direction == SortDirection::Ascending)) + .with_label_width(16) + .with_update_bool_value(&|(i, list): &(usize, Arc>), value: bool| { + list.lock().unwrap()[*i].sort_direction = if value { SortDirection::Ascending } else { SortDirection::Descending }; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("dirs_edit_has_new_files"), ListValue::Bool(item.has_new_files)) + .with_label_width(16) + .with_update_bool_value(&|(i, list): &(usize, Arc>), value: bool| { + list.lock().unwrap()[*i].sort_direction = if value { SortDirection::Ascending } else { SortDirection::Descending }; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("dirs_edit_is_free"), ListValue::Bool(item.is_free)) + .with_label_width(16) + .with_update_bool_value(&|(i, list): &(usize, Arc>), value: bool| { + list.lock().unwrap()[*i].is_free = value; + }), + ), + ConfigEntry::Item( + ListItem::new( + get_text("dirs_edit_list_sec"), + ListValue::Security(item.list_security.clone(), item.list_security.to_string()), + ) + .with_label_width(16) + .with_update_sec_value( + &|(i, list): &(usize, Arc>), value: SecurityExpression| { + list.lock().unwrap()[*i].list_security = value; + }, ), - ConfigEntry::Item(ListItem::new("Has New Files".to_string(), ListValue::Bool(item.has_new_files)).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Is Read-Only".to_string(), ListValue::Bool(item.is_readonly)).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Is Free".to_string(), ListValue::Bool(item.is_free)).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Allow Upload Password".to_string(), ListValue::Bool(item.allow_ul_pwd)).with_label_width(16)), - ConfigEntry::Item( - ListItem::new("List Security".to_string(), ListValue::Text(25, item.list_security.to_string())).with_label_width(16), + ), + ConfigEntry::Item( + ListItem::new( + get_text("dirs_download_sec"), + ListValue::Security(item.download_security.clone(), item.download_security.to_string()), + ) + .with_label_width(16) + .with_update_sec_value( + &|(i, list): &(usize, Arc>), value: SecurityExpression| { + list.lock().unwrap()[*i].download_security = value; + }, ), - ConfigEntry::Item( - ListItem::new("Download Security".to_string(), ListValue::Text(25, item.download_security.to_string())) - .with_label_width(16), - ), - ConfigEntry::Item( - ListItem::new("Upload Security".to_string(), ListValue::Text(25, item.upload_security.to_string())).with_label_width(16), - ), - ], - }); - } else { - self.insert_table.handle_key_press(key).unwrap(); - } - } - - _ => { + ), + ], + }); + } else { self.insert_table.handle_key_press(key).unwrap(); } - }, + } + + _ => { + self.insert_table.handle_key_press(key).unwrap(); + } } PageMessage::None } diff --git a/crates/icbsetup/src/editors/door.rs b/crates/icbsetup/src/editors/door.rs index 6f980b9e..91f0d8c4 100644 --- a/crates/icbsetup/src/editors/door.rs +++ b/crates/icbsetup/src/editors/door.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, path::PathBuf, sync::{Arc, Mutex}, }; @@ -14,7 +15,9 @@ use icy_board_engine::{ }; use icy_board_tui::{ config_menu::{ComboBox, ComboBoxValue, ConfigEntry, ConfigMenu, ConfigMenuState, ListItem, ListValue}, + get_text, get_text_args, insert_table::InsertTable, + save_changes_dialog::SaveChangesDialog, tab_page::{Page, PageMessage}, theme::get_tui_theme, }; @@ -33,21 +36,22 @@ enum EditCommandMode { pub struct DoorEditor<'a> { path: std::path::PathBuf, - door_list: DoorList, + door_list_orig: DoorList, + door_list: Arc>, + menu: ConfigMenu, menu_state: ConfigMenuState, mode: EditCommandMode, insert_table: InsertTable<'a>, - command: Arc>>, - edit_config_state: ConfigMenuState, - edit_config: Option>, + edit_config: Option>)>>, + save_dialog: Option, } impl<'a> DoorEditor<'a> { pub(crate) fn new(path: &std::path::PathBuf) -> Res { - let mut door_list = if path.exists() { + let mut door_list_orig = if path.exists() { DoorList::load(&path)? } else { let mut door_list = DoorList::default(); @@ -55,11 +59,11 @@ impl<'a> DoorEditor<'a> { door_list }; - if door_list.accounts.is_empty() { - door_list.accounts.push(DoorServerAccount::BBSLink(BBSLink::default())); + if door_list_orig.accounts.is_empty() { + door_list_orig.accounts.push(DoorServerAccount::BBSLink(BBSLink::default())); } - let DoorServerAccount::BBSLink(bbs_link) = &door_list.accounts[0]; + let DoorServerAccount::BBSLink(bbs_link) = &door_list_orig.accounts[0]; let l = 16; let items = vec![ConfigEntry::Group( "BBSLink credentials".to_string(), @@ -71,14 +75,19 @@ impl<'a> DoorEditor<'a> { )]; let menu = ConfigMenu { obj: 0, entry: items }; - let command_arc = Arc::new(Mutex::new(door_list.doors.clone())); - let scroll_state = ScrollbarState::default().content_length(door_list.doors.len()); - let content_length = door_list.doors.len(); - let cmd2 = command_arc.clone(); + let door_list = Arc::new(Mutex::new(door_list_orig.clone())); + let scroll_state = ScrollbarState::default().content_length(door_list_orig.doors.len()); + let content_length = door_list_orig.doors.len(); + let cmd2 = door_list.clone(); + let insert_table = InsertTable { scroll_state, table_state: TableState::default().with_selected(0), - headers: vec!["Door ".to_string(), "Description".to_string(), "Type".to_string()], + headers: vec![ + format!("{:<15}", get_text("doors_editor_header_door")), + format!("{:<33}", get_text("doors_editor_header_description")), + get_text("doors_editor_header_type"), + ], get_content: Box::new(move |_table, i, j| { if *i >= cmd2.lock().unwrap().len() { return Line::from("".to_string()); @@ -92,17 +101,17 @@ impl<'a> DoorEditor<'a> { }), content_length, }; - Ok(Self { path: path.clone(), door_list, + door_list_orig, menu, menu_state: ConfigMenuState::default(), insert_table, - command: command_arc, mode: EditCommandMode::Config, edit_config: None, edit_config_state: ConfigMenuState::default(), + save_dialog: None, }) } @@ -119,13 +128,25 @@ impl<'a> DoorEditor<'a> { impl<'a> Page for DoorEditor<'a> { fn render(&mut self, frame: &mut Frame, area: Rect) { Clear.render(area, frame.buffer_mut()); + let conference_name = crate::tabs::conferences::get_cur_conference_name(); + let title = get_text_args("doors_editor_title", HashMap::from([("conference".to_string(), conference_name)])); + let block = Block::new() .title_alignment(Alignment::Center) - .title(Title::from(Span::from(" Edit Doors ").style(get_tui_theme().dialog_box_title))) + .title(Title::from(Span::from(title).style(get_tui_theme().dialog_box_title))) .style(get_tui_theme().dialog_box) .padding(Padding::new(2, 2, 1, 1)) .borders(Borders::ALL) - .border_type(BorderType::Double); + .border_set(icy_board_tui::BORDER_SET) + .title_bottom(Span::styled( + if self.mode == EditCommandMode::Config { + get_text("doors_editor_key_help") + } else { + get_text("doors_editor_key_help_door") + }, + get_tui_theme().key_binding, + )); + block.render(area, frame.buffer_mut()); let vertical = Layout::vertical([Constraint::Length(6), Constraint::Fill(1)]); @@ -146,11 +167,13 @@ impl<'a> Page for DoorEditor<'a> { .set_cursor_position(frame); if let Some(edit_config) = &mut self.edit_config { - let area = area.inner(Margin { vertical: 8, horizontal: 3 }); + let area = area.inner(Margin { vertical: 7, horizontal: 3 }); Clear.render(area, frame.buffer_mut()); let block = Block::new() .title_alignment(Alignment::Center) - .title(Title::from(Span::from(" Edit Door ").style(get_tui_theme().dialog_box_title))) + .title(Title::from( + Span::from(get_text("doors_editor_edit_title")).style(get_tui_theme().dialog_box_title), + )) .style(get_tui_theme().dialog_box) .padding(Padding::new(2, 2, 1, 1)) .borders(Borders::ALL) @@ -165,54 +188,36 @@ impl<'a> Page for DoorEditor<'a> { .text_field_state .set_cursor_position(frame); } + if let Some(save_changes) = &self.save_dialog { + save_changes.render(frame, area); + } } fn handle_key_press(&mut self, key: KeyEvent) -> PageMessage { + if self.save_dialog.is_some() { + let res = self.save_dialog.as_mut().unwrap().handle_key_press(key); + return match res { + icy_board_tui::save_changes_dialog::SaveChangesMessage::Cancel => { + self.save_dialog = None; + PageMessage::None + } + icy_board_tui::save_changes_dialog::SaveChangesMessage::Close => PageMessage::Close, + icy_board_tui::save_changes_dialog::SaveChangesMessage::Save => { + if let Some(parent) = self.path.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent).unwrap(); + } + } + self.door_list.lock().unwrap().save(&self.path).unwrap(); + PageMessage::Close + } + icy_board_tui::save_changes_dialog::SaveChangesMessage::None => PageMessage::None, + }; + } + if let Some(edit_config) = &mut self.edit_config { match key.code { KeyCode::Esc => { - /* - let Some(selected_item) = self.insert_table.table_state.selected() else { - return true; - }; - for item in edit_config.iter() { - match item.id.as_str() { - "name" => { - if let ListValue::Text(_, ref value) = item.value { - self.command.lock().unwrap()[selected_item].name = value.clone(); - } - } - "description" => { - if let ListValue::Text(_, ref value) = item.value { - self.command.lock().unwrap()[selected_item].description = value.clone(); - } - } - "password" => { - if let ListValue::Text(_, ref value) = item.value { - self.command.lock().unwrap()[selected_item].password = value.clone(); - } - } - "path" => { - if let ListValue::Text(_, ref value) = item.value { - self.command.lock().unwrap()[selected_item].path = value.clone(); - } - } - "use_shell_execute" => { - if let ListValue::Bool(ref value) = item.value { - self.command.lock().unwrap()[selected_item].use_shell_execute = *value; - } - } - - "door_type" => { - if let ListValue::ComboBox(ref value) = item.value { - let value = value.cur_value.value.parse::().unwrap(); - self.command.lock().unwrap()[selected_item].door_type = value; - } - } - _ => {} - } - }*/ - self.edit_config = None; return PageMessage::None; } @@ -225,31 +230,11 @@ impl<'a> Page for DoorEditor<'a> { match key.code { KeyCode::Esc => { - /*/ - for item in self.menu.iter() { - if let ListValue::Text(_, value) = &item.value { - match item.id.as_str() { - "system_code" => { - let DoorServerAccount::BBSLink(bbs_link) = &mut self.door_list.accounts[0]; - bbs_link.system_code = value.clone(); - } - "auth_code" => { - let DoorServerAccount::BBSLink(bbs_link) = &mut self.door_list.accounts[0]; - bbs_link.auth_code = value.clone(); - } - "sheme_code" => { - let DoorServerAccount::BBSLink(bbs_link) = &mut self.door_list.accounts[0]; - bbs_link.sheme_code = value.clone(); - } - _ => {} - } - } + if self.door_list_orig == self.door_list.lock().unwrap().clone() { + return PageMessage::Close; } - */ - self.door_list.doors.clear(); - self.door_list.doors.append(&mut self.command.lock().unwrap()); - self.door_list.save(&self.path).unwrap(); - return PageMessage::Close; + self.save_dialog = Some(SaveChangesDialog::new()); + return PageMessage::None; } KeyCode::Tab => { @@ -263,22 +248,25 @@ impl<'a> Page for DoorEditor<'a> { _ => match self.mode { EditCommandMode::Table => match key.code { KeyCode::Insert => { - self.command.lock().unwrap().push(Door { - name: format!("door{}", self.door_list.len() + 1), - description: "".to_string(), - password: "".to_string(), - securiy_level: SecurityExpression::default(), - use_shell_execute: false, - door_type: DoorType::BBSlink, - path: "".to_string(), - drop_file: Default::default(), - }); + let name = format!("door{}", self.door_list.lock().unwrap().len() + 1); + if let Ok(lock) = &mut self.door_list.lock() { + lock.push(Door { + name, + description: "".to_string(), + password: "".to_string(), + securiy_level: SecurityExpression::default(), + use_shell_execute: false, + door_type: DoorType::BBSlink, + path: "".to_string(), + drop_file: Default::default(), + }); + } self.insert_table.content_length += 1; } KeyCode::Delete => { if let Some(selected_item) = self.insert_table.table_state.selected() { - if selected_item < self.command.lock().unwrap().len() { - self.command.lock().unwrap().remove(selected_item); + if selected_item < self.door_list.lock().unwrap().len() { + self.door_list.lock().unwrap().remove(selected_item); self.insert_table.content_length -= 1; } } @@ -288,22 +276,44 @@ impl<'a> Page for DoorEditor<'a> { self.edit_config_state = ConfigMenuState::default(); if let Some(selected_item) = self.insert_table.table_state.selected() { - let cmd = self.command.lock().unwrap(); + let cmd = self.door_list.lock().unwrap(); let Some(action) = cmd.get(selected_item) else { return PageMessage::None; }; self.edit_config = Some(ConfigMenu { - obj: 0, + obj: (selected_item, self.door_list.clone()), entry: vec![ - ConfigEntry::Item(ListItem::new("Name".to_string(), ListValue::Text(30, action.name.clone())).with_label_width(16)), ConfigEntry::Item( - ListItem::new("Description".to_string(), ListValue::Text(30, action.description.clone())).with_label_width(16), + ListItem::new(get_text("door_editor_name"), ListValue::Text(30, action.name.clone())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + list.lock().unwrap()[*i].name = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("door_editor_description"), ListValue::Text(30, action.description.clone())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + list.lock().unwrap()[*i].description = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("door_editor_password"), ListValue::Text(30, action.password.clone())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + list.lock().unwrap()[*i].password = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("door_editor_path"), ListValue::Text(30, action.path.clone())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + list.lock().unwrap()[*i].path = value; + }), ), - ConfigEntry::Item(ListItem::new("Password".to_string(), ListValue::Text(30, action.password.clone())).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Path".to_string(), ListValue::Text(30, action.path.clone())).with_label_width(16)), ConfigEntry::Item( ListItem::new( - "Door Type".to_string(), + get_text("door_editor_door_type"), ListValue::ComboBox(ComboBox { cur_value: ComboBoxValue::new(format!("{}", action.door_type), format!("{}", action.door_type)), first: 0, @@ -313,10 +323,23 @@ impl<'a> Page for DoorEditor<'a> { .collect::>(), }), ) - .with_label_width(16), + .with_label_width(16) + .with_update_combobox_value( + &|(i, list): &(usize, Arc>), value: &ComboBox| { + if value.cur_value.value == "BBSlink" { + list.lock().unwrap()[*i].door_type = DoorType::BBSlink; + } else { + list.lock().unwrap()[*i].door_type = DoorType::Local; + } + }, + ), ), ConfigEntry::Item( - ListItem::new("Use Shell Execute".to_string(), ListValue::Bool(action.use_shell_execute)).with_label_width(16), + ListItem::new(get_text("door_editor_use_shell_execute"), ListValue::Bool(action.use_shell_execute)) + .with_label_width(16) + .with_update_bool_value(&|(i, list): &(usize, Arc>), value: bool| { + list.lock().unwrap()[*i].use_shell_execute = value; + }), ), ], }); diff --git a/crates/icbsetup/src/editors/languages.rs b/crates/icbsetup/src/editors/languages.rs index a7926b96..6e1bf243 100644 --- a/crates/icbsetup/src/editors/languages.rs +++ b/crates/icbsetup/src/editors/languages.rs @@ -13,7 +13,9 @@ use icy_board_engine::{ }; use icy_board_tui::{ config_menu::{ConfigEntry, ConfigMenu, ConfigMenuState, ListItem, ListValue}, + get_text, insert_table::InsertTable, + save_changes_dialog::SaveChangesDialog, tab_page::{Page, PageMessage}, theme::get_tui_theme, }; @@ -28,33 +30,35 @@ pub struct LanguageListEditor<'a> { path: std::path::PathBuf, insert_table: InsertTable<'a>, - dir_list: Arc>, + lang_list_orig: SupportedLanguages, + lang_list: Arc>, edit_config_state: ConfigMenuState, - edit_config: Option>, + edit_config: Option>)>>, + save_dialog: Option, } impl<'a> LanguageListEditor<'a> { pub(crate) fn new(path: &std::path::PathBuf) -> Res { - let surveys = if path.exists() { + let lang_list_orig = if path.exists() { SupportedLanguages::load(&path)? } else { SupportedLanguages::default() }; - let dir_list = Arc::new(Mutex::new(surveys.clone())); - let scroll_state = ScrollbarState::default().content_length(surveys.len()); - let content_length = surveys.len(); - let dl2 = dir_list.clone(); + let lang_list = Arc::new(Mutex::new(lang_list_orig.clone())); + let scroll_state = ScrollbarState::default().content_length(lang_list_orig.len()); + let content_length = lang_list_orig.len(); + let dl2 = lang_list.clone(); let insert_table = InsertTable { scroll_state, table_state: TableState::default().with_selected(0), headers: vec![ "".to_string(), - "Language ".to_string(), - "Extension".to_string(), - "Locale".to_string(), - "Yes".to_string(), - "No".to_string(), + get_text("lang_editor_header_language"), + get_text("lang_editor_header_ext"), + get_text("lang_editor_header_locale"), + get_text("lang_editor_header_yes"), + get_text("lang_editor_header_no"), ], get_content: Box::new(move |_table, i, j| { if *i >= dl2.lock().unwrap().len() { @@ -62,11 +66,11 @@ impl<'a> LanguageListEditor<'a> { } match j { 0 => Line::from(format!("{})", *i + 1)), - 1 => Line::from(format!("{}", dl2.lock().unwrap().languages[*i].description)), - 2 => Line::from(format!("{}", dl2.lock().unwrap().languages[*i].extension)), - 3 => Line::from(format!("{}", dl2.lock().unwrap().languages[*i].locale)), - 4 => Line::from(format!("{}", dl2.lock().unwrap().languages[*i].yes_char)), - 5 => Line::from(format!("{}", dl2.lock().unwrap().languages[*i].no_char)), + 1 => Line::from(format!("{}", dl2.lock().unwrap()[*i].description)), + 2 => Line::from(format!("{}", dl2.lock().unwrap()[*i].extension)), + 3 => Line::from(format!("{}", dl2.lock().unwrap()[*i].locale)), + 4 => Line::from(format!("{}", dl2.lock().unwrap()[*i].yes_char)), + 5 => Line::from(format!("{}", dl2.lock().unwrap()[*i].no_char)), _ => Line::from("".to_string()), } }), @@ -76,9 +80,11 @@ impl<'a> LanguageListEditor<'a> { Ok(Self { path: path.clone(), insert_table, - dir_list, + lang_list_orig, + lang_list, edit_config: None, edit_config_state: ConfigMenuState::default(), + save_dialog: None, }) } @@ -91,8 +97,8 @@ impl<'a> LanguageListEditor<'a> { fn move_up(&mut self) { if let Some(selected) = self.insert_table.table_state.selected() { if selected > 0 { - let mut levels = self.dir_list.lock().unwrap(); - levels.languages.swap(selected, selected - 1); + let mut levels = self.lang_list.lock().unwrap(); + levels.swap(selected, selected - 1); self.insert_table.table_state.select(Some(selected - 1)); } } @@ -100,9 +106,9 @@ impl<'a> LanguageListEditor<'a> { fn move_down(&mut self) { if let Some(selected) = self.insert_table.table_state.selected() { - if selected + 1 < self.dir_list.lock().unwrap().len() { - let mut levels = self.dir_list.lock().unwrap(); - levels.languages.swap(selected, selected + 1); + if selected + 1 < self.lang_list.lock().unwrap().len() { + let mut levels = self.lang_list.lock().unwrap(); + levels.swap(selected, selected + 1); self.insert_table.table_state.select(Some(selected + 1)); } } @@ -112,23 +118,29 @@ impl<'a> LanguageListEditor<'a> { impl<'a> Page for LanguageListEditor<'a> { fn render(&mut self, frame: &mut Frame, area: Rect) { Clear.render(area, frame.buffer_mut()); + let title = get_text("lang_editor_title"); + let block = Block::new() .title_alignment(Alignment::Center) - .title(Title::from(Span::from(" Languages ").style(get_tui_theme().dialog_box_title))) + .title(Title::from(Span::from(title).style(get_tui_theme().dialog_box_title))) .style(get_tui_theme().dialog_box) .padding(Padding::new(2, 2, 1, 1)) .borders(Borders::ALL) - .border_type(BorderType::Double); + .border_set(icy_board_tui::BORDER_SET) + .title_bottom(Span::styled(get_text("icb_setup_key_conf_list_help"), get_tui_theme().key_binding)); block.render(area, frame.buffer_mut()); let area = area.inner(Margin { horizontal: 1, vertical: 1 }); self.display_insert_table(frame, &area); if let Some(edit_config) = &mut self.edit_config { - let area = area.inner(Margin { vertical: 8, horizontal: 6 }); + let mut area = area.inner(Margin { vertical: 6, horizontal: 6 }); + area.height -= 2; Clear.render(area, frame.buffer_mut()); let block = Block::new() .title_alignment(Alignment::Center) - .title(Title::from(Span::from(" Edit Language ").style(get_tui_theme().dialog_box_title))) + .title(Title::from( + Span::from(get_text("lang_editor_edit_lang")).style(get_tui_theme().dialog_box_title), + )) .style(get_tui_theme().dialog_box) .padding(Padding::new(2, 2, 1, 1)) .borders(Borders::ALL) @@ -143,53 +155,37 @@ impl<'a> Page for LanguageListEditor<'a> { .text_field_state .set_cursor_position(frame); } + + if let Some(save_changes) = &self.save_dialog { + save_changes.render(frame, area); + } } fn handle_key_press(&mut self, key: KeyEvent) -> PageMessage { + if self.save_dialog.is_some() { + let res = self.save_dialog.as_mut().unwrap().handle_key_press(key); + return match res { + icy_board_tui::save_changes_dialog::SaveChangesMessage::Cancel => { + self.save_dialog = None; + PageMessage::None + } + icy_board_tui::save_changes_dialog::SaveChangesMessage::Close => PageMessage::Close, + icy_board_tui::save_changes_dialog::SaveChangesMessage::Save => { + if let Some(parent) = self.path.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent).unwrap(); + } + } + self.lang_list.lock().unwrap().save(&self.path).unwrap(); + PageMessage::Close + } + icy_board_tui::save_changes_dialog::SaveChangesMessage::None => PageMessage::None, + }; + } + if let Some(edit_config) = &mut self.edit_config { match key.code { KeyCode::Esc => { - /* - let Some(selected_item) = self.insert_table.table_state.selected() else { - return true; - }; - for item in edit_config.iter() { - match item.id.as_str() { - "description" => { - if let ListValue::Text(_, text) = &item.value { - self.dir_list.lock().unwrap().languages[selected_item].description = text.to_string(); - } - } - - "extension" => { - if let ListValue::Text(_, text) = &item.value { - self.dir_list.lock().unwrap().languages[selected_item].extension = text.to_string(); - } - } - - "locale" => { - if let ListValue::Text(_, text) = &item.value { - self.dir_list.lock().unwrap().languages[selected_item].locale = text.to_string(); - } - } - - "yes_char" => { - if let ListValue::Text(_, text) = &item.value { - self.dir_list.lock().unwrap().languages[selected_item].yes_char = text.chars().next().unwrap_or('Y'); - } - } - - "no_char" => { - if let ListValue::Text(_, text) = &item.value { - self.dir_list.lock().unwrap().languages[selected_item].no_char = text.chars().next().unwrap_or('N'); - } - } - - _ => { - panic!("Unknown item: {}", item.id); - } - } - }*/ self.edit_config = None; return PageMessage::None; } @@ -202,55 +198,87 @@ impl<'a> Page for LanguageListEditor<'a> { match key.code { KeyCode::Esc => { - self.dir_list.lock().unwrap().save(&self.path).unwrap(); - return PageMessage::Close; + if self.lang_list_orig == self.lang_list.lock().unwrap().clone() { + return PageMessage::Close; + } + self.save_dialog = Some(SaveChangesDialog::new()); + return PageMessage::None; } - _ => match key.code { - KeyCode::Char('1') => self.move_up(), - KeyCode::Char('2') => self.move_down(), + KeyCode::PageUp => self.move_up(), + KeyCode::PageDown => self.move_down(), - KeyCode::Insert => { - self.dir_list.lock().unwrap().languages.push(Language::default()); - self.insert_table.content_length += 1; - } - KeyCode::Delete => { - if let Some(selected_item) = self.insert_table.table_state.selected() { - if selected_item < self.dir_list.lock().unwrap().len() { - self.dir_list.lock().unwrap().languages.remove(selected_item); - self.insert_table.content_length -= 1; - } + KeyCode::Insert => { + self.lang_list.lock().unwrap().push(Language::default()); + self.insert_table.content_length += 1; + } + KeyCode::Delete => { + if let Some(selected_item) = self.insert_table.table_state.selected() { + if selected_item < self.lang_list.lock().unwrap().len() { + self.lang_list.lock().unwrap().remove(selected_item); + self.insert_table.content_length -= 1; } } + } - KeyCode::Enter => { - self.edit_config_state = ConfigMenuState::default(); - - if let Some(selected_item) = self.insert_table.table_state.selected() { - let cmd = self.dir_list.lock().unwrap(); - let Some(item) = cmd.languages.get(selected_item) else { - return PageMessage::None; - }; - self.edit_config = Some(ConfigMenu { - obj: 0, - entry: vec![ - ConfigEntry::Item( - ListItem::new("Language".to_string(), ListValue::Text(25, item.description.to_string())).with_label_width(16), - ), - ConfigEntry::Item(ListItem::new("Extension".to_string(), ListValue::Text(25, item.extension.to_string())).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Locale".to_string(), ListValue::Text(25, item.locale.to_string())).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Yes Char".to_string(), ListValue::Text(25, item.yes_char.to_string())).with_label_width(16)), - ConfigEntry::Item(ListItem::new("No Char".to_string(), ListValue::Text(25, item.no_char.to_string())).with_label_width(16)), - ], - }); - } else { - self.insert_table.handle_key_press(key).unwrap(); - } - } + KeyCode::Enter => { + self.edit_config_state = ConfigMenuState::default(); - _ => { + if let Some(selected_item) = self.insert_table.table_state.selected() { + let cmd = self.lang_list.lock().unwrap(); + let Some(item) = cmd.get(selected_item) else { + return PageMessage::None; + }; + self.edit_config = Some(ConfigMenu { + obj: (selected_item, self.lang_list.clone()), + entry: vec![ + ConfigEntry::Item( + ListItem::new(get_text("lang_editor_edit_lang_label"), ListValue::Text(25, item.description.to_string())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + list.lock().unwrap()[*i].description = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("lang_editor_edit_extension"), ListValue::Text(25, item.extension.to_string())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + list.lock().unwrap()[*i].extension = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("lang_editor_edit_locale"), ListValue::Text(25, item.locale.to_string())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + list.lock().unwrap()[*i].locale = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("lang_editor_edit_yes_char"), ListValue::Text(1, item.yes_char.to_string())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + if let Some(c) = value.chars().next() { + list.lock().unwrap()[*i].yes_char = c; + } + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("lang_editor_edit_no_char"), ListValue::Text(1, item.no_char.to_string())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + if let Some(c) = value.chars().next() { + list.lock().unwrap()[*i].no_char = c; + } + }), + ), + ], + }); + } else { self.insert_table.handle_key_press(key).unwrap(); } - }, + } + _ => { + self.insert_table.handle_key_press(key).unwrap(); + } } PageMessage::None } diff --git a/crates/icbsetup/src/editors/messages.rs b/crates/icbsetup/src/editors/messages.rs deleted file mode 100644 index a59d92c3..00000000 --- a/crates/icbsetup/src/editors/messages.rs +++ /dev/null @@ -1,266 +0,0 @@ -use std::{ - path::PathBuf, - sync::{Arc, Mutex}, -}; - -use crossterm::event::{KeyCode, KeyEvent}; -use icy_board_engine::{ - icy_board::{ - message_area::{AreaList, MessageArea}, - IcyBoard, IcyBoardSerializer, - }, - Res, -}; -use icy_board_tui::{ - config_menu::{ConfigEntry, ConfigMenu, ConfigMenuState, ListItem, ListValue}, - insert_table::InsertTable, - tab_page::{Page, PageMessage}, - theme::get_tui_theme, -}; -use ratatui::{ - layout::{Alignment, Margin, Rect}, - text::{Line, Span}, - widgets::{block::Title, Block, BorderType, Borders, Clear, Padding, ScrollbarState, TableState, Widget}, - Frame, -}; - -pub struct MessageAreasEditor<'a> { - path: std::path::PathBuf, - - insert_table: InsertTable<'a>, - dir_list: Arc>, - - edit_config_state: ConfigMenuState, - edit_config: Option>, -} - -impl<'a> MessageAreasEditor<'a> { - pub(crate) fn new(path: &std::path::PathBuf) -> Res { - let surveys = if path.exists() { AreaList::load(&path)? } else { AreaList::default() }; - let dir_list = Arc::new(Mutex::new(surveys.clone())); - let scroll_state = ScrollbarState::default().content_length(surveys.len()); - let content_length = surveys.len(); - let dl2 = dir_list.clone(); - let insert_table = InsertTable { - scroll_state, - table_state: TableState::default().with_selected(0), - headers: vec!["".to_string(), "Name ".to_string(), "Path".to_string()], - get_content: Box::new(move |_table, i, j| { - if *i >= dl2.lock().unwrap().len() { - return Line::from("".to_string()); - } - match j { - 0 => Line::from(format!("{})", *i + 1)), - 1 => Line::from(format!("{}", dl2.lock().unwrap()[*i].name)), - 2 => Line::from(format!("{}", dl2.lock().unwrap()[*i].filename.display())), - _ => Line::from("".to_string()), - } - }), - content_length, - }; - - Ok(Self { - path: path.clone(), - insert_table, - dir_list, - edit_config: None, - edit_config_state: ConfigMenuState::default(), - }) - } - - fn display_insert_table(&mut self, frame: &mut Frame, area: &Rect) { - let sel = self.insert_table.table_state.selected(); - self.insert_table.render_table(frame, *area); - self.insert_table.table_state.select(sel); - } - - fn move_up(&mut self) { - if let Some(selected) = self.insert_table.table_state.selected() { - if selected > 0 { - let mut levels = self.dir_list.lock().unwrap(); - levels.swap(selected, selected - 1); - self.insert_table.table_state.select(Some(selected - 1)); - } - } - } - - fn move_down(&mut self) { - if let Some(selected) = self.insert_table.table_state.selected() { - if selected + 1 < self.dir_list.lock().unwrap().len() { - let mut levels = self.dir_list.lock().unwrap(); - levels.swap(selected, selected + 1); - self.insert_table.table_state.select(Some(selected + 1)); - } - } - } -} - -impl<'a> Page for MessageAreasEditor<'a> { - fn render(&mut self, frame: &mut Frame, area: Rect) { - Clear.render(area, frame.buffer_mut()); - let block = Block::new() - .title_alignment(Alignment::Center) - .title(Title::from(Span::from(" Message Areas ").style(get_tui_theme().dialog_box_title))) - .style(get_tui_theme().dialog_box) - .padding(Padding::new(2, 2, 1, 1)) - .borders(Borders::ALL) - .border_type(BorderType::Double); - block.render(area, frame.buffer_mut()); - let area = area.inner(Margin { horizontal: 1, vertical: 1 }); - self.display_insert_table(frame, &area); - - if let Some(edit_config) = &mut self.edit_config { - let area = area.inner(Margin { vertical: 3, horizontal: 3 }); - Clear.render(area, frame.buffer_mut()); - let block = Block::new() - .title_alignment(Alignment::Center) - .title(Title::from(Span::from(" Message Area ").style(get_tui_theme().dialog_box_title))) - .style(get_tui_theme().dialog_box) - .padding(Padding::new(2, 2, 1, 1)) - .borders(Borders::ALL) - .border_type(BorderType::Double); - // let area = footer.inner(&Margin { vertical: 15, horizontal: 5 }); - block.render(area, frame.buffer_mut()); - edit_config.render(area.inner(Margin { vertical: 1, horizontal: 1 }), frame, &mut self.edit_config_state); - - edit_config - .get_item(self.edit_config_state.selected) - .unwrap() - .text_field_state - .set_cursor_position(frame); - } - } - - fn handle_key_press(&mut self, key: KeyEvent) -> PageMessage { - if let Some(edit_config) = &mut self.edit_config { - match key.code { - KeyCode::Esc => { - /* - let Some(selected_item) = self.insert_table.table_state.selected() else { - return true; - }; - for item in edit_config.iter() { - match item.id.as_str() { - "name" => { - if let ListValue::Text(_, text) = &item.value { - self.dir_list.lock().unwrap()[selected_item].name = text.to_string(); - } - } - - "filename" => { - if let ListValue::Path(path) = &item.value { - self.dir_list.lock().unwrap()[selected_item].filename = path.clone(); - } - } - - "is_readonly" => { - if let ListValue::Bool(value) = &item.value { - self.dir_list.lock().unwrap()[selected_item].is_read_only = *value; - } - } - - "allow_aliases" => { - if let ListValue::Bool(value) = &item.value { - self.dir_list.lock().unwrap()[selected_item].allow_aliases = *value; - } - } - - "req_level_to_list" => { - if let ListValue::Text(_, text) = &item.value { - self.dir_list.lock().unwrap()[selected_item].req_level_to_list = SecurityExpression::from_str(text).unwrap(); - } - } - - "req_level_to_enter" => { - if let ListValue::Text(_, text) = &item.value { - self.dir_list.lock().unwrap()[selected_item].req_level_to_enter = SecurityExpression::from_str(text).unwrap(); - } - } - - "req_level_to_save_attach" => { - if let ListValue::Text(_, text) = &item.value { - self.dir_list.lock().unwrap()[selected_item].req_level_to_save_attach = SecurityExpression::from_str(text).unwrap(); - } - } - - _ => { - panic!("Unknown item: {}", item.id); - } - } - }*/ - self.edit_config = None; - return PageMessage::None; - } - _ => { - edit_config.handle_key_press(key, &mut self.edit_config_state); - } - } - return PageMessage::None; - } - - match key.code { - KeyCode::Esc => { - self.dir_list.lock().unwrap().save(&self.path).unwrap(); - return PageMessage::Close; - } - _ => match key.code { - KeyCode::Char('1') => self.move_up(), - KeyCode::Char('2') => self.move_down(), - - KeyCode::Insert => { - self.dir_list.lock().unwrap().push(MessageArea::default()); - self.insert_table.content_length += 1; - } - KeyCode::Delete => { - if let Some(selected_item) = self.insert_table.table_state.selected() { - if selected_item < self.dir_list.lock().unwrap().len() { - self.dir_list.lock().unwrap().remove(selected_item); - self.insert_table.content_length -= 1; - } - } - } - - KeyCode::Enter => { - self.edit_config_state = ConfigMenuState::default(); - - if let Some(selected_item) = self.insert_table.table_state.selected() { - let cmd = self.dir_list.lock().unwrap(); - let Some(item) = cmd.get(selected_item) else { - return PageMessage::None; - }; - self.edit_config = Some(ConfigMenu { - obj: 0, - entry: vec![ - ConfigEntry::Item(ListItem::new("Name".to_string(), ListValue::Text(25, item.name.to_string())).with_label_width(16)), - ConfigEntry::Item(ListItem::new("File".to_string(), ListValue::Path(item.filename.clone())).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Is Read-Only".to_string(), ListValue::Bool(item.is_read_only)).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Allow Aliases".to_string(), ListValue::Bool(item.allow_aliases)).with_label_width(16)), - ConfigEntry::Item( - ListItem::new("List Security".to_string(), ListValue::Text(25, item.req_level_to_list.to_string())).with_label_width(16), - ), - ConfigEntry::Item( - ListItem::new("Enter Security".to_string(), ListValue::Text(25, item.req_level_to_enter.to_string())).with_label_width(16), - ), - ConfigEntry::Item( - ListItem::new("Attach Security".to_string(), ListValue::Text(25, item.req_level_to_save_attach.to_string())) - .with_label_width(16), - ), - ], - }); - } else { - self.insert_table.handle_key_press(key).unwrap(); - } - } - - _ => { - self.insert_table.handle_key_press(key).unwrap(); - } - }, - } - PageMessage::None - } -} - -pub fn edit_areas(_board: (usize, Arc>), path: PathBuf) -> PageMessage { - PageMessage::OpenSubPage(Box::new(MessageAreasEditor::new(&path).unwrap())) -} diff --git a/crates/icbsetup/src/editors/mod.rs b/crates/icbsetup/src/editors/mod.rs index ed84179e..2a90dc17 100644 --- a/crates/icbsetup/src/editors/mod.rs +++ b/crates/icbsetup/src/editors/mod.rs @@ -1,8 +1,8 @@ pub mod accounting_rates; +pub mod areas; pub mod bullettins; pub mod dirs; pub mod door; pub mod languages; -pub mod messages; pub mod sec_editor; pub mod surveys; diff --git a/crates/icbsetup/src/editors/sec_editor.rs b/crates/icbsetup/src/editors/sec_editor.rs index cc082dbc..45dbc631 100644 --- a/crates/icbsetup/src/editors/sec_editor.rs +++ b/crates/icbsetup/src/editors/sec_editor.rs @@ -13,7 +13,9 @@ use icy_board_engine::{ }; use icy_board_tui::{ config_menu::{ConfigEntry, ConfigMenu, ConfigMenuState, ListItem, ListValue}, + get_text, insert_table::InsertTable, + save_changes_dialog::SaveChangesDialog, tab_page::{Page, PageMessage}, theme::get_tui_theme, }; @@ -26,18 +28,19 @@ use ratatui::{ pub struct SecurityLevelEditor<'a> { path: std::path::PathBuf, - door_list: SecurityLevelDefinitions, insert_table: InsertTable<'a>, - sec_levels: Arc>>, + sec_levels_orig: SecurityLevelDefinitions, + sec_levels: Arc>, edit_config_state: ConfigMenuState, - edit_config: Option>, + edit_config: Option>)>>, + save_dialog: Option, } impl<'a> SecurityLevelEditor<'a> { pub(crate) fn new(path: &std::path::PathBuf) -> Res { - let sec_levels = if path.exists() { + let sec_levels_orig = if path.exists() { SecurityLevelDefinitions::load(&path)? } else { let mut sec_levels = SecurityLevelDefinitions::default(); @@ -105,10 +108,10 @@ impl<'a> SecurityLevelEditor<'a> { ]; sec_levels }; - let command_arc = Arc::new(Mutex::new(sec_levels.levels.clone())); - let scroll_state = ScrollbarState::default().content_length(sec_levels.levels.len()); - let content_length = sec_levels.levels.len(); - let cmd2 = command_arc.clone(); + let sec_levels = Arc::new(Mutex::new(sec_levels_orig.clone())); + let scroll_state = ScrollbarState::default().content_length(sec_levels_orig.levels.len()); + let content_length = sec_levels_orig.levels.len(); + let cmd2 = sec_levels.clone(); let insert_table = InsertTable { scroll_state, table_state: TableState::default().with_selected(0), @@ -126,14 +129,14 @@ impl<'a> SecurityLevelEditor<'a> { }), content_length, }; - Ok(Self { path: path.clone(), - door_list: sec_levels, + sec_levels_orig, insert_table, - sec_levels: command_arc, + sec_levels, edit_config: None, edit_config_state: ConfigMenuState::default(), + save_dialog: None, }) } @@ -167,23 +170,30 @@ impl<'a> SecurityLevelEditor<'a> { impl<'a> Page for SecurityLevelEditor<'a> { fn render(&mut self, frame: &mut Frame, area: Rect) { Clear.render(area, frame.buffer_mut()); + let block = Block::new() .title_alignment(Alignment::Center) - .title(Title::from(Span::from(" Edit Security Levels ").style(get_tui_theme().dialog_box_title))) + .title(Title::from( + Span::from(get_text("sec_level_editor_title")).style(get_tui_theme().dialog_box_title), + )) .style(get_tui_theme().dialog_box) .padding(Padding::new(2, 2, 1, 1)) .borders(Borders::ALL) - .border_type(BorderType::Double); + .border_set(icy_board_tui::BORDER_SET) + .title_bottom(Span::styled(get_text("icb_setup_key_conf_list_help"), get_tui_theme().key_binding)); block.render(area, frame.buffer_mut()); let area = area.inner(Margin { horizontal: 1, vertical: 1 }); self.display_insert_table(frame, &area); if let Some(edit_config) = &mut self.edit_config { - let area = area.inner(Margin { vertical: 2, horizontal: 3 }); + let mut area = area.inner(Margin { vertical: 2, horizontal: 3 }); + area.height += 1; Clear.render(area, frame.buffer_mut()); let block = Block::new() .title_alignment(Alignment::Center) - .title(Title::from(Span::from(" Edit Security Level ").style(get_tui_theme().dialog_box_title))) + .title(Title::from( + Span::from(get_text("sec_level_editor_editor")).style(get_tui_theme().dialog_box_title), + )) .style(get_tui_theme().dialog_box) .padding(Padding::new(2, 2, 1, 1)) .borders(Borders::ALL) @@ -198,104 +208,35 @@ impl<'a> Page for SecurityLevelEditor<'a> { .text_field_state .set_cursor_position(frame); } + if let Some(save_changes) = &self.save_dialog { + save_changes.render(frame, area); + } } fn handle_key_press(&mut self, key: KeyEvent) -> PageMessage { + if self.save_dialog.is_some() { + let res = self.save_dialog.as_mut().unwrap().handle_key_press(key); + return match res { + icy_board_tui::save_changes_dialog::SaveChangesMessage::Cancel => { + self.save_dialog = None; + PageMessage::None + } + icy_board_tui::save_changes_dialog::SaveChangesMessage::Close => PageMessage::Close, + icy_board_tui::save_changes_dialog::SaveChangesMessage::Save => { + if let Some(parent) = self.path.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent).unwrap(); + } + } + self.sec_levels.lock().unwrap().save(&self.path).unwrap(); + PageMessage::Close + } + icy_board_tui::save_changes_dialog::SaveChangesMessage::None => PageMessage::None, + }; + } if let Some(edit_config) = &mut self.edit_config { match key.code { KeyCode::Esc => { - /* - let Some(selected_item) = self.insert_table.table_state.selected() else { - return true; - }; - for item in edit_config.iter() { - match item.id.as_str() { - "security" => { - if let ListValue::U32(value, _, _) = item.value { - self.sec_levels.lock().unwrap()[selected_item].security = value as u8; - } - } - "description" => { - if let ListValue::Text(_, value) = &item.value { - self.sec_levels.lock().unwrap()[selected_item].description = value.to_string(); - } - } - "password" => { - if let ListValue::Text(_, value) = &item.value { - self.sec_levels.lock().unwrap()[selected_item].password = value.to_string(); - } - } - "time_per_day" => { - if let ListValue::U32(value, _, _) = item.value { - self.sec_levels.lock().unwrap()[selected_item].time_per_day = value as u32; - } - } - "daily_file_kb_limit" => { - if let ListValue::U32(value, _, _) = item.value { - self.sec_levels.lock().unwrap()[selected_item].daily_file_kb_limit = value as u64; - } - } - - "uldl_ratio" => { - if let ListValue::U32(value, _, _) = item.value { - self.sec_levels.lock().unwrap()[selected_item].uldl_ratio = value as u32; - } - } - "uldl_kb_ratio" => { - if let ListValue::U32(value, _, _) = item.value { - self.sec_levels.lock().unwrap()[selected_item].uldl_kb_ratio = value as u32; - } - } - "file_limit" => { - if let ListValue::U32(value, _, _) = item.value { - self.sec_levels.lock().unwrap()[selected_item].file_limit = value as u64; - } - } - "file_kb_limit" => { - if let ListValue::U32(value, _, _) = item.value { - self.sec_levels.lock().unwrap()[selected_item].file_kb_limit = value as u64; - } - } - "file_credit" => { - if let ListValue::U32(value, _, _) = item.value { - self.sec_levels.lock().unwrap()[selected_item].file_credit = value as u64; - } - } - "file_kb_credit" => { - if let ListValue::U32(value, _, _) = item.value { - self.sec_levels.lock().unwrap()[selected_item].file_kb_credit = value as u64; - } - } - "enforce_time_limit" => { - if let ListValue::Bool(value) = item.value { - self.sec_levels.lock().unwrap()[selected_item].enforce_time_limit = value; - } - } - "allow_alias" => { - if let ListValue::Bool(value) = item.value { - self.sec_levels.lock().unwrap()[selected_item].allow_alias = value; - } - } - "enforce_read_mail" => { - if let ListValue::Bool(value) = item.value { - self.sec_levels.lock().unwrap()[selected_item].enforce_read_mail = value; - } - } - "is_demo_account" => { - if let ListValue::Bool(value) = item.value { - self.sec_levels.lock().unwrap()[selected_item].is_demo_account = value; - } - } - "is_enabled" => { - if let ListValue::Bool(value) = item.value { - self.sec_levels.lock().unwrap()[selected_item].is_enabled = value; - } - } - _ => { - panic!("Unknown item: {}", item.id); - } - } - }*/ self.edit_config = None; return PageMessage::None; } @@ -308,14 +249,15 @@ impl<'a> Page for SecurityLevelEditor<'a> { match key.code { KeyCode::Esc => { - self.door_list.levels.clear(); - self.door_list.levels.append(&mut self.sec_levels.lock().unwrap()); - self.door_list.save(&self.path).unwrap(); - return PageMessage::Close; + if self.sec_levels_orig == self.sec_levels.lock().unwrap().clone() { + return PageMessage::Close; + } + self.save_dialog = Some(SaveChangesDialog::new()); + return PageMessage::None; } _ => match key.code { - KeyCode::Char('1') => self.move_up(), - KeyCode::Char('2') => self.move_down(), + KeyCode::PageUp => self.move_up(), + KeyCode::PageDown => self.move_down(), KeyCode::Insert => { self.sec_levels.lock().unwrap().push(SecurityLevel { @@ -359,45 +301,140 @@ impl<'a> Page for SecurityLevelEditor<'a> { return PageMessage::None; }; self.edit_config = Some(ConfigMenu { - obj: 0, + obj: (selected_item, self.sec_levels.clone()), entry: vec![ - ConfigEntry::Item(ListItem::new("Security".to_string(), ListValue::U32(action.security as u32, 0, 255)).with_label_width(16)), ConfigEntry::Item( - ListItem::new("Description".to_string(), ListValue::Text(30, action.description.clone())).with_label_width(16), + ListItem::new(get_text("sec_level_editor_security"), ListValue::U32(action.security as u32, 0, 255)) + .with_label_width(16) + .with_update_u32_value(&|(i, list): &(usize, Arc>), value: u32| { + list.lock().unwrap()[*i].security = value as u8; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("sec_level_editor_description"), ListValue::Text(30, action.description.clone())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + list.lock().unwrap()[*i].description = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("sec_level_editor_password"), ListValue::Text(30, action.password.clone())) + .with_label_width(16) + .with_update_text_value(&|(i, list): &(usize, Arc>), value: String| { + list.lock().unwrap()[*i].password = value; + }), + ), + ConfigEntry::Item( + ListItem::new( + get_text("sec_level_editor_time_per_day"), + ListValue::U32(action.time_per_day as u32, 0, u32::MAX), + ) + .with_label_width(16) + .with_update_u32_value( + &|(i, list): &(usize, Arc>), value: u32| { + list.lock().unwrap()[*i].time_per_day = value; + }, + ), + ), + ConfigEntry::Item( + ListItem::new( + get_text("sec_level_editor_daily_bytes"), + ListValue::U32(action.daily_file_kb_limit as u32, 0, u32::MAX), + ) + .with_label_width(16) + .with_update_u32_value( + &|(i, list): &(usize, Arc>), value: u32| { + list.lock().unwrap()[*i].daily_file_kb_limit = value as u64; + }, + ), + ), + ConfigEntry::Item( + ListItem::new(get_text("sec_level_editor_file_ratio"), ListValue::U32(action.uldl_ratio as u32, 0, u32::MAX)) + .with_label_width(16) + .with_update_u32_value(&|(i, list): &(usize, Arc>), value: u32| { + list.lock().unwrap()[*i].uldl_ratio = value; + }), + ), + ConfigEntry::Item( + ListItem::new( + get_text("sec_level_editor_byte_ratio"), + ListValue::U32(action.uldl_kb_ratio as u32, 0, u32::MAX), + ) + .with_label_width(16) + .with_update_u32_value( + &|(i, list): &(usize, Arc>), value: u32| { + list.lock().unwrap()[*i].uldl_kb_ratio = value; + }, + ), ), - ConfigEntry::Item(ListItem::new("Password".to_string(), ListValue::Text(30, action.password.clone())).with_label_width(16)), ConfigEntry::Item( - ListItem::new("Time".to_string(), ListValue::U32(action.time_per_day as u32, 0, u32::MAX)).with_label_width(16), + ListItem::new(get_text("sec_level_editor_file_limit"), ListValue::U32(action.file_limit as u32, 0, u32::MAX)) + .with_label_width(16) + .with_update_u32_value(&|(i, list): &(usize, Arc>), value: u32| { + list.lock().unwrap()[*i].file_limit = value as u64; + }), ), ConfigEntry::Item( - ListItem::new("Daily KBytes".to_string(), ListValue::U32(action.daily_file_kb_limit as u32, 0, u32::MAX)) - .with_label_width(16), + ListItem::new(get_text("sec_level_editor_kb_limit"), ListValue::U32(action.file_kb_limit as u32, 0, u32::MAX)) + .with_label_width(16) + .with_update_u32_value(&|(i, list): &(usize, Arc>), value: u32| { + list.lock().unwrap()[*i].file_kb_limit = value as u64; + }), ), ConfigEntry::Item( - ListItem::new("File Ratio".to_string(), ListValue::U32(action.uldl_ratio as u32, 0, u32::MAX)).with_label_width(16), + ListItem::new(get_text("sec_level_editor_file_credit"), ListValue::U32(action.file_credit as u32, 0, u32::MAX)) + .with_label_width(16) + .with_update_u32_value(&|(i, list): &(usize, Arc>), value: u32| { + list.lock().unwrap()[*i].file_credit = value as u64; + }), ), ConfigEntry::Item( - ListItem::new("Byte Ratio".to_string(), ListValue::U32(action.uldl_kb_ratio as u32, 0, u32::MAX)).with_label_width(16), + ListItem::new( + get_text("sec_level_editor_kb_credit"), + ListValue::U32(action.file_kb_credit as u32, 0, u32::MAX), + ) + .with_label_width(16) + .with_update_u32_value( + &|(i, list): &(usize, Arc>), value: u32| { + list.lock().unwrap()[*i].file_kb_credit = value as u64; + }, + ), ), ConfigEntry::Item( - ListItem::new("File Limit".to_string(), ListValue::U32(action.file_limit as u32, 0, u32::MAX)).with_label_width(16), + ListItem::new(get_text("sec_level_editor_enforce_time"), ListValue::Bool(action.enforce_time_limit)) + .with_label_width(16) + .with_update_bool_value(&|(i, list): &(usize, Arc>), value: bool| { + list.lock().unwrap()[*i].enforce_time_limit = value; + }), ), ConfigEntry::Item( - ListItem::new("KByte Limit".to_string(), ListValue::U32(action.file_kb_limit as u32, 0, u32::MAX)).with_label_width(16), + ListItem::new(get_text("sec_level_editor_allow_alias"), ListValue::Bool(action.allow_alias)) + .with_label_width(16) + .with_update_bool_value(&|(i, list): &(usize, Arc>), value: bool| { + list.lock().unwrap()[*i].allow_alias = value; + }), ), ConfigEntry::Item( - ListItem::new("File Credit".to_string(), ListValue::U32(action.file_credit as u32, 0, u32::MAX)).with_label_width(16), + ListItem::new(get_text("sec_level_force_read_mail"), ListValue::Bool(action.enforce_read_mail)) + .with_label_width(16) + .with_update_bool_value(&|(i, list): &(usize, Arc>), value: bool| { + list.lock().unwrap()[*i].enforce_read_mail = value; + }), ), ConfigEntry::Item( - ListItem::new("KByte Credit".to_string(), ListValue::U32(action.file_kb_credit as u32, 0, u32::MAX)).with_label_width(16), + ListItem::new(get_text("sec_level_demo_acc"), ListValue::Bool(action.is_demo_account)) + .with_label_width(16) + .with_update_bool_value(&|(i, list): &(usize, Arc>), value: bool| { + list.lock().unwrap()[*i].is_demo_account = value; + }), ), ConfigEntry::Item( - ListItem::new("Enforce Time Limit".to_string(), ListValue::Bool(action.enforce_time_limit)).with_label_width(16), + ListItem::new(get_text("sec_level_enable_acc"), ListValue::Bool(action.is_enabled)) + .with_label_width(16) + .with_update_bool_value(&|(i, list): &(usize, Arc>), value: bool| { + list.lock().unwrap()[*i].is_enabled = value; + }), ), - ConfigEntry::Item(ListItem::new("Allow Alias".to_string(), ListValue::Bool(action.allow_alias)).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Force Read Mail".to_string(), ListValue::Bool(action.enforce_read_mail)).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Demo Account".to_string(), ListValue::Bool(action.is_demo_account)).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Enable Account".to_string(), ListValue::Bool(action.is_enabled)).with_label_width(16)), ], }); } else { diff --git a/crates/icbsetup/src/editors/surveys.rs b/crates/icbsetup/src/editors/surveys.rs index c7b53fa3..dc1a428e 100644 --- a/crates/icbsetup/src/editors/surveys.rs +++ b/crates/icbsetup/src/editors/surveys.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, path::PathBuf, sync::{Arc, Mutex}, }; @@ -6,6 +7,7 @@ use std::{ use crossterm::event::{KeyCode, KeyEvent}; use icy_board_engine::{ icy_board::{ + security_expr::SecurityExpression, surveys::{Survey, SurveyList}, IcyBoard, IcyBoardSerializer, }, @@ -13,7 +15,9 @@ use icy_board_engine::{ }; use icy_board_tui::{ config_menu::{ConfigEntry, ConfigMenu, ConfigMenuState, ListItem, ListValue}, + get_text, get_text_args, insert_table::InsertTable, + save_changes_dialog::SaveChangesDialog, tab_page::{Page, PageMessage}, theme::get_tui_theme, }; @@ -26,26 +30,31 @@ use ratatui::{ pub struct SurveyEditor<'a> { path: std::path::PathBuf, - survey_list: SurveyList, - + survey_list_orig: SurveyList, insert_table: InsertTable<'a>, - surveys: Arc>>, + survey_list: Arc>, edit_config_state: ConfigMenuState, - edit_config: Option>, + edit_config: Option>)>>, + save_dialog: Option, } impl<'a> SurveyEditor<'a> { pub(crate) fn new(path: &std::path::PathBuf) -> Res { - let surveys = if path.exists() { SurveyList::load(&path)? } else { SurveyList::default() }; - let command_arc = Arc::new(Mutex::new(surveys.surveys.clone())); - let scroll_state = ScrollbarState::default().content_length(surveys.surveys.len()); - let content_length = surveys.surveys.len(); - let cmd2 = command_arc.clone(); + let survey_list_orig = if path.exists() { SurveyList::load(&path)? } else { SurveyList::default() }; + let survey_list = Arc::new(Mutex::new(survey_list_orig.clone())); + let scroll_state = ScrollbarState::default().content_length(survey_list_orig.surveys.len()); + let content_length = survey_list_orig.len(); + let cmd2 = survey_list.clone(); let insert_table = InsertTable { scroll_state, table_state: TableState::default().with_selected(0), - headers: vec!["".to_string(), "Question ".to_string(), "Answer".to_string()], + + headers: vec![ + "".to_string(), + format!("{:<30}", get_text("survey_editor_editor_header_question")), + format!("{:<30}", get_text("survey_editor_editor_header_answer")), + ], get_content: Box::new(move |_table, i, j| { if *i >= cmd2.lock().unwrap().len() { return Line::from("".to_string()); @@ -59,14 +68,14 @@ impl<'a> SurveyEditor<'a> { }), content_length, }; - Ok(Self { path: path.clone(), - survey_list: surveys, + survey_list_orig, insert_table, - surveys: command_arc, + survey_list, edit_config: None, edit_config_state: ConfigMenuState::default(), + save_dialog: None, }) } @@ -79,7 +88,7 @@ impl<'a> SurveyEditor<'a> { fn move_up(&mut self) { if let Some(selected) = self.insert_table.table_state.selected() { if selected > 0 { - let mut levels = self.surveys.lock().unwrap(); + let mut levels = self.survey_list.lock().unwrap(); levels.swap(selected, selected - 1); self.insert_table.table_state.select(Some(selected - 1)); } @@ -88,8 +97,8 @@ impl<'a> SurveyEditor<'a> { fn move_down(&mut self) { if let Some(selected) = self.insert_table.table_state.selected() { - if selected + 1 < self.surveys.lock().unwrap().len() { - let mut levels = self.surveys.lock().unwrap(); + if selected + 1 < self.survey_list.lock().unwrap().len() { + let mut levels = self.survey_list.lock().unwrap(); levels.swap(selected, selected + 1); self.insert_table.table_state.select(Some(selected + 1)); } @@ -100,23 +109,29 @@ impl<'a> SurveyEditor<'a> { impl<'a> Page for SurveyEditor<'a> { fn render(&mut self, frame: &mut Frame, area: Rect) { Clear.render(area, frame.buffer_mut()); + let conference_name = crate::tabs::conferences::get_cur_conference_name(); + let title = get_text_args("surveys_editor_title", HashMap::from([("conference".to_string(), conference_name)])); + let block = Block::new() .title_alignment(Alignment::Center) - .title(Title::from(Span::from(" Surveys ").style(get_tui_theme().dialog_box_title))) + .title(Title::from(Span::from(title).style(get_tui_theme().dialog_box_title))) .style(get_tui_theme().dialog_box) .padding(Padding::new(2, 2, 1, 1)) .borders(Borders::ALL) - .border_type(BorderType::Double); + .border_set(icy_board_tui::BORDER_SET) + .title_bottom(Span::styled(get_text("icb_setup_key_conf_list_help"), get_tui_theme().key_binding)); block.render(area, frame.buffer_mut()); let area = area.inner(Margin { horizontal: 1, vertical: 1 }); self.display_insert_table(frame, &area); if let Some(edit_config) = &mut self.edit_config { - let area = area.inner(Margin { vertical: 9, horizontal: 3 }); + let area = area.inner(Margin { vertical: 8, horizontal: 3 }); Clear.render(area, frame.buffer_mut()); let block = Block::new() .title_alignment(Alignment::Center) - .title(Title::from(Span::from(" Edit Survey ").style(get_tui_theme().dialog_box_title))) + .title(Title::from( + Span::from(get_text("survey_editor_editor")).style(get_tui_theme().dialog_box_title), + )) .style(get_tui_theme().dialog_box) .padding(Padding::new(2, 2, 1, 1)) .borders(Borders::ALL) @@ -131,40 +146,36 @@ impl<'a> Page for SurveyEditor<'a> { .text_field_state .set_cursor_position(frame); } + if let Some(save_changes) = &self.save_dialog { + save_changes.render(frame, area); + } } fn handle_key_press(&mut self, key: KeyEvent) -> PageMessage { + if self.save_dialog.is_some() { + let res = self.save_dialog.as_mut().unwrap().handle_key_press(key); + return match res { + icy_board_tui::save_changes_dialog::SaveChangesMessage::Cancel => { + self.save_dialog = None; + PageMessage::None + } + icy_board_tui::save_changes_dialog::SaveChangesMessage::Close => PageMessage::Close, + icy_board_tui::save_changes_dialog::SaveChangesMessage::Save => { + if let Some(parent) = self.path.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent).unwrap(); + } + } + self.survey_list.lock().unwrap().save(&self.path).unwrap(); + PageMessage::Close + } + icy_board_tui::save_changes_dialog::SaveChangesMessage::None => PageMessage::None, + }; + } + if let Some(edit_config) = &mut self.edit_config { match key.code { KeyCode::Esc => { - /* - let Some(selected_item) = self.insert_table.table_state.selected() else { - return true; - }; - for item in edit_config.iter() { - match item.id.as_str() { - "survey" => { - if let ListValue::Path(path) = &item.value { - self.surveys.lock().unwrap()[selected_item].survey_file = path.clone(); - } - } - "answer" => { - if let ListValue::Path(path) = &item.value { - self.surveys.lock().unwrap()[selected_item].answer_file = path.clone(); - } - } - "security" => { - if let ListValue::Text(_, text) = &item.value { - if let Ok(expr) = SecurityExpression::from_str(text) { - self.surveys.lock().unwrap()[selected_item].required_security = expr; - } - } - } - _ => { - panic!("Unknown item: {}", item.id); - } - } - }*/ self.edit_config = None; return PageMessage::None; } @@ -177,23 +188,24 @@ impl<'a> Page for SurveyEditor<'a> { match key.code { KeyCode::Esc => { - self.survey_list.surveys.clear(); - self.survey_list.surveys.append(&mut self.surveys.lock().unwrap()); - self.survey_list.save(&self.path).unwrap(); - return PageMessage::Close; + if self.survey_list_orig == self.survey_list.lock().unwrap().clone() { + return PageMessage::Close; + } + self.save_dialog = Some(SaveChangesDialog::new()); + return PageMessage::None; } _ => match key.code { - KeyCode::Char('1') => self.move_up(), - KeyCode::Char('2') => self.move_down(), + KeyCode::PageUp => self.move_up(), + KeyCode::PageDown => self.move_down(), KeyCode::Insert => { - self.surveys.lock().unwrap().push(Survey::default()); + self.survey_list.lock().unwrap().push(Survey::default()); self.insert_table.content_length += 1; } KeyCode::Delete => { if let Some(selected_item) = self.insert_table.table_state.selected() { - if selected_item < self.surveys.lock().unwrap().len() { - self.surveys.lock().unwrap().remove(selected_item); + if selected_item < self.survey_list.lock().unwrap().len() { + self.survey_list.lock().unwrap().remove(selected_item); self.insert_table.content_length -= 1; } } @@ -203,17 +215,38 @@ impl<'a> Page for SurveyEditor<'a> { self.edit_config_state = ConfigMenuState::default(); if let Some(selected_item) = self.insert_table.table_state.selected() { - let cmd = self.surveys.lock().unwrap(); + let cmd = self.survey_list.lock().unwrap(); let Some(action) = cmd.get(selected_item) else { return PageMessage::None; }; self.edit_config = Some(ConfigMenu { - obj: 0, + obj: (selected_item, self.survey_list.clone()), entry: vec![ - ConfigEntry::Item(ListItem::new("Survey File".to_string(), ListValue::Path(action.survey_file.clone())).with_label_width(16)), - ConfigEntry::Item(ListItem::new("Answer File".to_string(), ListValue::Path(action.answer_file.clone())).with_label_width(16)), ConfigEntry::Item( - ListItem::new("Security".to_string(), ListValue::Text(25, action.required_security.to_string())).with_label_width(16), + ListItem::new(get_text("survey_editor_editor_file"), ListValue::Path(action.survey_file.clone())) + .with_label_width(16) + .with_update_path_value(&|(i, list): &(usize, Arc>), value: PathBuf| { + list.lock().unwrap()[*i].survey_file = value; + }), + ), + ConfigEntry::Item( + ListItem::new(get_text("survey_editor_editor_answer_file"), ListValue::Path(action.answer_file.clone())) + .with_label_width(16) + .with_update_path_value(&|(i, list): &(usize, Arc>), value: PathBuf| { + list.lock().unwrap()[*i].answer_file = value; + }), + ), + ConfigEntry::Item( + ListItem::new( + get_text("survey_editor_editor_security"), + ListValue::Security(action.required_security.clone(), action.required_security.to_string()), + ) + .with_label_width(16) + .with_update_sec_value( + &|(i, list): &(usize, Arc>), value: SecurityExpression| { + list.lock().unwrap()[*i].required_security = value; + }, + ), ), ], }); diff --git a/crates/icbsetup/src/tabs/general/conferences/conference_editor.rs b/crates/icbsetup/src/tabs/general/conferences/conference_editor.rs index 0a595437..400f081d 100644 --- a/crates/icbsetup/src/tabs/general/conferences/conference_editor.rs +++ b/crates/icbsetup/src/tabs/general/conferences/conference_editor.rs @@ -24,6 +24,13 @@ pub struct ConferenceEditor { menu: ConfigMenu<(usize, Arc>)>, } +static mut CUR_CONFERENCE: String = String::new(); + +#[allow(static_mut_refs)] +pub fn get_cur_conference_name() -> String { + unsafe { CUR_CONFERENCE.clone() } +} + impl ConferenceEditor { pub fn new(icy_board: Arc>, num_conf: usize) -> Self { let conf_name = get_text_args("conf_name", HashMap::from([("number".to_string(), num_conf.to_string())])); @@ -31,6 +38,9 @@ impl ConferenceEditor { let menu: ConfigMenu<(usize, Arc>)> = { let ib = icy_board.lock().unwrap(); let conf = ib.conferences.get(num_conf).unwrap(); + unsafe { + CUR_CONFERENCE = if conf.name.is_empty() { conf_name.clone() } else { conf.name.clone() }; + } let name_block_width = 27; @@ -264,7 +274,7 @@ impl ConferenceEditor { ConfigEntry::Item( ListItem::new("".to_string(), ListValue::Path(conf.area_file.clone())) .with_edit_width(rpath_width) - .with_path_editor(Box::new(crate::editors::messages::edit_areas)) + .with_path_editor(Box::new(crate::editors::areas::edit_areas)) .with_update_path_value(&|board: &(usize, Arc>), value: PathBuf| { let mut ib = board.1.lock().unwrap(); ib.conferences[board.0].area_file = value; diff --git a/crates/icbsetup/src/tabs/general/mod.rs b/crates/icbsetup/src/tabs/general/mod.rs index 14544f07..4d9a7971 100644 --- a/crates/icbsetup/src/tabs/general/mod.rs +++ b/crates/icbsetup/src/tabs/general/mod.rs @@ -21,7 +21,7 @@ use ratatui::{layout::Rect, text::Text, Frame}; use subscription_information::SubscriptionInformation; mod accounting; mod board_configuration; -mod conferences; +pub mod conferences; mod configuration_options; mod connection_info; mod event_setup; diff --git a/crates/icy_board_engine/src/icy_board/doors/mod.rs b/crates/icy_board_engine/src/icy_board/doors/mod.rs index 00e5fa44..75358fc7 100644 --- a/crates/icy_board_engine/src/icy_board/doors/mod.rs +++ b/crates/icy_board_engine/src/icy_board/doors/mod.rs @@ -32,19 +32,19 @@ mod tribbs_sys; const DOOR_COM_PORT: u8 = 1; const DOOR_BPS_RATE: u32 = 57600; -#[derive(Clone, Serialize, Deserialize, Default)] +#[derive(Clone, Serialize, Deserialize, Default, PartialEq)] pub struct BBSLink { pub system_code: String, pub auth_code: String, pub sheme_code: String, } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize, PartialEq)] pub enum DoorServerAccount { BBSLink(BBSLink), } -#[derive(Clone, Serialize, Deserialize, Default)] +#[derive(Clone, Serialize, Deserialize, Default, PartialEq)] pub enum DoorType { #[default] Local, @@ -111,7 +111,7 @@ pub enum DropFile { } #[serde_as] -#[derive(Clone, Serialize, Deserialize, Default)] +#[derive(Clone, Serialize, Deserialize, Default, PartialEq)] pub struct Door { pub name: String, pub description: String, @@ -209,7 +209,7 @@ lazy_static::lazy_static! { pub static ref HAS_ACCESS: unicase::Ascii = unicase::Ascii::new("HasAccess".to_string()); } -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Default, Clone, PartialEq)] pub struct DoorList { #[serde(rename = "account")] pub accounts: Vec, diff --git a/crates/icy_board_engine/src/icy_board/file_directory.rs b/crates/icy_board_engine/src/icy_board/file_directory.rs index 0d7bbb15..99b668c7 100644 --- a/crates/icy_board_engine/src/icy_board/file_directory.rs +++ b/crates/icy_board_engine/src/icy_board/file_directory.rs @@ -17,7 +17,7 @@ use serde_with::{serde_as, DisplayFromStr}; use super::{is_false, security_expr::SecurityExpression, user_base::Password, IcyBoardError, IcyBoardSerializer, PCBoardRecordImporter}; -#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug)] +#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, PartialEq)] pub enum SortOrder { NoSort, #[default] @@ -57,7 +57,7 @@ pub enum SortDirection { /// A survey is a question and answer pair. /// PCBoard calles them "Questionnairies" but we call them surveys. #[serde_as] -#[derive(Clone, Serialize, Deserialize, Default)] +#[derive(Clone, Serialize, Deserialize, Default, PartialEq)] pub struct FileDirectory { pub name: String, pub path: PathBuf, @@ -72,18 +72,11 @@ pub struct FileDirectory { #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub has_new_files: bool, - #[serde(default)] - #[serde(skip_serializing_if = "is_false")] - pub is_readonly: bool, #[serde(default)] #[serde(skip_serializing_if = "is_false")] pub is_free: bool, - #[serde(default)] - #[serde(skip_serializing_if = "is_false")] - pub allow_ul_pwd: bool, - #[serde(default)] #[serde(skip_serializing_if = "SecurityExpression::is_empty")] #[serde_as(as = "DisplayFromStr")] @@ -93,14 +86,9 @@ pub struct FileDirectory { #[serde(skip_serializing_if = "SecurityExpression::is_empty")] #[serde_as(as = "DisplayFromStr")] pub download_security: SecurityExpression, - - #[serde(default)] - #[serde(skip_serializing_if = "SecurityExpression::is_empty")] - #[serde_as(as = "DisplayFromStr")] - pub upload_security: SecurityExpression, } -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Default, Clone, PartialEq)] pub struct DirectoryList { #[serde(rename = "area")] areas: Vec, @@ -184,12 +172,9 @@ impl PCBoardRecordImporter for DirectoryList { password: Password::default(), has_new_files: false, - is_readonly: false, is_free: false, - allow_ul_pwd: false, list_security: SecurityExpression::default(), download_security: SecurityExpression::default(), - upload_security: SecurityExpression::default(), }) } } diff --git a/crates/icy_board_engine/src/icy_board/language.rs b/crates/icy_board_engine/src/icy_board/language.rs index f3cdedff..cb9efcde 100644 --- a/crates/icy_board_engine/src/icy_board/language.rs +++ b/crates/icy_board_engine/src/icy_board/language.rs @@ -1,11 +1,14 @@ -use std::path::Path; +use std::{ + ops::{Deref, DerefMut}, + path::Path, +}; use crate::Res; use serde::{Deserialize, Serialize}; use super::{IcyBoardSerializer, PCBoardImport, PCBoardTextImport}; -#[derive(Clone, Serialize, Deserialize, Default)] +#[derive(Clone, Serialize, Deserialize, Default, PartialEq)] pub struct Language { pub description: String, pub locale: String, @@ -14,21 +17,28 @@ pub struct Language { pub no_char: char, } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize, PartialEq)] pub struct SupportedLanguages { pub date_formats: Vec<(String, String)>, #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] #[serde(rename = "language")] - pub languages: Vec, + languages: Vec, } -impl SupportedLanguages { - pub fn len(&self) -> usize { - self.languages.len() + +impl Deref for SupportedLanguages { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.languages } } +impl DerefMut for SupportedLanguages { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.languages + } +} impl Default for SupportedLanguages { fn default() -> Self { Self { diff --git a/crates/icy_board_engine/src/icy_board/message_area.rs b/crates/icy_board_engine/src/icy_board/message_area.rs index 56693c41..8a946475 100644 --- a/crates/icy_board_engine/src/icy_board/message_area.rs +++ b/crates/icy_board_engine/src/icy_board/message_area.rs @@ -15,7 +15,7 @@ use crate::{ use super::{security_expr::SecurityExpression, IcyBoardSerializer}; #[serde_as] -#[derive(Default, Clone, Serialize, Deserialize)] +#[derive(Default, Clone, Serialize, Deserialize, PartialEq)] pub struct MessageArea { pub name: String, pub filename: PathBuf, @@ -38,7 +38,7 @@ pub struct MessageArea { pub req_level_to_save_attach: SecurityExpression, } -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Default, Clone, PartialEq)] pub struct AreaList { #[serde(rename = "area")] areas: Vec, diff --git a/crates/icy_board_engine/src/icy_board/sec_levels.rs b/crates/icy_board_engine/src/icy_board/sec_levels.rs index 7a5faa8f..ca93b8b2 100644 --- a/crates/icy_board_engine/src/icy_board/sec_levels.rs +++ b/crates/icy_board_engine/src/icy_board/sec_levels.rs @@ -2,9 +2,10 @@ use super::IcyBoardSerializer; use super::{is_false, is_null_32, is_null_64, is_null_8, PCBoardImport, PCBoardTextImport}; use crate::Res; use serde::{Deserialize, Serialize}; +use std::ops::{Deref, DerefMut}; use std::path::Path; -#[derive(Clone, Serialize, Deserialize, Default)] +#[derive(Clone, Serialize, Deserialize, Default, PartialEq)] pub struct SecurityLevel { #[serde(default)] #[serde(skip_serializing_if = "String::is_empty")] @@ -81,7 +82,7 @@ pub struct SecurityLevel { pub is_enabled: bool, } -#[derive(Serialize, Deserialize, Default)] +#[derive(Serialize, Deserialize, Default, Clone, PartialEq)] pub struct SecurityLevelDefinitions { #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] @@ -89,6 +90,19 @@ pub struct SecurityLevelDefinitions { pub levels: Vec, } +impl Deref for SecurityLevelDefinitions { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.levels + } +} + +impl DerefMut for SecurityLevelDefinitions { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.levels + } +} + impl SecurityLevelDefinitions { pub fn export_pcboard(&self, file: &std::path::PathBuf) -> Res<()> { let mut data = String::new(); diff --git a/crates/icy_board_engine/src/icy_board/security_expr.rs b/crates/icy_board_engine/src/icy_board/security_expr.rs index dc2bbd7c..43eff1c0 100644 --- a/crates/icy_board_engine/src/icy_board/security_expr.rs +++ b/crates/icy_board_engine/src/icy_board/security_expr.rs @@ -75,7 +75,7 @@ pub enum SecurityExpression { impl Default for SecurityExpression { fn default() -> Self { - SecurityExpression::Constant(Value::Bool(true)) + SecurityExpression::Constant(Value::Integer(0)) } } diff --git a/crates/icy_board_engine/src/icy_board/state/user_commands/pcb/lang_set_language.rs b/crates/icy_board_engine/src/icy_board/state/user_commands/pcb/lang_set_language.rs index 5feba4e8..86e1d63e 100644 --- a/crates/icy_board_engine/src/icy_board/state/user_commands/pcb/lang_set_language.rs +++ b/crates/icy_board_engine/src/icy_board/state/user_commands/pcb/lang_set_language.rs @@ -34,7 +34,7 @@ impl IcyBoardState { self.new_line().await?; let l = self.get_board().await.languages.clone(); let mut cur_lang_str = String::new(); - for (i, lang) in l.languages.iter().enumerate() { + for (i, lang) in l.iter().enumerate() { if lang.extension == cur_language { cur_lang_str = format!("{}", i + 1); languages.push(format!("=> ({}) {}", i + 1, lang.description)); @@ -65,11 +65,11 @@ impl IcyBoardState { return Ok(language); } if let Ok(number) = language.parse::() { - if number > 0 && number <= l.languages.len() { + if number > 0 && number <= l.len() { if number == 1 { self.display_text(IceText::LanguageActive, display_flags::NEWLINE).await?; } - return Ok(l.languages[number - 1].extension.clone()); + return Ok(l[number - 1].extension.clone()); } } self.display_text(IceText::LanguageNotAvailable, display_flags::NEWLINE).await?; diff --git a/crates/icy_board_engine/src/icy_board/surveys.rs b/crates/icy_board_engine/src/icy_board/surveys.rs index 1dd88e3a..f6e31e2b 100644 --- a/crates/icy_board_engine/src/icy_board/surveys.rs +++ b/crates/icy_board_engine/src/icy_board/surveys.rs @@ -12,7 +12,7 @@ use super::{security_expr::SecurityExpression, IcyBoardSerializer, PCBoardRecord /// A survey is a question and answer pair. /// PCBoard calles them "Questionnairies" but we call them surveys. #[serde_as] -#[derive(Clone, Serialize, Deserialize, Default)] +#[derive(Clone, Serialize, Deserialize, Default, PartialEq)] pub struct Survey { pub survey_file: PathBuf, pub answer_file: PathBuf, @@ -23,7 +23,7 @@ pub struct Survey { pub required_security: SecurityExpression, } -#[derive(Serialize, Deserialize, Default)] +#[derive(Clone, Serialize, Deserialize, Default, PartialEq)] pub struct SurveyList { #[serde(rename = "survey")] pub surveys: Vec, diff --git a/crates/icy_board_engine/src/vm/statements/predefined_procedures.rs b/crates/icy_board_engine/src/vm/statements/predefined_procedures.rs index 560e919f..798623f9 100644 --- a/crates/icy_board_engine/src/vm/statements/predefined_procedures.rs +++ b/crates/icy_board_engine/src/vm/statements/predefined_procedures.rs @@ -1303,7 +1303,7 @@ pub async fn ayjtfiles(vm: &mut VirtualMachine<'_>, args: &[PPEExpr]) -> Res<()> pub async fn lang(vm: &mut VirtualMachine<'_>, args: &[PPEExpr]) -> Res<()> { let language = vm.eval_expr(&args[0]).await?.as_int(); - let lang = if let Some(lang) = vm.icy_board_state.board.lock().await.languages.languages.get(language as usize) { + let lang = if let Some(lang) = vm.icy_board_state.board.lock().await.languages.get(language as usize) { lang.extension.clone() } else { log::error!("PPE: lang(): Language not found: {}", language); diff --git a/crates/icy_board_tui/i18n/en/icy_board_tui.ftl b/crates/icy_board_tui/i18n/en/icy_board_tui.ftl index 1f7a7acb..0b7cd4de 100644 --- a/crates/icy_board_tui/i18n/en/icy_board_tui.ftl +++ b/crates/icy_board_tui/i18n/en/icy_board_tui.ftl @@ -1451,3 +1451,227 @@ user_editor_email-help=TODO user_editor_web=Web Address user_editor_web-status=Web user_editor_web-help=TODO + +# ICBSETUP -> Conferences > Edit DIRS.TOML + +dirs_editor_title=DIR.LST Editor { $conference } +dirs_table_name_header=Name +dirs_table_path_header=Path +dirs_edit_directory_title=Edit Directory + +dirs_edit_name=Name +dirs_edit_name-status=Name +dirs_edit_name-help=TODO + +dirs_edit_path=Path +dirs_edit_path-status=Path +dirs_edit_path-help=TODO + +dirs_edit_password=Password +dirs_edit_password-status=Password +dirs_edit_password-help=TODO + +dirs_edit_sort=Sort +dirs_edit_sort-status=Sort +dirs_edit_sort-help=TODO + +dirs_edit_sort_asc=Sort ascending +dirs_edit_sort_asc-status=Sort ascending +dirs_edit_sort_asc-help=TODO + +dirs_edit_has_new_files=Has New Files +dirs_edit_has_new_files-status=Has New Files +dirs_edit_has_new_files-help=TODO + +dirs_edit_is_free=Is Free +dirs_edit_is_free-status=Is Free +dirs_edit_is_free-help=TODO + +dirs_edit_list_sec=List Security +dirs_edit_list_sec-status=List Security +dirs_edit_list_sec-help=TODO + +dirs_download_sec=Download Security +dirs_download_sec-status=Download Security +dirs_download_sec-help=TODO + +area_editor_title=AREA.LST Editor - { $conference } +area_editor_edit_title=Edit Area + +area_editor_name=Name +area_editor_name-status=Name +area_editor_name-help=TODO + +area_editor_file=File +area_editor_file-status=File +area_editor_file-help=TODO + +area_editor_is_readonly=Is Read-Only +area_editor_is_readonly-status=Is Read-Only +area_editor_is_readonly-help=TODO + +area_editor_allow_aliases=Allow Aliases +area_editor_allow_aliases-status=Allow Aliases +area_editor_allow_aliases-help=TODO + +area_editor_list_sec=List Security +area_editor_list_sec-status=List Security +area_editor_list_sec-help=TODO + +area_editor_enter_sec=Enter Security +area_editor_enter_sec-status=Enter Security +area_editor_enter_sec-help=TODO + +area_editor_attach_sec=Attach Security +area_editor_attach_sec-status=Attach Security +area_editor_attach_sec-help=TODO + +doors_editor_title=DOORS File Editor { $conference } +doors_editor_edit_title=Edit Door +doors_editor_key_help=↑ Up ↓ Down Tab Edit Doors ␛ Back +doors_editor_key_help_door=↑ Up ↓ Down INS New ␡ Delete Tab Edit BBSLINK ␛ Back + +doors_editor_header_door=Door +doors_editor_header_description=Description +doors_editor_header_type=Type + +door_editor_name=Name +door_editor_name-status=Name +door_editor_name-help=TODO + +door_editor_description=Description +door_editor_description-status=Description +door_editor_description-help=TODO + +door_editor_password=Password +door_editor_password-status=Password +door_editor_password-help=TODO + +door_editor_path=Path +door_editor_path-status=Path +door_editor_path-help=TODO + +door_editor_door_type=Door Type +door_editor_door_type-status=Door Type +door_editor_door_type-help=TODO + +door_editor_use_shell_execute=Use Shell Execute +door_editor_use_shell_execute-status=Use Shell Execute +door_editor_use_shell_execute-help=TODO + +lang_editor_title=Language Table + +lang_editor_header_language=Language +lang_editor_header_ext=Extension +lang_editor_header_locale=Locale +lang_editor_header_yes=Yes +lang_editor_header_no=No +lang_editor_edit_lang=Edit Language + +lang_editor_edit_lang_label=Language +lang_editor_edit_lang_label-status=Language +lang_editor_edit_lang_label-help=TODO + +lang_editor_edit_extension=Extension +lang_editor_edit_extension-status=Extension +lang_editor_edit_extension-help=TODO + +lang_editor_edit_locale=Locale +lang_editor_edit_locale-status=Locale +lang_editor_edit_locale-help=TODO + +lang_editor_edit_yes_char=Yes Char +lang_editor_edit_yes_char-status=Yes Char +lang_editor_edit_yes_char-help=TODO + +lang_editor_edit_no_char=No Char +lang_editor_edit_no_char-status=No Char +lang_editor_edit_no_char-help=TODO + +surveys_editor_title=Surveys { $conference } +survey_editor_editor=Edit Survey + +survey_editor_editor_header_question=Question +survey_editor_editor_header_answer=Answer + +survey_editor_editor_file=Survey File +survey_editor_editor_file-status=Survey File +survey_editor_editor_file-help=TODO + +survey_editor_editor_answer_file=Answer File +survey_editor_editor_answer_file-status=Answer File +survey_editor_editor_answer_file-help=TODO + +survey_editor_editor_security=Security +survey_editor_editor_security-status=Security +survey_editor_editor_security-help=TODO + +sec_level_editor_title=Edit Security Levels +sec_level_editor_editor=Edit Security Level + +sec_level_editor_security=Security +sec_level_editor_security-status=Security +sec_level_editor_security-help=TODO + +sec_level_editor_description=Description +sec_level_editor_description-status=Description +sec_level_editor_description-help=TODO + +sec_level_editor_password=Password +sec_level_editor_password-status=Password +sec_level_editor_password-help=TODO + +sec_level_editor_time_per_day=Time +sec_level_editor_time_per_day-status=Time +sec_level_editor_time_per_day-help=TODO + +sec_level_editor_daily_bytes=Daily KBytes +sec_level_editor_daily_bytes-status=Daily KBytes +sec_level_editor_daily_bytes-help=TODO + +sec_level_editor_file_ratio=File Ratio +sec_level_editor_file_ratio-status=File Ratio +sec_level_editor_file_ratio-help=TODO + +sec_level_editor_byte_ratio=Byte Ratio +sec_level_editor_byte_ratio-status=Byte Ratio +sec_level_editor_byte_ratio-help=TODO + +sec_level_editor_file_limit=File Limit +sec_level_editor_file_limit-status=File Limit +sec_level_editor_file_limit-help=TODO + +sec_level_editor_kb_limit=KByte Limit +sec_level_editor_kb_limit-status=KByte Limit +sec_level_editor_kb_limit-help=TODO + + +sec_level_editor_file_credit=File Credit +sec_level_editor_file_credit-status=File Credit +sec_level_editor_file_credit-help=TODO + + +sec_level_editor_kb_credit=KByte Credit +sec_level_editor_kb_credit-status=KByte Credit +sec_level_editor_kb_credit-help=TODO + +sec_level_editor_enforce_time=Enforce Time Limit +sec_level_editor_enforce_time-status=Enforce Time Limit +sec_level_editor_enforce_time-help=TODO + + +sec_level_editor_allow_alias=Allow Alias +sec_level_editor_allow_alias-status=Allow Alias +sec_level_editor_allow_alias-help=TODO + +sec_level_force_read_mail=Force Read Mail +sec_level_force_read_mail-status=Force Read Mail +sec_level_force_read_mail-help=TODO + +sec_level_demo_acc=Demo Account +sec_level_demo_acc-status=Demo Account +sec_level_demo_acc-help=TODO + +sec_level_enable_acc=Enable Account +sec_level_enable_acc-status=Enable Account +sec_level_enable_acc-help=TODO diff --git a/crates/icy_board_tui/src/config_menu.rs b/crates/icy_board_tui/src/config_menu.rs index b0a95052..16da7585 100644 --- a/crates/icy_board_tui/src/config_menu.rs +++ b/crates/icy_board_tui/src/config_menu.rs @@ -211,6 +211,17 @@ impl ListItem { self } + pub fn with_update_combobox_value(mut self, update_value: &'static dyn Fn(&T, &ComboBox) -> ()) -> Self { + let b: Box ()> = Box::new(move |val: &T, value: &ListValue| { + let ListValue::ComboBox(b) = value else { + return; + }; + update_value(val, b); + }); + self.update_value = Some(b); + self + } + pub fn with_update_bool_value(mut self, update_value: &'static dyn Fn(&T, bool) -> ()) -> Self { let b: Box ()> = Box::new(move |val: &T, value: &ListValue| { let ListValue::Bool(b) = value else {