mirror of
				https://github.com/johrpan/musicus.git
				synced 2025-10-26 19:57:25 +01:00 
			
		
		
		
	Add online selection to person selector
This commit is contained in:
		
							parent
							
								
									40050b3ac3
								
							
						
					
					
						commit
						c61db562f4
					
				
					 8 changed files with 339 additions and 39 deletions
				
			
		|  | @ -6,12 +6,10 @@ | ||||||
|   <object class="HdyWindow" id="window"> |   <object class="HdyWindow" id="window"> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="modal">True</property> |     <property name="modal">True</property> | ||||||
|     <property name="default-width">350</property> |  | ||||||
|     <property name="default-height">300</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |     <property name="destroy-with-parent">True</property> | ||||||
|     <property name="type-hint">dialog</property> |     <property name="type-hint">dialog</property> | ||||||
|     <child> |     <child> | ||||||
|       <object class="GtkBox" id="vbox"> |       <object class="GtkBox"> | ||||||
|         <property name="visible">True</property> |         <property name="visible">True</property> | ||||||
|         <property name="can-focus">False</property> |         <property name="can-focus">False</property> | ||||||
|         <property name="orientation">vertical</property> |         <property name="orientation">vertical</property> | ||||||
|  | @ -43,7 +41,168 @@ | ||||||
|           </packing> |           </packing> | ||||||
|         </child> |         </child> | ||||||
|         <child> |         <child> | ||||||
|           <placeholder/> |           <object class="HdySearchBar"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="search-mode-enabled">True</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkBox"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="orientation">vertical</property> | ||||||
|  |                 <property name="spacing">6</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkSearchEntry" id="search_entry"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">True</property> | ||||||
|  |                     <property name="has-focus">True</property> | ||||||
|  |                     <property name="primary-icon-name">edit-find-symbolic</property> | ||||||
|  |                     <property name="primary-icon-activatable">False</property> | ||||||
|  |                     <property name="primary-icon-sensitive">False</property> | ||||||
|  |                     <property name="placeholder-text" translatable="yes">Search persons …</property> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">0</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkCheckButton" id="server_check_button"> | ||||||
|  |                     <property name="label" translatable="yes">Show persons from the Musicus server</property> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">True</property> | ||||||
|  |                     <property name="receives-default">False</property> | ||||||
|  |                     <property name="active">True</property> | ||||||
|  |                     <property name="draw-indicator">True</property> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">1</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |               </object> | ||||||
|  |             </child> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="expand">False</property> | ||||||
|  |             <property name="fill">True</property> | ||||||
|  |             <property name="position">1</property> | ||||||
|  |           </packing> | ||||||
|  |         </child> | ||||||
|  |         <child> | ||||||
|  |           <object class="GtkStack" id="stack"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="transition-type">crossfade</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkSpinner"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="active">True</property> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="name">loading</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkScrolledWindow" id="scroll"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">True</property> | ||||||
|  |                 <child> | ||||||
|  |                   <placeholder/> | ||||||
|  |                 </child> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="name">content</property> | ||||||
|  |                 <property name="position">1</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkBox"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="halign">center</property> | ||||||
|  |                 <property name="valign">center</property> | ||||||
|  |                 <property name="border-width">18</property> | ||||||
|  |                 <property name="orientation">vertical</property> | ||||||
|  |                 <property name="spacing">18</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkImage"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="opacity">0.5019607843137255</property> | ||||||
|  |                     <property name="pixel-size">80</property> | ||||||
|  |                     <property name="icon-name">network-error-symbolic</property> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">0</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkLabel"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="opacity">0.5019607843137255</property> | ||||||
|  |                     <property name="label" translatable="yes">An error occured!</property> | ||||||
|  |                     <attributes> | ||||||
|  |                       <attribute name="size" value="16384"/> | ||||||
|  |                     </attributes> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">1</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkLabel"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="opacity">0.5019607843137255</property> | ||||||
|  |                     <property name="label" translatable="yes">The server was not reachable or responded with an error. Please check your internet connection.</property> | ||||||
|  |                     <property name="justify">center</property> | ||||||
|  |                     <property name="wrap">True</property> | ||||||
|  |                     <property name="max-width-chars">40</property> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">2</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkButton" id="try_again_button"> | ||||||
|  |                     <property name="label" translatable="yes">Try again</property> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">True</property> | ||||||
|  |                     <property name="receives-default">True</property> | ||||||
|  |                     <property name="halign">center</property> | ||||||
|  |                     <style> | ||||||
|  |                       <class name="suggested-action"/> | ||||||
|  |                     </style> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">3</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="name">error</property> | ||||||
|  |                 <property name="position">2</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="expand">True</property> | ||||||
|  |             <property name="fill">True</property> | ||||||
|  |             <property name="position">3</property> | ||||||
|  |           </packing> | ||||||
|         </child> |         </child> | ||||||
|       </object> |       </object> | ||||||
|     </child> |     </child> | ||||||
|  |  | ||||||
|  | @ -6,6 +6,9 @@ use isahc::http::StatusCode; | ||||||
| use isahc::prelude::*; | use isahc::prelude::*; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| 
 | 
 | ||||||
|  | pub mod persons; | ||||||
|  | pub use persons::*; | ||||||
|  | 
 | ||||||
| /// Credentials used for login.
 | /// Credentials used for login.
 | ||||||
| #[derive(Serialize, Debug, Clone)] | #[derive(Serialize, Debug, Clone)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
							
								
								
									
										24
									
								
								musicus/src/backend/client/persons.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								musicus/src/backend/client/persons.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | ||||||
|  | use super::Backend; | ||||||
|  | use crate::database::Person; | ||||||
|  | use anyhow::{anyhow, Result}; | ||||||
|  | use isahc::prelude::*; | ||||||
|  | use std::time::Duration; | ||||||
|  | 
 | ||||||
|  | impl Backend { | ||||||
|  |     /// Get all available persons from the server.
 | ||||||
|  |     pub async fn get_persons(&self) -> Result<Vec<Person>> { | ||||||
|  |         let server_url = self.get_server_url().ok_or(anyhow!("No server URL set!"))?; | ||||||
|  | 
 | ||||||
|  |         let mut response = Request::get(format!("{}/persons", server_url)) | ||||||
|  |             .timeout(Duration::from_secs(10)) | ||||||
|  |             .body(())? | ||||||
|  |             .send_async() | ||||||
|  |             .await?; | ||||||
|  | 
 | ||||||
|  |         let body = response.text_async().await?; | ||||||
|  | 
 | ||||||
|  |         let persons: Vec<Person> = serde_json::from_str(&body)?; | ||||||
|  | 
 | ||||||
|  |         Ok(persons) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,60 +1,161 @@ | ||||||
| use super::PersonEditor; | use super::PersonEditor; | ||||||
| use crate::backend::Backend; | use crate::backend::Backend; | ||||||
| use crate::database::*; | use crate::database::Person; | ||||||
| use crate::widgets::*; | use crate::widgets::List; | ||||||
|  | use gettextrs::gettext; | ||||||
| use gio::prelude::*; | use gio::prelude::*; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
| use gtk_macros::get_widget; | use gtk_macros::get_widget; | ||||||
|  | use std::cell::RefCell; | ||||||
| use std::rc::Rc; | use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
|  | /// A dialog for selecting a person.
 | ||||||
| pub struct PersonSelector { | pub struct PersonSelector { | ||||||
|  |     backend: Rc<Backend>, | ||||||
|     window: libhandy::Window, |     window: libhandy::Window, | ||||||
|  |     server_check_button: gtk::CheckButton, | ||||||
|  |     stack: gtk::Stack, | ||||||
|  |     list: Rc<List<Person>>, | ||||||
|  |     selected_cb: RefCell<Option<Box<dyn Fn(Person) -> ()>>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl PersonSelector { | impl PersonSelector { | ||||||
|     pub fn new<P, F>(backend: Rc<Backend>, parent: &P, callback: F) -> Self |     pub fn new<P>(backend: Rc<Backend>, parent: &P) -> Rc<Self> | ||||||
|     where |     where | ||||||
|         P: IsA<gtk::Window>, |         P: IsA<gtk::Window>, | ||||||
|         F: Fn(Person) -> () + 'static, |  | ||||||
|     { |     { | ||||||
|  |         // Create UI
 | ||||||
|  | 
 | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_selector.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_selector.ui"); | ||||||
| 
 | 
 | ||||||
|         get_widget!(builder, libhandy::Window, window); |         get_widget!(builder, libhandy::Window, window); | ||||||
|         get_widget!(builder, gtk::Box, vbox); |  | ||||||
|         get_widget!(builder, gtk::Button, add_button); |         get_widget!(builder, gtk::Button, add_button); | ||||||
|  |         get_widget!(builder, gtk::CheckButton, server_check_button); | ||||||
|  |         get_widget!(builder, gtk::SearchEntry, search_entry); | ||||||
|  |         get_widget!(builder, gtk::Stack, stack); | ||||||
|  |         get_widget!(builder, gtk::ScrolledWindow, scroll); | ||||||
|  |         get_widget!(builder, gtk::Button, try_again_button); | ||||||
| 
 | 
 | ||||||
|         let callback = Rc::new(callback); |  | ||||||
| 
 |  | ||||||
|         let list = PersonList::new(backend.clone()); |  | ||||||
| 
 |  | ||||||
|         list.set_selected(clone!(@strong window, @strong callback => move |person| { |  | ||||||
|             window.close(); |  | ||||||
|             callback(person.clone()); |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         vbox.pack_start(&list.widget, true, true, 0); |  | ||||||
|         window.set_transient_for(Some(parent)); |         window.set_transient_for(Some(parent)); | ||||||
| 
 | 
 | ||||||
|         add_button.connect_clicked( |         let list = List::<Person>::new(&gettext("No persons found.")); | ||||||
|             clone!(@strong backend, @strong window, @strong callback => move |_| { |         scroll.add(&list.widget); | ||||||
|                 let editor = PersonEditor::new( |  | ||||||
|                     backend.clone(), |  | ||||||
|                     &window, |  | ||||||
|                     None, |  | ||||||
|                     clone!(@strong window, @strong callback => move |person| { |  | ||||||
|                         window.close(); |  | ||||||
|                         callback(person); |  | ||||||
|                     }), |  | ||||||
|                 ); |  | ||||||
| 
 | 
 | ||||||
|                 editor.show(); |         let this = Rc::new(Self { | ||||||
|  |             backend, | ||||||
|  |             window, | ||||||
|  |             server_check_button, | ||||||
|  |             stack, | ||||||
|  |             list, | ||||||
|  |             selected_cb: RefCell::new(None), | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         // Connect signals and callbacks
 | ||||||
|  | 
 | ||||||
|  |         add_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  |             let editor = PersonEditor::new( | ||||||
|  |                 this.backend.clone(), | ||||||
|  |                 &this.window, | ||||||
|  |                 None, | ||||||
|  |                 clone!(@strong this => move |person| { | ||||||
|  |                     if let Some(cb) = &*this.selected_cb.borrow() { | ||||||
|  |                         cb(person); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     this.window.close(); | ||||||
|  |                 }), | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             editor.show(); | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         search_entry.connect_search_changed(clone!(@strong this => move |_| { | ||||||
|  |             this.list.invalidate_filter(); | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         let load_online = Rc::new(clone!(@strong this => move || { | ||||||
|  |             this.stack.set_visible_child_name("loading"); | ||||||
|  | 
 | ||||||
|  |             let context = glib::MainContext::default(); | ||||||
|  |             let clone = this.clone(); | ||||||
|  |             context.spawn_local(async move { | ||||||
|  |                 match clone.backend.get_persons().await { | ||||||
|  |                     Ok(persons) => { | ||||||
|  |                         clone.list.show_items(persons); | ||||||
|  |                         clone.stack.set_visible_child_name("content"); | ||||||
|  |                     } | ||||||
|  |                     Err(_) => { | ||||||
|  |                         clone.list.show_items(Vec::new()); | ||||||
|  |                         clone.stack.set_visible_child_name("error"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         let load_local = Rc::new(clone!(@strong this => move || { | ||||||
|  |             this.stack.set_visible_child_name("loading"); | ||||||
|  | 
 | ||||||
|  |             let context = glib::MainContext::default(); | ||||||
|  |             let clone = this.clone(); | ||||||
|  |             context.spawn_local(async move { | ||||||
|  |                 let persons = clone.backend.db().get_persons().await.unwrap(); | ||||||
|  |                 clone.list.show_items(persons); | ||||||
|  |                 clone.stack.set_visible_child_name("content"); | ||||||
|  |             }); | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         this.server_check_button.connect_toggled( | ||||||
|  |             clone!(@strong this, @strong load_local, @strong load_online => move |_| { | ||||||
|  |                 if this.server_check_button.get_active() { | ||||||
|  |                     load_online(); | ||||||
|  |                 } else { | ||||||
|  |                     load_local(); | ||||||
|  |                 } | ||||||
|             }), |             }), | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         Self { window } |         this.list.set_make_widget(|person: &Person| { | ||||||
|  |             let label = gtk::Label::new(Some(&person.name_lf())); | ||||||
|  |             label.set_halign(gtk::Align::Start); | ||||||
|  |             label.set_margin_start(6); | ||||||
|  |             label.set_margin_end(6); | ||||||
|  |             label.set_margin_top(6); | ||||||
|  |             label.set_margin_bottom(6); | ||||||
|  |             label.upcast() | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         this.list | ||||||
|  |             .set_filter(clone!(@strong search_entry => move |person: &Person| { | ||||||
|  |                 let search = search_entry.get_text().to_string().to_lowercase(); | ||||||
|  |                 let name = person.name_fl().to_lowercase(); | ||||||
|  |                 search.is_empty() || name.contains(&search) | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         this.list.set_selected(clone!(@strong this => move |work| { | ||||||
|  |             if let Some(cb) = &*this.selected_cb.borrow() { | ||||||
|  |                 cb(work.clone()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             this.window.close(); | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         try_again_button.connect_clicked(clone!(@strong load_online => move |_| { | ||||||
|  |             load_online(); | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         // Initialize
 | ||||||
|  |         load_online(); | ||||||
|  | 
 | ||||||
|  |         this | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /// Set the closure to be called when the user has selected a person.
 | ||||||
|  |     pub fn set_selected_cb<F: Fn(Person) -> () + 'static>(&self, cb: F) { | ||||||
|  |         self.selected_cb.replace(Some(Box::new(cb))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Show the person selector.
 | ||||||
|     pub fn show(&self) { |     pub fn show(&self) { | ||||||
|         self.window.show(); |         self.window.show(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -81,12 +81,16 @@ impl PerformanceEditor { | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         person_button.connect_clicked(clone!(@strong this => move |_| { |         person_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             PersonSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |person| { |             let dialog = PersonSelector::new(this.backend.clone(), &this.window); | ||||||
|  | 
 | ||||||
|  |             dialog.set_selected_cb(clone!(@strong this => move |person| { | ||||||
|                 this.show_person(Some(&person)); |                 this.show_person(Some(&person)); | ||||||
|                 this.person.replace(Some(person)); |                 this.person.replace(Some(person)); | ||||||
|                 this.show_ensemble(None); |                 this.show_ensemble(None); | ||||||
|                 this.ensemble.replace(None); |                 this.ensemble.replace(None); | ||||||
|             })).show(); |             })); | ||||||
|  | 
 | ||||||
|  |             dialog.show(); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         ensemble_button.connect_clicked(clone!(@strong this => move |_| { |         ensemble_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|  |  | ||||||
|  | @ -76,10 +76,14 @@ impl PartEditor { | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         composer_button.connect_clicked(clone!(@strong this => move |_| { |         composer_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             PersonSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |person| { |             let dialog = PersonSelector::new(this.backend.clone(), &this.window); | ||||||
|  | 
 | ||||||
|  |             dialog.set_selected_cb(clone!(@strong this => move |person| { | ||||||
|                 this.show_composer(Some(&person)); |                 this.show_composer(Some(&person)); | ||||||
|                 this.composer.replace(Some(person)); |                 this.composer.replace(Some(person)); | ||||||
|             })).show(); |             })); | ||||||
|  | 
 | ||||||
|  |             dialog.show(); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.reset_composer_button |         this.reset_composer_button | ||||||
|  |  | ||||||
|  | @ -156,10 +156,14 @@ impl WorkEditor { | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         composer_button.connect_clicked(clone!(@strong this => move |_| { |         composer_button.connect_clicked(clone!(@strong this => move |_| { | ||||||
|             PersonSelector::new(this.backend.clone(), &this.parent, clone!(@strong this => move |person| { |             let dialog = PersonSelector::new(this.backend.clone(), &this.parent); | ||||||
|  | 
 | ||||||
|  |             dialog.set_selected_cb(clone!(@strong this => move |person| { | ||||||
|                 this.show_composer(&person); |                 this.show_composer(&person); | ||||||
|                 this.composer.replace(Some(person)); |                 this.composer.replace(Some(person)); | ||||||
|             })).show(); |             })); | ||||||
|  | 
 | ||||||
|  |             dialog.show(); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.instrument_list.set_make_widget(|instrument| { |         this.instrument_list.set_make_widget(|instrument| { | ||||||
|  |  | ||||||
|  | @ -33,7 +33,8 @@ run_command( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| sources = files( | sources = files( | ||||||
|   'backend/client.rs', |   'backend/client/mod.rs', | ||||||
|  |   'backend/client/persons.rs', | ||||||
|   'backend/library.rs', |   'backend/library.rs', | ||||||
|   'backend/mod.rs', |   'backend/mod.rs', | ||||||
|   'backend/secure.rs', |   'backend/secure.rs', | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Elias Projahn
						Elias Projahn