save uncompleted state

This commit is contained in:
yggverse 2024-12-07 07:39:11 +02:00
parent fcceb5af1c
commit 4b5e260792
8 changed files with 147 additions and 207 deletions

View File

@ -68,9 +68,6 @@ impl Widget {
let form = form.clone(); let form = form.clone();
let alert_dialog = alert_dialog.clone(); let alert_dialog = alert_dialog.clone();
move || { move || {
// Update form with it children components
form.update();
// Deactivate apply button if the form values could not be processed // Deactivate apply button if the form values could not be processed
alert_dialog.set_response_enabled(RESPONSE_APPLY.0, form.is_applicable()); alert_dialog.set_response_enabled(RESPONSE_APPLY.0, form.is_applicable());
} }
@ -96,14 +93,13 @@ impl Widget {
this.set_response_enabled(response, false); this.set_response_enabled(response, false);
// Result // Result
callback(form.list.selected_item().value_enum()) callback(form.list.selected().value_enum())
} }
}); });
} }
/// Show dialog with new preset /// Show dialog with new preset
pub fn present(&self, parent: Option<&impl IsA<gtk::Widget>>) { pub fn present(&self, parent: Option<&impl IsA<gtk::Widget>>) {
self.form.update();
self.alert_dialog.present(parent) self.alert_dialog.present(parent)
} }
} }

View File

@ -18,13 +18,13 @@ use gtk::{prelude::BoxExt, Box, Orientation};
use std::rc::Rc; use std::rc::Rc;
pub struct Form { pub struct Form {
// pub action: Rc<Action>, // pub widget_action: Rc<Action>,
pub drop: Rc<Drop>, // pub drop: Rc<Drop>,
pub exit: Rc<Exit>, // pub exit: Rc<Exit>,
pub file: Rc<File>, pub file: Rc<File>,
pub list: Rc<List>, pub list: Rc<List>,
pub name: Rc<Name>, pub name: Rc<Name>,
pub save: Rc<Save>, // pub save: Rc<Save>,
pub g_box: Box, pub g_box: Box,
} }
@ -32,14 +32,14 @@ impl Form {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn new(profile: Rc<Profile>, action: Rc<Action>, auth_url: &str) -> Self { pub fn new(profile: Rc<Profile>, widget_action: Rc<Action>, auth_url: &str) -> Self {
// Init components // Init components
let file = Rc::new(File::new(action.clone())); let list = Rc::new(List::new(profile.clone(), auth_url));
let list = Rc::new(List::new(profile.clone(), action.clone(), auth_url)); let file = Rc::new(File::new(widget_action.clone()));
let name = Rc::new(Name::new(action.clone())); let name = Rc::new(Name::new(widget_action.clone()));
let save = Rc::new(Save::new(profile.clone())); let save = Rc::new(Save::new(profile.clone(), list.clone()));
let drop = Rc::new(Drop::new(profile.clone(), action.clone(), list.clone())); let drop = Rc::new(Drop::new(profile.clone(), list.clone()));
let exit = Rc::new(Exit::new(profile.clone(), action.clone(), list.clone())); let exit = Rc::new(Exit::new(profile.clone(), list.clone()));
// Init main container // Init main container
let g_box = Box::builder().orientation(Orientation::Vertical).build(); let g_box = Box::builder().orientation(Orientation::Vertical).build();
@ -51,15 +51,49 @@ impl Form {
g_box.append(&drop.button); g_box.append(&drop.button);
g_box.append(&save.button); g_box.append(&save.button);
// Connect events
list.dropdown.connect_selected_notify({
let list = list.clone();
let name = name.clone();
let file = file.clone();
// let drop = drop.clone();
// let exit = exit.clone();
// let save = save.clone();
move |_| {
// Get selected item
let item = list.selected();
// Update name entry visibility
name.set_visible(matches!(item.value_enum(), Value::GeneratePem));
// Update file choose button visibility
file.set_visible(matches!(item.value_enum(), Value::ImportPem));
// Update ID-related components
match item.value_enum() {
Value::ProfileIdentityGeminiId(_) => {
drop.set_visible(true);
exit.set_visible(true);
save.set_visible(true);
}
_ => {
drop.set_visible(false);
exit.set_visible(false);
save.set_visible(false);
}
}
}
});
// Return activated `Self` // Return activated `Self`
Self { Self {
// action, // widget_action,
drop, // drop,
exit, // exit,
file, file,
list, list,
name, name,
save, // save,
g_box, g_box,
} }
} }
@ -68,38 +102,11 @@ impl Form {
/// Validate `Self` components match current selection /// Validate `Self` components match current selection
pub fn is_applicable(&self) -> bool { pub fn is_applicable(&self) -> bool {
match self.list.selected_item().value_enum() { match self.list.selected().value_enum() {
Value::GeneratePem => self.name.is_valid(), Value::GeneratePem => self.name.is_valid(),
Value::ImportPem => self.file.is_valid(), Value::ImportPem => self.file.is_valid(),
Value::ProfileIdentityGeminiId(_) => !self.list.selected_item().is_active(), Value::ProfileIdentityGeminiId(_) => !self.list.selected().is_active(),
_ => true, _ => true,
} }
} }
pub fn update(&self) {
// Get selected item
let item = self.list.selected_item();
// Update name entry visibility
self.name
.update(matches!(item.value_enum(), Value::GeneratePem));
// Update file choose button visibility
self.file
.update(matches!(item.value_enum(), Value::ImportPem));
// Update ID-related components
match item.value_enum() {
Value::ProfileIdentityGeminiId(value) => {
self.drop.update(Some(value));
self.exit.update(Some(value));
self.save.update(Some(value));
}
_ => {
self.drop.update(None);
self.exit.update(None);
self.save.update(None);
}
}
}
} }

View File

@ -1,5 +1,4 @@
use super::Action; use super::list::{item::Value, List};
use super::List;
use crate::profile::Profile; use crate::profile::Profile;
use adw::{ use adw::{
prelude::{AdwDialogExt, AlertDialogExt, AlertDialogExtManual}, prelude::{AdwDialogExt, AlertDialogExt, AlertDialogExtManual},
@ -9,7 +8,7 @@ use gtk::{
prelude::{ButtonExt, WidgetExt}, prelude::{ButtonExt, WidgetExt},
Button, Button,
}; };
use std::{cell::RefCell, rc::Rc}; use std::rc::Rc;
// Defaults // Defaults
@ -23,7 +22,6 @@ const RESPONSE_CANCEL: (&str, &str) = ("cancel", "Cancel");
const RESPONSE_CONFIRM: (&str, &str) = ("confirm", "Confirm"); const RESPONSE_CONFIRM: (&str, &str) = ("confirm", "Confirm");
pub struct Drop { pub struct Drop {
profile_identity_gemini_id: Rc<RefCell<Option<i64>>>,
pub button: Button, pub button: Button,
} }
@ -31,10 +29,7 @@ impl Drop {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn new(profile: Rc<Profile>, action: Rc<Action>, list: Rc<List>) -> Self { pub fn new(profile: Rc<Profile>, list: Rc<List>) -> Self {
// Init selected option holder
let profile_identity_gemini_id = Rc::new(RefCell::new(None::<i64>));
// Init main widget // Init main widget
let button = Button::builder() let button = Button::builder()
.label(LABEL) .label(LABEL)
@ -45,13 +40,11 @@ impl Drop {
// Init events // Init events
button.connect_clicked({ button.connect_clicked({
let action = action.clone();
let button = button.clone(); let button = button.clone();
let profile_identity_gemini_id = profile_identity_gemini_id.clone(); let list = list.clone();
move |_| { move |_| {
// Get selected identity from holder match list.selected().value_enum() {
match profile_identity_gemini_id.borrow().as_ref() { Value::ProfileIdentityGeminiId(profile_identity_gemini_id) => {
Some(profile_identity_gemini_id) => {
// Init sub-widget // Init sub-widget
let alert_dialog = AlertDialog::builder() let alert_dialog = AlertDialog::builder()
.heading(HEADING) .heading(HEADING)
@ -76,13 +69,14 @@ impl Drop {
// Connect confirmation event // Connect confirmation event
alert_dialog.connect_response(Some(RESPONSE_CONFIRM.0), { alert_dialog.connect_response(Some(RESPONSE_CONFIRM.0), {
let action = action.clone();
let button = button.clone(); let button = button.clone();
let list = list.clone(); let list = list.clone();
let profile = profile.clone(); let profile = profile.clone();
let profile_identity_gemini_id = *profile_identity_gemini_id; move |_, _| match profile
move |_, _| { .identity
match profile.identity.gemini.delete(profile_identity_gemini_id) { .gemini
.delete(profile_identity_gemini_id)
{
Ok(_) => { Ok(_) => {
if list.remove(profile_identity_gemini_id).is_some() { if list.remove(profile_identity_gemini_id).is_some() {
button.set_css_classes(&["success"]); button.set_css_classes(&["success"]);
@ -90,7 +84,6 @@ impl Drop {
} else { } else {
button.set_css_classes(&["error"]); button.set_css_classes(&["error"]);
button.set_label("List item not found") button.set_label("List item not found")
// @TODO unexpected
} }
} }
Err(e) => { Err(e) => {
@ -98,39 +91,23 @@ impl Drop {
button.set_label(&e.to_string()) button.set_label(&e.to_string())
} }
} }
action.update.activate()
}
}); });
// Show dialog // Show dialog
alert_dialog.present(Some(&button)) alert_dialog.present(Some(&button))
} }
None => todo!(), // unexpected _ => todo!(), // unexpected
} }
} }
}); });
// Return activated `Self` // Return activated `Self`
Self { Self { button }
profile_identity_gemini_id,
button,
}
} }
// Actions // Actions
/// Update `profile_identity_gemini_id` holder, pub fn set_visible(&self, is_visible: bool) {
/// toggle visibility depending on given value self.button.set_visible(is_visible)
pub fn update(&self, profile_identity_gemini_id: Option<i64>) {
self.button.set_visible(match profile_identity_gemini_id {
Some(value) => {
self.profile_identity_gemini_id.replace(Some(value));
true
}
None => {
self.profile_identity_gemini_id.replace(None);
false
}
})
} }
} }

View File

@ -1,5 +1,4 @@
use super::Action; use super::list::{item::Value, List};
use super::List;
use crate::profile::Profile; use crate::profile::Profile;
use adw::{ use adw::{
prelude::{AdwDialogExt, AlertDialogExt, AlertDialogExtManual}, prelude::{AdwDialogExt, AlertDialogExt, AlertDialogExtManual},
@ -9,7 +8,7 @@ use gtk::{
prelude::{ButtonExt, WidgetExt}, prelude::{ButtonExt, WidgetExt},
Button, Button,
}; };
use std::{cell::RefCell, rc::Rc}; use std::rc::Rc;
// Defaults // Defaults
@ -23,7 +22,6 @@ const RESPONSE_CANCEL: (&str, &str) = ("cancel", "Cancel");
const RESPONSE_CONFIRM: (&str, &str) = ("confirm", "Confirm"); const RESPONSE_CONFIRM: (&str, &str) = ("confirm", "Confirm");
pub struct Exit { pub struct Exit {
profile_identity_gemini_id: Rc<RefCell<Option<i64>>>,
pub button: Button, pub button: Button,
} }
@ -31,10 +29,7 @@ impl Exit {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn new(profile: Rc<Profile>, action: Rc<Action>, list: Rc<List>) -> Self { pub fn new(profile: Rc<Profile>, list: Rc<List>) -> Self {
// Init selected option holder
let profile_identity_gemini_id = Rc::new(RefCell::new(None::<i64>));
// Init main widget // Init main widget
let button = Button::builder() let button = Button::builder()
.label(LABEL) .label(LABEL)
@ -45,13 +40,11 @@ impl Exit {
// Init events // Init events
button.connect_clicked({ button.connect_clicked({
let action = action.clone();
let button = button.clone(); let button = button.clone();
let profile_identity_gemini_id = profile_identity_gemini_id.clone();
move |_| { move |_| {
// Get selected identity from holder // Get selected identity from holder
match profile_identity_gemini_id.borrow().as_ref() { match list.selected().value_enum() {
Some(profile_identity_gemini_id) => { Value::ProfileIdentityGeminiId(profile_identity_gemini_id) => {
// Init sub-widget // Init sub-widget
let alert_dialog = AlertDialog::builder() let alert_dialog = AlertDialog::builder()
.heading(HEADING) .heading(HEADING)
@ -76,66 +69,44 @@ impl Exit {
// Connect confirmation event // Connect confirmation event
alert_dialog.connect_response(Some(RESPONSE_CONFIRM.0), { alert_dialog.connect_response(Some(RESPONSE_CONFIRM.0), {
let action = action.clone();
let button = button.clone(); let button = button.clone();
let list = list.clone(); let list = list.clone();
let profile = profile.clone(); let profile = profile.clone();
let profile_identity_gemini_id = *profile_identity_gemini_id; move |_, _| match profile
move |_, _| {
match profile
.identity .identity
.gemini .gemini
.auth .auth
.remove_ref(profile_identity_gemini_id) .remove_ref(profile_identity_gemini_id)
{ {
Ok(_) => { Ok(_) => match list.update(profile_identity_gemini_id) {
if list.remove(profile_identity_gemini_id).is_some() { Some(_) => {
button.set_css_classes(&["success"]); button.set_css_classes(&["success"]);
button.set_label("Identity successfully disconnected") button.set_label("Identity successfully disconnected");
} else {
button.set_css_classes(&["error"]);
button.set_label("List item not found")
// @TODO unexpected
}
} }
None => todo!(),
},
Err(e) => { Err(e) => {
button.set_css_classes(&["error"]); button.set_css_classes(&["error"]);
button.set_label(&e.to_string()) button.set_label(&e.to_string())
} }
} }
action.update.activate()
}
}); });
// Show dialog // Show dialog
alert_dialog.present(Some(&button)) alert_dialog.present(Some(&button))
} }
None => todo!(), // unexpected _ => todo!(), // unexpected
} }
} }
}); });
// Return activated `Self` // Return activated `Self`
Self { Self { button }
profile_identity_gemini_id,
button,
}
} }
// Actions // Actions
/// Update `profile_identity_gemini_id` holder, pub fn set_visible(&self, is_visible: bool) {
/// toggle visibility depending on given value self.button.set_visible(is_visible)
pub fn update(&self, profile_identity_gemini_id: Option<i64>) {
self.button.set_visible(match profile_identity_gemini_id {
Some(value) => {
self.profile_identity_gemini_id.replace(Some(value));
true
}
None => {
self.profile_identity_gemini_id.replace(None);
false
}
})
} }
} }

View File

@ -21,7 +21,7 @@ impl File {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn new(action: Rc<Action>) -> Self { pub fn new(widget_action: Rc<Action>) -> Self {
// Init PEM // Init PEM
let pem = Rc::new(RefCell::new(None)); let pem = Rc::new(RefCell::new(None));
@ -37,7 +37,7 @@ impl File {
button.connect_clicked({ button.connect_clicked({
let button = button.clone(); let button = button.clone();
let pem = pem.clone(); let pem = pem.clone();
let update = action.update.clone(); let widget_action = widget_action.clone();
move |_| { move |_| {
// Lock open button (prevent double click) // Lock open button (prevent double click)
button.set_sensitive(false); button.set_sensitive(false);
@ -63,7 +63,7 @@ impl File {
.open(None::<&Window>, None::<&Cancellable>, { .open(None::<&Window>, None::<&Cancellable>, {
let button = button.clone(); let button = button.clone();
let pem = pem.clone(); let pem = pem.clone();
let update = update.clone(); let widget_action = widget_action.clone();
move |result| { move |result| {
match result { match result {
Ok(file) => match file.path() { Ok(file) => match file.path() {
@ -89,7 +89,7 @@ impl File {
} }
} }
button.set_sensitive(true); // unlock button.set_sensitive(true); // unlock
update.activate() widget_action.update.activate()
} }
}); });
} }
@ -102,12 +102,8 @@ impl File {
// Actions // Actions
/// Change visibility status /// Change visibility status
/// * grab focus on `is_visible` pub fn set_visible(&self, is_visible: bool) {
pub fn update(&self, is_visible: bool) {
self.button.set_visible(is_visible); self.button.set_visible(is_visible);
if is_visible {
self.button.grab_focus();
}
} }
// Getters // Getters

View File

@ -3,7 +3,6 @@ use std::rc::Rc;
use item::Item; use item::Item;
use super::Action;
use crate::profile::Profile; use crate::profile::Profile;
use gtk::{ use gtk::{
gdk::Cursor, gdk::Cursor,
@ -18,22 +17,23 @@ use gtk::{
pub struct List { pub struct List {
pub dropdown: DropDown, pub dropdown: DropDown,
list_store: ListStore, list_store: ListStore,
// profile: Rc<Profile>,
} }
impl List { impl List {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn new(profile: Rc<Profile>, action: Rc<Action>, auth_url: &str) -> Self { pub fn new(profile: Rc<Profile>, auth_url: &str) -> Self {
// Init model // Init model
let list_store = ListStore::new::<Item>(); let list_store = ListStore::new::<Item>();
list_store.remove_all();
list_store.append(&Item::new_guest_session()); list_store.append(&Item::new_guest_session());
list_store.append(&Item::new_generate_pem()); list_store.append(&Item::new_generate_pem());
list_store.append(&Item::new_import_pem()); list_store.append(&Item::new_import_pem());
// Append identities from profile database
// * memory cache synced also and could be faster @TODO
match profile.identity.gemini.database.records() { match profile.identity.gemini.database.records() {
Ok(identities) => { Ok(identities) => {
for identity in identities { for identity in identities {
@ -132,41 +132,52 @@ impl List {
.factory(&factory) .factory(&factory)
.build(); .build();
// Connect events
dropdown.connect_selected_notify(move |_| action.update.activate());
// Return activated `Self` // Return activated `Self`
Self { Self {
list_store,
dropdown, dropdown,
list_store,
// profile,
} }
} }
// Actions // Actions
/// Find list item by `value` (stores ID) /// Find list item by `profile_identity_gemini_id`
/// * return `position` found /// * return `position` found
pub fn find(&self, value: i64) -> Option<u32> { pub fn find(&self, profile_identity_gemini_id: i64) -> Option<u32> {
self.list_store self.list_store.find_with_equal_func(|this| {
.find_with_equal_func(|this| value == this.clone().downcast::<Item>().unwrap().value()) profile_identity_gemini_id == this.downcast_ref::<Item>().unwrap().value()
})
} }
/// Remove list item by `value` (stores ID) /// Remove list item by `profile_identity_gemini_id` (stores ID)
/// * return `position` of removed list item /// * return `position` of removed list item
pub fn remove(&self, value: i64) -> Option<u32> { pub fn remove(&self, profile_identity_gemini_id: i64) -> Option<u32> {
match self.find(value) { match self.find(profile_identity_gemini_id) {
Some(position) => { Some(position) => {
self.list_store.remove(position); self.list_store.remove(position);
Some(position) Some(position)
} }
None => None, None => todo!(),
}
}
/// Update list item by `profile_identity_gemini_id` (stores ID)
/// * return `position` of updated list item
pub fn update(&self, profile_identity_gemini_id: i64) -> Option<u32> {
match self.find(profile_identity_gemini_id) {
Some(position) => {
// @TODO
Some(position)
}
None => todo!(),
} }
} }
// Getters // Getters
/// Get selected `Item` GObject /// Get selected `Item` GObject
pub fn selected_item(&self) -> Item { pub fn selected(&self) -> Item {
self.dropdown self.dropdown
.selected_item() .selected_item()
.and_downcast::<Item>() .and_downcast::<Item>()

View File

@ -19,7 +19,7 @@ impl Name {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn new(action: Rc<Action>) -> Self { pub fn new(widget_action: Rc<Action>) -> Self {
// Init main gobject // Init main gobject
let entry = Entry::builder() let entry = Entry::builder()
.margin_top(MARGIN) .margin_top(MARGIN)
@ -29,7 +29,7 @@ impl Name {
.build(); .build();
// Init events // Init events
entry.connect_changed(move |_| action.update.activate()); entry.connect_changed(move |_| widget_action.update.activate());
// Return activated `Self` // Return activated `Self`
Self { entry } Self { entry }
@ -39,7 +39,7 @@ impl Name {
/// Change visibility status /// Change visibility status
/// * grab focus on `is_visible` /// * grab focus on `is_visible`
pub fn update(&self, is_visible: bool) { pub fn set_visible(&self, is_visible: bool) {
self.entry.set_visible(is_visible); self.entry.set_visible(is_visible);
if is_visible { if is_visible {
self.entry.grab_focus(); self.entry.grab_focus();

View File

@ -1,20 +1,20 @@
mod certificate; mod certificate;
use certificate::Certificate; use certificate::Certificate;
use super::list::{item::Value, List};
use crate::profile::Profile; use crate::profile::Profile;
use gtk::{ use gtk::{
gio::{Cancellable, ListStore}, gio::{Cancellable, ListStore},
prelude::{ButtonExt, FileExt, WidgetExt}, prelude::{ButtonExt, FileExt, WidgetExt},
Button, FileDialog, FileFilter, Window, Button, FileDialog, FileFilter, Window,
}; };
use std::{cell::RefCell, fs::File, io::Write, rc::Rc}; use std::{fs::File, io::Write, rc::Rc};
const LABEL: &str = "Export"; const LABEL: &str = "Export";
const TOOLTIP_TEXT: &str = "Export selected identity to file"; const TOOLTIP_TEXT: &str = "Export selected identity to file";
const MARGIN: i32 = 8; const MARGIN: i32 = 8;
pub struct Save { pub struct Save {
profile_identity_gemini_id: Rc<RefCell<Option<i64>>>,
pub button: Button, pub button: Button,
} }
@ -22,10 +22,7 @@ impl Save {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn new(profile: Rc<Profile>) -> Self { pub fn new(profile: Rc<Profile>, list: Rc<List>) -> Self {
// Init selected option holder
let profile_identity_gemini_id = Rc::new(RefCell::new(None));
// Init main widget // Init main widget
let button = Button::builder() let button = Button::builder()
.label(LABEL) .label(LABEL)
@ -36,17 +33,16 @@ impl Save {
// Init events // Init events
button.connect_clicked({ button.connect_clicked({
let profile_identity_gemini_id = profile_identity_gemini_id.clone();
let button = button.clone(); let button = button.clone();
move |_| { move |_| {
// Get selected identity from holder // Get selected identity from holder
match profile_identity_gemini_id.borrow().as_ref() { match list.selected().value_enum() {
Some(profile_identity_gemini_id) => { Value::ProfileIdentityGeminiId(profile_identity_gemini_id) => {
// Lock open button (prevent double click) // Lock open button (prevent double click)
button.set_sensitive(false); button.set_sensitive(false);
// Create PEM file based on option ID selected // Create PEM file based on option ID selected
match Certificate::new(profile.clone(), *profile_identity_gemini_id) { match Certificate::new(profile.clone(), profile_identity_gemini_id) {
Ok(certificate) => { Ok(certificate) => {
// Init file filters related with PEM extension // Init file filters related with PEM extension
let filters = ListStore::new::<FileFilter>(); let filters = ListStore::new::<FileFilter>();
@ -121,32 +117,18 @@ impl Save {
} }
} }
} }
None => todo!(), // unexpected _ => todo!(), // unexpected
} }
} }
}); });
// Return activated `Self` // Return activated `Self`
Self { Self { button }
profile_identity_gemini_id,
button,
}
} }
// Actions // Actions
/// Update `profile_identity_gemini_id` holder, pub fn set_visible(&self, is_visible: bool) {
/// toggle visibility depending on given value self.button.set_visible(is_visible)
pub fn update(&self, profile_identity_gemini_id: Option<i64>) {
self.button.set_visible(match profile_identity_gemini_id {
Some(value) => {
self.profile_identity_gemini_id.replace(Some(value));
true
}
None => {
self.profile_identity_gemini_id.replace(None);
false
}
})
} }
} }