Browse Source

Add identity management

Billy Barrow 1 năm trước cách đây
mục cha
commit
08163cd71a

+ 5 - 0
src/App.vala

@@ -11,6 +11,7 @@ namespace Publicate {
 
         protected override void activate () {
             var window = new ViewerWindow(this);
+            add_window (window);
             window.present ();
         }
 
@@ -29,5 +30,9 @@ namespace Publicate {
         var app = new PpubViewerApplication();
         return app.run (argv);
     }
+
+    string get_publicate_path() {
+        return Environment.get_home_dir () + "/.publicate";
+    }
     
 }

+ 0 - 0
src/CollectionView.vala


+ 34 - 1
src/Editor.vala

@@ -37,6 +37,19 @@ namespace Publicate {
             header_title = new WindowTitle("Untitled Publication", "");
             header.title_widget = header_title;
 
+            var menu_button = new MenuButton();
+            menu_button.icon_name = "open-menu-symbolic";
+            header.pack_end(menu_button);
+            var menu = new Menu();
+            menu_button.menu_model = menu;
+            menu.append_section(null, win.window_menu);
+
+            var document_menu = new Menu();
+            document_menu.append("Save", "win.save");
+            document_menu.append("Save as…", "win.save-as");
+            document_menu.append("Save all", "win.save-all");
+            menu.append_section(null, document_menu);
+
             var file_box = new Box(Orientation.VERTICAL, 0);
             file_box.vexpand = true;
             file_explorer = new FileExplorer();
@@ -227,7 +240,27 @@ namespace Publicate {
             yield save(to_save);
         }
 
-        private async void save_all() {
+        public async void save_as() {
+            var dialog = new FileDialog();
+            var filter = new FileFilter();
+            var filters = new GLib.ListStore(Type.OBJECT);
+            filters.append(filter);
+            filter.add_pattern("*.ppub");
+            filter.name = "Portable Publications";
+            dialog.filters = filters;
+            var file = yield dialog.save(window, null);
+
+            if(file == null) {
+                return;
+            }
+
+            progress_window.display_progress("Saving…", 0.0);
+            yield publication_file.copy_async(file, FileCopyFlags.OVERWRITE, 1, null);
+            publication_file = file;
+            yield save_tab();
+        }
+
+        public async void save_all() {
             var to_save = window.publication.assets.select<Savable>(get_savable_editor_or_asset);
             yield save(to_save);
         }

+ 2 - 2
src/FileCreationPopover.vala

@@ -3,11 +3,11 @@ using Gtk;
 
 namespace Publicate {
 
-    public class FileCreat8ionPopover : Popover {
+    public class FileCreationPopover : Popover {
 
         private ViewerWindow toplevel;
 
-        public FileCreat8ionPopover(ViewerWindow window) {
+        public FileCreationPopover(ViewerWindow window) {
 
             toplevel = window;
             var box = new Box(Orientation.VERTICAL, 8);

+ 31 - 0
src/Identities/IdentityActionRow.vala

@@ -0,0 +1,31 @@
+using Gtk;
+using Adw;
+
+namespace Publicate {
+
+    private class IdentityActionRow : ActionRow {
+        private Gtk.Window toplevel;
+        public Ppub.CollectionMemberCredentials creds;
+        public Pprf.MemberIdentity? identity;
+
+        public IdentityActionRow(Gtk.Window window, Ppub.CollectionMemberCredentials credentials, Pprf.MemberIdentity? identity, string? name) {
+            toplevel = window;
+            creds = credentials;
+            this.identity = identity;
+
+            if(identity != null) {
+                title = identity.name;
+            }
+            else {
+                title = name;
+            }
+            subtitle = credentials.get_public_keys().to_string();
+            
+            var copy_key_button = new Gtk.Button.from_icon_name ("edit-copy-symbolic");
+            copy_key_button.valign = Align.CENTER;
+            copy_key_button.clicked.connect (() => get_clipboard ().set_text(subtitle));
+            copy_key_button.tooltip_text = "Copy the public key of this identity";
+            add_suffix (copy_key_button);
+        }
+    }
+}

+ 69 - 0
src/Identities/IdentityList.vala

@@ -0,0 +1,69 @@
+using Adw;
+using Gtk;
+using Invercargill;
+
+namespace Publicate {
+
+    public class IdentityList : Box {
+
+        
+        private ListBox identity_list;
+        private ScrolledWindow scroll_window;
+
+        public signal void identity_selected(Ppub.CollectionMemberCredentials redentials, Pprf.MemberIdentity? identity, string? name);
+
+        public Pprf.MemberIdentity? selected_identity {get; private set;}
+        public Ppub.CollectionMemberCredentials? selected_credentials {get; private set;}
+        public string? selected_name {get; private set;}
+        private Gtk.Window window;
+
+        public bool has_entries { get; private set; }
+
+        public IdentityList(Gtk.Window window) {
+            this.window = window;
+            scroll_window = new ScrolledWindow();
+            scroll_window.hscrollbar_policy = PolicyType.NEVER;
+            scroll_window.propagate_natural_width = true;
+            scroll_window.vexpand = true;
+            append(scroll_window);
+
+            create_list();
+        }
+
+        private void create_list() {
+            has_entries = false;
+            identity_list = new ListBox();
+            scroll_window.child = identity_list;
+            identity_list.row_selected.connect((row) => {
+                if(row == null) {
+                    return;
+                }
+                var item = (IdentityActionRow)row;
+                selected_identity = item.identity;
+                selected_credentials = item.creds;
+                selected_name = item.title;
+                identity_selected(item.creds, item.identity, item.title);
+            });
+        }
+        
+        public void populate_credentials() throws Error {
+            identity_list.remove_all();
+            var dir = Dir.open(get_publicate_path() + "/credentials", 0);
+            string name = null;
+            bool first = true;
+            while((name = dir.read_name()) != null) {
+                has_entries = true;
+                string cred_str;
+                FileUtils.get_contents(get_publicate_path() + "/credentials/" + name, out cred_str, null);
+                var creds = new Ppub.CollectionMemberCredentials.from_string(cred_str);
+                var row = new IdentityActionRow(window, creds, null, name);
+                identity_list.append(row);
+                if(first) {
+                    identity_list.select_row(row);
+                    first = false;
+                }
+            }
+        }
+
+    }
+}

+ 126 - 0
src/Identities/ManageIdentitiesWindow.vala

@@ -0,0 +1,126 @@
+using Gtk;
+using Adw;
+
+namespace Publicate {
+
+
+    public class ManageIdentitiesWindow : Adw.Window {
+
+        private Adw.HeaderBar header_bar;
+        private ViewerWindow window;
+        private IdentityList identity_list;
+        private Button delete_button;
+        private Button import_button;
+        private Button export_button;
+
+        public ManageIdentitiesWindow(ViewerWindow window) {
+            this.window = window;
+            modal = true;
+            transient_for = window;
+
+            var box = new Box(Orientation.VERTICAL, 8);
+            header_bar = new Adw.HeaderBar();
+            title = "Manage identities";
+
+            box.append (header_bar);
+            
+            identity_list = new IdentityList(window);
+            identity_list.margin_start = 18;
+            identity_list.margin_end = 18;
+            identity_list.margin_top = 18;
+            identity_list.add_css_class ("boxed-list");
+            box.append(identity_list);
+
+            default_height = 400;
+            default_width = 450;
+
+            var action_buttons = new Box(Orientation.HORIZONTAL, 8);
+            action_buttons.margin_bottom = 18;
+            action_buttons.margin_start = 18;
+            action_buttons.margin_end = 18;
+            action_buttons.halign = Align.FILL;
+            
+            delete_button = new Button.with_label("Delete");
+            delete_button.add_css_class("destructive-action");
+            delete_button.halign = Align.START;
+            delete_button.clicked.connect(() => delete_entry());
+            action_buttons.append(delete_button);
+            
+            export_button = new Button.with_label("Export");
+            export_button.halign = Align.START;
+            export_button.clicked.connect(() => export_credentials.begin());
+            action_buttons.append(export_button);
+            
+            import_button = new Button.with_label("Import Identity");
+            import_button.hexpand = true;
+            import_button.halign = Align.END;
+            import_button.clicked.connect(() => import_credentials.begin());
+            action_buttons.append(import_button);
+
+            box.append(action_buttons);
+            content = box;
+
+            refresh_list();
+        }
+
+        private void refresh_list() {
+            identity_list.populate_credentials();
+            delete_button.sensitive = identity_list.has_entries;
+            export_button.sensitive = identity_list.has_entries;
+        }
+
+        private void delete_entry() {
+            var prompt = new Adw.MessageDialog(this, "Delete identity?", @"Are you sure you want to delete the identity $(identity_list.selected_name)? This cannot be undone.");
+            prompt.add_response("cancel", "Cancel");
+            prompt.add_response("delete", "Delete");
+            prompt.set_response_appearance("delete", ResponseAppearance.DESTRUCTIVE);
+            prompt.response.connect(r => {
+                if(r == "delete") {
+                    File.new_for_path(get_publicate_path() + "/credentials/" + identity_list.selected_name).delete();
+                    refresh_list();
+                }
+            });
+            prompt.present();
+        }
+
+        public async void export_credentials() throws Error {
+            var dialog = new FileDialog();
+            dialog.accept_label = "Export";
+            var file = yield dialog.save(window, null);
+
+            if(file == null) {
+                return;
+            }
+
+            yield File.new_for_path(get_publicate_path() + "/credentials/" + identity_list.selected_name).copy_async(file, FileCopyFlags.OVERWRITE, 1, null, null);
+        }
+
+        public async void import_credentials() throws Error {
+            var dialog = new FileDialog();
+            dialog.accept_label = "Import";
+            var file = yield dialog.open(window, null);
+
+            if(file == null) {
+                return;
+            }
+
+            try {
+                // First verify it's a valid credentials file
+                string cred_str;
+                FileUtils.get_contents(file.get_path(), out cred_str, null);
+                new Ppub.CollectionMemberCredentials.from_string(cred_str);
+
+                yield file.copy_async(File.new_for_path(get_publicate_path() + "/credentials/" + file.get_basename()), FileCopyFlags.OVERWRITE, 1, null, null);
+                refresh_list();
+            }
+            catch(Error e) {
+                var prompt = new Adw.MessageDialog(this, "Import error", @"Could not import identity: $(e.message)");
+                prompt.add_response("ok", "Close");
+                prompt.present();
+            }
+            
+        }
+
+    }
+
+}

+ 6 - 0
src/StartupMenu.vala

@@ -8,6 +8,7 @@ namespace Publicate {
         private Adw.HeaderBar header_bar;
         private Box box;
         private Stack stack;
+        private MenuButton menu_button;
 
         private Wizards.StandardWizard standard_wizard;
         private Wizards.VideoWizard video_wizard;
@@ -22,6 +23,11 @@ namespace Publicate {
             header_bar.add_css_class ("flat");
             header_bar.show_end_title_buttons = true;
             header_bar.title_widget = new Adw.WindowTitle ("", "");
+            
+            menu_button = new MenuButton();
+            menu_button.icon_name = "open-menu-symbolic";
+            header_bar.pack_end(menu_button);
+            menu_button.menu_model = win.window_menu;
 
             stack = new Stack();
             stack.transition_type = StackTransitionType.SLIDE_LEFT_RIGHT;

+ 31 - 1
src/Window.vala

@@ -10,11 +10,17 @@ namespace Publicate {
         private StartupMenu startup_menu;
         public PpubEditor editor {get; set;}
         public Ppub.Publication publication {get; set;}
+        public Menu window_menu { get; set; }
 
         private SimpleAction extract_action;
 
         public ViewerWindow(Adw.Application app) {
             application = app;
+
+            window_menu = new Menu();
+            window_menu.append("New window", "win.new-window");
+            window_menu.append("Manage identities", "win.manage-identities");
+
             stack = new Stack();
             startup_menu = new StartupMenu(this);
             editor = new PpubEditor(this);
@@ -30,7 +36,25 @@ namespace Publicate {
             var save_action = new SimpleAction("save", null);
             save_action.activate.connect(() => editor.save_tab.begin());
             add_action(save_action);
-            add_binding_action(Gdk.Key.s, Gdk.ModifierType.CONTROL_MASK, "win.save", "Save current tab");
+            add_binding_action(Gdk.Key.s, Gdk.ModifierType.CONTROL_MASK, "win.save", null);
+
+            var save_as_action = new SimpleAction("save-as", null);
+            save_as_action.activate.connect(() => editor.save_as.begin());
+            add_action(save_as_action);
+            add_binding_action(Gdk.Key.S, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK, "win.save-as", null);
+
+            var save_all_action = new SimpleAction("save-all", null);
+            save_all_action.activate.connect(() => editor.save_all.begin());
+            add_action(save_all_action);
+
+            var new_window_action = new SimpleAction("new-window", null);
+            new_window_action.activate.connect(() => app.activate());
+            add_action(new_window_action);
+            add_binding_action(Gdk.Key.N, Gdk.ModifierType.CONTROL_MASK, "win.new-window", null);
+
+            var manage_identities_action = new SimpleAction("manage-identities", null);
+            manage_identities_action.activate.connect(() => manage_identities());
+            add_action(manage_identities_action);
         }
 
         public async void open_ppub() throws Error {
@@ -57,6 +81,12 @@ namespace Publicate {
             yield editor.load_ppub(file);
         }
 
+        public void manage_identities() {
+            print("hell0\n");
+            var window = new ManageIdentitiesWindow(this);
+            window.present();
+        }
+
 
     }
 

+ 4 - 0
src/meson.build

@@ -38,6 +38,9 @@ sources += files('Licences/CreativeCommonsByNcNd.vala')
 sources += files('Licences/CreativeCommonsByNcSa.vala')
 sources += files('Licences/CreativeCommonsByNd.vala')
 sources += files('Licences/CreativeCommonsBySa.vala')
+sources += files('Identities/ManageIdentitiesWindow.vala')
+sources += files('Identities/IdentityActionRow.vala')
+sources += files('Identities/IdentityList.vala')
 
 
 dependencies = [
@@ -51,6 +54,7 @@ dependencies = [
     dependency('gtk4'),
     dependency('gtkcommonmark'),
     dependency('libppub'),
+    dependency('libpprf'),
     dependency('gtksourceview-5'),
     dependency('libspelling-1'),
 ]