{
+impl InstrumentSelector {
+ pub fn new(backend: Rc, parent: &P) -> Rc
+ where
+ P: IsA,
+ {
+ // Create UI
+
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/instrument_selector.ui");
get_widget!(builder, libhandy::Window, window);
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::ListBox, list);
+ get_widget!(builder, gtk::Stack, stack);
+ get_widget!(builder, gtk::ScrolledWindow, scroll);
+ get_widget!(builder, gtk::Button, try_again_button);
- let result = Rc::new(InstrumentSelector {
- backend: backend,
- window: window,
- callback: callback,
- search_entry: search_entry,
- list: list,
+ window.set_transient_for(Some(parent));
+
+ let list = List::::new(&gettext("No instruments found."));
+ scroll.add(&list.widget);
+
+ let this = Rc::new(Self {
+ backend,
+ window,
+ server_check_button,
+ stack,
+ list,
+ selected_cb: RefCell::new(None),
});
- let c = glib::MainContext::default();
- let clone = result.clone();
- c.spawn_local(async move {
- let instruments = clone.backend.db().get_instruments().await.unwrap();
+ // Connect signals and callbacks
- for (index, instrument) in instruments.iter().enumerate() {
- let label = gtk::Label::new(Some(&instrument.name));
- 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);
-
- let row = SelectorRow::new(index.try_into().unwrap(), &label);
- row.show_all();
- clone.list.insert(&row, -1);
- }
-
- clone.list.connect_row_activated(
- clone!(@strong clone, @strong instruments => move |_, row| {
- clone.window.close();
- let row = row.get_child().unwrap().downcast::().unwrap();
- let index: usize = row.get_index().try_into().unwrap();
- (clone.callback)(instruments[index].clone());
- }),
- );
-
- clone
- .list
- .set_filter_func(Some(Box::new(clone!(@strong clone => move |row| {
- let row = row.get_child().unwrap().downcast::().unwrap();
- let index: usize = row.get_index().try_into().unwrap();
- let search = clone.search_entry.get_text().to_string();
-
- search.is_empty() || instruments[index]
- .name
- .to_lowercase()
- .contains(&clone.search_entry.get_text().to_string().to_lowercase())
- }))));
- });
-
- result
- .search_entry
- .connect_search_changed(clone!(@strong result => move |_| {
- result.list.invalidate_filter();
- }));
-
- add_button.connect_clicked(clone!(@strong result => move |_| {
+ add_button.connect_clicked(clone!(@strong this => move |_| {
let editor = InstrumentEditor::new(
- result.backend.clone(),
- &result.window,
+ this.backend.clone(),
+ &this.window,
None,
- clone!(@strong result => move |instrument| {
- result.window.close();
- (result.callback)(instrument);
- }),
);
+ editor.set_saved_cb(clone!(@strong this => move |instrument| {
+ if let Some(cb) = &*this.selected_cb.borrow() {
+ cb(instrument);
+ }
+
+ this.window.close();
+ }));
+
editor.show();
}));
- result.window.set_transient_for(Some(parent));
+ search_entry.connect_search_changed(clone!(@strong this => move |_| {
+ this.list.invalidate_filter();
+ }));
- result
+ 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_instruments().await {
+ Ok(instruments) => {
+ clone.list.show_items(instruments);
+ 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 instruments = clone.backend.db().get_instruments().await.unwrap();
+ clone.list.show_items(instruments);
+ 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();
+ }
+ }),
+ );
+
+ this.list.set_make_widget(|instrument: &Instrument| {
+ let label = gtk::Label::new(Some(&instrument.name));
+ 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 |instrument: &Instrument| {
+ let search = search_entry.get_text().to_string().to_lowercase();
+ search.is_empty() || instrument.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 instrument.
+ pub fn set_selected_cb () + 'static>(&self, cb: F) {
+ self.selected_cb.replace(Some(Box::new(cb)));
+ }
+
+ /// Show the instrument selector.
pub fn show(&self) {
self.window.show();
}
diff --git a/musicus/src/dialogs/recording/performance_editor.rs b/musicus/src/dialogs/recording/performance_editor.rs
index af95b7d..abfd0b2 100644
--- a/musicus/src/dialogs/recording/performance_editor.rs
+++ b/musicus/src/dialogs/recording/performance_editor.rs
@@ -107,10 +107,14 @@ impl PerformanceEditor {
}));
role_button.connect_clicked(clone!(@strong this => move |_| {
- InstrumentSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |role| {
+ let dialog = InstrumentSelector::new(this.backend.clone(), &this.window);
+
+ dialog.set_selected_cb(clone!(@strong this => move |role| {
this.show_role(Some(&role));
this.role.replace(Some(role));
- })).show();
+ }));
+
+ dialog.show();
}));
this.reset_role_button
diff --git a/musicus/src/dialogs/work/work_editor.rs b/musicus/src/dialogs/work/work_editor.rs
index 5693c7e..f83334b 100644
--- a/musicus/src/dialogs/work/work_editor.rs
+++ b/musicus/src/dialogs/work/work_editor.rs
@@ -178,7 +178,9 @@ impl WorkEditor {
});
add_instrument_button.connect_clicked(clone!(@strong this => move |_| {
- InstrumentSelector::new(this.backend.clone(), &this.parent, clone!(@strong this => move |instrument| {
+ let dialog = InstrumentSelector::new(this.backend.clone(), &this.parent);
+
+ dialog.set_selected_cb(clone!(@strong this => move |instrument| {
let mut instruments = this.instruments.borrow_mut();
let index = match this.instrument_list.get_selected_index() {
@@ -189,7 +191,9 @@ impl WorkEditor {
instruments.insert(index, instrument);
this.instrument_list.show_items(instruments.clone());
this.instrument_list.select_index(index);
- })).show();
+ }));
+
+ dialog.show();
}));
remove_instrument_button.connect_clicked(clone!(@strong this => move |_| {
diff --git a/musicus_server/src/main.rs b/musicus_server/src/main.rs
index 30267b6..98b39d7 100644
--- a/musicus_server/src/main.rs
+++ b/musicus_server/src/main.rs
@@ -35,6 +35,10 @@ async fn main() -> std::io::Result<()> {
.service(update_ensemble)
.service(delete_ensemble)
.service(get_ensembles)
+ .service(get_instrument)
+ .service(update_instrument)
+ .service(delete_instrument)
+ .service(get_instruments)
});
server.bind("127.0.0.1:8087")?.run().await
diff --git a/musicus_server/src/routes/instruments.rs b/musicus_server/src/routes/instruments.rs
index e69de29..2320fb6 100644
--- a/musicus_server/src/routes/instruments.rs
+++ b/musicus_server/src/routes/instruments.rs
@@ -0,0 +1,71 @@
+use super::authenticate;
+use crate::database;
+use crate::database::{DbPool, Instrument};
+use crate::error::ServerError;
+use actix_web::{delete, get, post, web, HttpResponse};
+use actix_web_httpauth::extractors::bearer::BearerAuth;
+
+/// Get an existing instrument.
+#[get("/instruments/{id}")]
+pub async fn get_instrument(
+ db: web::Data,
+ id: web::Path,
+) -> Result {
+ let data = web::block(move || {
+ let conn = db.into_inner().get()?;
+ database::get_instrument(&conn, &id.into_inner())?.ok_or(ServerError::NotFound)
+ })
+ .await?;
+
+ Ok(HttpResponse::Ok().json(data))
+}
+
+/// Add a new instrument or update an existin one. The user must be authorized to do that.
+#[post("/instruments")]
+pub async fn update_instrument(
+ auth: BearerAuth,
+ db: web::Data,
+ data: web::Json,
+) -> Result {
+ web::block(move || {
+ let conn = db.into_inner().get()?;
+ let user = authenticate(&conn, auth.token()).or(Err(ServerError::Unauthorized))?;
+
+ database::update_instrument(&conn, &data.into_inner(), &user)?;
+
+ Ok(())
+ })
+ .await?;
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+#[get("/instruments")]
+pub async fn get_instruments(db: web::Data) -> Result {
+ let data = web::block(move || {
+ let conn = db.into_inner().get()?;
+ Ok(database::get_instruments(&conn)?)
+ })
+ .await?;
+
+ Ok(HttpResponse::Ok().json(data))
+}
+
+#[delete("/instruments/{id}")]
+pub async fn delete_instrument(
+ auth: BearerAuth,
+ db: web::Data,
+ id: web::Path,
+) -> Result {
+ web::block(move || {
+ let conn = db.into_inner().get()?;
+ let user = authenticate(&conn, auth.token()).or(Err(ServerError::Unauthorized))?;
+
+ database::delete_instrument(&conn, &id.into_inner(), &user)?;
+
+ Ok(())
+ })
+ .await?;
+
+ Ok(HttpResponse::Ok().finish())
+}