diff --git a/res/gtk/connection-recent.blp b/res/gtk/connection-recent.blp index 41408ce..cd3c4bd 100644 --- a/res/gtk/connection-recent.blp +++ b/res/gtk/connection-recent.blp @@ -21,6 +21,21 @@ template $PsequelConnectionSidebar : Gtk.Box { hexpand: true; halign: end; + Button { + styles ["flat", "big-icon", "linked"] + icon-name: "document-export-symbolic"; + tooltip-text: "Import Connection"; + clicked => $on_import_connection(); + } + + Button { + styles ["flat", "big-icon", "linked"] + icon-name: "document-import-symbolic"; + tooltip-text: "Export Connection"; + clicked => $on_export_connection(); + } + + Button { tooltip-text: "Add new connection"; styles ["flat"] @@ -43,6 +58,7 @@ template $PsequelConnectionSidebar : Gtk.Box { ScrolledWindow { + vexpand: true; ListBox conn_list { styles ["navigation-sidebar"] vexpand: true; diff --git a/res/gtk/window.blp b/res/gtk/window.blp index 3a47ca0..b11cfaa 100644 --- a/res/gtk/window.blp +++ b/res/gtk/window.blp @@ -7,22 +7,23 @@ template $PsequelWindow : Adw.ApplicationWindow { title: "Psequel"; + Adw.ToastOverlay overlay { + Stack stack { + transition-type: slide_up_down; - Stack stack { - transition-type: slide_up_down; + StackPage { + name: "connection-view"; + child: $PsequelConnectionView { + + }; + } - StackPage { - name: "connection-view"; - child: $PsequelConnectionView { - - }; - } - - StackPage { - name: "query-view"; - child: $PsequelQueryView { + StackPage { + name: "query-view"; + child: $PsequelQueryView { - }; + }; + } } } } diff --git a/src/ui/connection/connection_recent.vala b/src/ui/connection/connection_recent.vala index 2049bbb..c610d71 100644 --- a/src/ui/connection/connection_recent.vala +++ b/src/ui/connection/connection_recent.vala @@ -7,6 +7,9 @@ namespace Psequel { [GtkChild] unowned Gtk.ListBox conn_list; + private Application app; + private ObservableArrayList model; + public ConnectionForm form { get; set; } public ConnectionSidebar () { @@ -14,6 +17,8 @@ namespace Psequel { } construct { + + this.app = ResourceManager.instance ().app; // print ("%s\n", this.form.name); // setup_bindings (); } @@ -21,15 +26,15 @@ namespace Psequel { public void setup_bindings () { debug ("setup bindings"); - var conns = ResourceManager.instance ().recent_connections; + this.model = ResourceManager.instance ().recent_connections; // Auto create a conn and focus it on first install. - if (conns.size == 0) { - conns.add (new Connection ()); + if (model.size == 0) { + model.add (new Connection ()); } // Bind the conns model to the list view. - conn_list.bind_model (conns, row_factory); + conn_list.bind_model (model, row_factory); // Auto select created row. var first_row = conn_list.get_row_at_index (0); @@ -99,6 +104,137 @@ namespace Psequel { return row; } } + + [GtkCallback] + private void on_import_connection (Gtk.Button btn) { + debug ("Importting connections"); + open_file_dialog.begin ("Import Connections"); + } + + [GtkCallback] + private void on_export_connection (Gtk.Button btn) { + debug ("Exporting connections"); + save_file_dialog.begin ("Export Connections"); + } + + private async void save_file_dialog (string title = "Save to file") { + + var filter = new Gtk.FileFilter (); + filter.add_suffix ("json"); + + var filters = new ListStore (typeof (Gtk.FileFilter)); + filters.append (filter); + + var file_dialog = new Gtk.FileDialog () { + modal = true, + initial_folder = File.new_for_path (Environment.get_home_dir ()), + title = title, + initial_name = "connections", + default_filter = filter, + filters = filters, + }; + + var content = serialize_connection (this.model); + var bytes = new Bytes.take (content.data); // Move data to byte so it live when out scope + var window = (Window) app.active_window; + + try { + var file = yield file_dialog.save (window, null); + yield file.replace_contents_bytes_async (bytes, null, false, FileCreateFlags.NONE, null, null); + + var toast = new Adw.Toast ("Data saved successfully.") { + timeout = 2, + }; + window.add_toast (toast); + + } catch (Error err) { + debug ("can't save file"); + + var toast = new Adw.Toast (err.message) { + timeout = 3, + }; + window.add_toast (toast); + } + + } + + private async void open_file_dialog (string title = "Open File") { + var filter = new Gtk.FileFilter (); + filter.add_mime_type ("application/json"); + var file_dialog = new Gtk.FileDialog () { + modal = true, + initial_folder = File.new_for_path (Environment.get_home_dir ()), + title = title, + initial_name = "connections", + default_filter = filter + }; + + uint8[] contents; + var window = (Window) app.active_window; + + try { + var file = yield file_dialog.open (window, null); + yield file.load_contents_async (null, out contents, null); + + var json_str = (string) contents; + var conns = deserialize_connection (json_str); + this.model.batch_add (conns.iterator ()); + + var toast = new Adw.Toast (@"Loaded $(conns.size) connections") { + timeout = 3, + }; + window.add_toast (toast); + + + } catch (Error err) { + debug ("Can't load data from file."); + + var toast = new Adw.Toast (err.message) { + timeout = 3, + }; + window.add_toast (toast); + } + } + + private string serialize_connection (ObservableArrayList conns) { + + var builder = new Json.Builder (); + builder.begin_object (); + builder.set_member_name ("recent_connections"); + builder.begin_array (); + + foreach (var conn in conns) { + builder.add_value (Json.gobject_serialize (conn)); + } + + builder.end_array (); + builder.end_object (); + + var node = builder.get_root (); + return Json.to_string (node, true); + } + + private ObservableArrayList deserialize_connection (string content) { + var parser = new Json.Parser (); + var recent_connections = new ObservableArrayList (); + + try { + parser.load_from_data (content); + var root = parser.get_root (); + var obj = root.get_object (); + var conns = obj.get_array_member ("recent_connections"); + + conns.foreach_element ((array, index, node) => { + var conn = (Connection) Json.gobject_deserialize (typeof (Connection), node); + recent_connections.add (conn); + }); + } catch (Error err) { + debug (err.message); + recent_connections.clear (); + } + + return recent_connections; + } } public class ConnectionRow : Gtk.ListBoxRow { diff --git a/src/ui/window.vala b/src/ui/window.vala index 2802662..f161119 100644 --- a/src/ui/window.vala +++ b/src/ui/window.vala @@ -57,7 +57,14 @@ namespace Psequel { } + public void add_toast (Adw.Toast toast) { + overlay.add_toast (toast); + } + [GtkChild] private unowned Gtk.Stack stack; + + [GtkChild] + private unowned Adw.ToastOverlay overlay; } } \ No newline at end of file