Add role editor

This commit is contained in:
Elias Projahn 2024-07-14 13:03:44 +02:00
parent 27ebbb4caa
commit c70abf9594
5 changed files with 217 additions and 2 deletions

42
data/ui/role_editor.blp Normal file
View file

@ -0,0 +1,42 @@
using Gtk 4.0;
using Adw 1;
template $MusicusRoleEditor: Adw.NavigationPage {
title: _("Role");
Adw.ToolbarView {
[top]
Adw.HeaderBar header_bar {}
Adw.Clamp {
Gtk.Box {
orientation: vertical;
Gtk.Label {
label: _("Name");
xalign: 0;
margin-top: 24;
styles [
"heading"
]
}
$MusicusTranslationEditor name_editor {
margin-top: 12;
}
Gtk.Button save_button {
margin-top: 24;
label: _("Create role");
clicked => $save() swapped;
styles [
"card",
"save"
]
}
}
}
}
}

View file

@ -2,8 +2,9 @@ pub mod activatable_row;
pub mod instrument_selector_popover;
pub mod person_editor;
pub mod person_selector_popover;
pub mod role_editor;
pub mod role_selector_popover;
pub mod translation_entry;
pub mod translation_editor;
pub mod translation_entry;
pub mod work_editor;
pub mod work_editor_composer_row;

110
src/editor/role_editor.rs Normal file
View file

@ -0,0 +1,110 @@
use std::cell::OnceCell;
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::glib::{self, subclass::Signal};
use once_cell::sync::Lazy;
use crate::{
db::models::Role, editor::translation_editor::MusicusTranslationEditor, library::MusicusLibrary,
};
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(file = "data/ui/role_editor.blp")]
pub struct MusicusRoleEditor {
pub navigation: OnceCell<adw::NavigationView>,
pub library: OnceCell<MusicusLibrary>,
pub role_id: OnceCell<String>,
#[template_child]
pub name_editor: TemplateChild<MusicusTranslationEditor>,
#[template_child]
pub save_button: TemplateChild<gtk::Button>,
}
#[glib::object_subclass]
impl ObjectSubclass for MusicusRoleEditor {
const NAME: &'static str = "MusicusRoleEditor";
type Type = super::MusicusRoleEditor;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
MusicusTranslationEditor::static_type();
klass.bind_template();
klass.bind_template_instance_callbacks();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for MusicusRoleEditor {
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
vec![Signal::builder("created")
.param_types([Role::static_type()])
.build()]
});
SIGNALS.as_ref()
}
}
impl WidgetImpl for MusicusRoleEditor {}
impl NavigationPageImpl for MusicusRoleEditor {}
}
glib::wrapper! {
pub struct MusicusRoleEditor(ObjectSubclass<imp::MusicusRoleEditor>)
@extends gtk::Widget, adw::NavigationPage;
}
#[gtk::template_callbacks]
impl MusicusRoleEditor {
pub fn new(
navigation: &adw::NavigationView,
library: &MusicusLibrary,
role: Option<&Role>,
) -> Self {
let obj: Self = glib::Object::new();
obj.imp().navigation.set(navigation.to_owned()).unwrap();
obj.imp().library.set(library.to_owned()).unwrap();
if let Some(role) = role {
obj.imp().save_button.set_label(&gettext("Save changes"));
obj.imp().role_id.set(role.role_id.clone()).unwrap();
obj.imp().name_editor.set_translation(&role.name);
}
obj
}
pub fn connect_created<F: Fn(&Self, Role) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.connect_local("created", true, move |values| {
let obj = values[0].get::<Self>().unwrap();
let role = values[1].get::<Role>().unwrap();
f(&obj, role);
None
})
}
#[template_callback]
fn save(&self, _: &gtk::Button) {
let library = self.imp().library.get().unwrap();
let name = self.imp().name_editor.translation();
if let Some(role_id) = self.imp().role_id.get() {
library.update_role(role_id, name).unwrap();
} else {
let role = library.create_role(name).unwrap();
self.emit_by_name::<()>("created", &[&role]);
}
self.imp().navigation.get().unwrap().pop();
}
}

View file

@ -78,7 +78,7 @@ impl MusicusTranslationEntry {
}
pub fn translation(&self) -> String {
self.imp().text().into()
self.text().to_string()
}
#[template_callback]

View file

@ -549,6 +549,29 @@ impl MusicusLibrary {
Ok(instruments)
}
pub fn search_works(&self, composer: &Person, search: &str) -> Result<Vec<Work>> {
let search = format!("%{}%", search);
let mut binding = self.imp().connection.borrow_mut();
let connection = &mut *binding.as_mut().unwrap();
let works: Vec<Work> = works::table
.inner_join(work_persons::table)
.filter(
works::name
.like(&search)
.and(work_persons::person_id.eq(&composer.person_id)),
)
.limit(9)
.select(works::all_columns)
.distinct()
.load::<tables::Work>(connection)?
.into_iter()
.map(|w| Work::from_table(w, connection))
.collect::<Result<Vec<Work>>>()?;
Ok(works)
}
pub fn composer_default_role(&self) -> Result<Role> {
let mut binding = self.imp().connection.borrow_mut();
let connection = &mut *binding.as_mut().unwrap();
@ -597,6 +620,45 @@ impl MusicusLibrary {
Ok(())
}
pub fn create_role(&self, name: TranslatedString) -> Result<Role> {
let mut binding = self.imp().connection.borrow_mut();
let connection = &mut *binding.as_mut().unwrap();
let now = Local::now().naive_local();
let role = Role {
role_id: db::generate_id(),
name,
created_at: now,
edited_at: now,
last_used_at: now,
};
diesel::insert_into(roles::table)
.values(&role)
.execute(connection)?;
Ok(role)
}
pub fn update_role(&self, id: &str, name: TranslatedString) -> Result<()> {
let mut binding = self.imp().connection.borrow_mut();
let connection = &mut *binding.as_mut().unwrap();
let now = Local::now().naive_local();
diesel::update(roles::table)
.filter(roles::role_id.eq(id))
.set((
roles::name.eq(name),
roles::edited_at.eq(now),
roles::last_used_at.eq(now),
))
.execute(connection)?;
Ok(())
}
}
#[derive(Default, Debug)]