mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 19:57:25 +01:00
Initial version of new import screen
This commit is contained in:
parent
e293972c0d
commit
606ee563e9
8 changed files with 361 additions and 2 deletions
|
|
@ -167,6 +167,22 @@ impl Database {
|
|||
Ok(medium)
|
||||
}
|
||||
|
||||
/// Get mediums that have a specific source ID.
|
||||
pub fn get_mediums_by_source_id(&self, source_id: &str) -> Result<Vec<Medium>> {
|
||||
let mut mediums: Vec<Medium> = Vec::new();
|
||||
|
||||
let rows = mediums::table
|
||||
.filter(mediums::discid.nullable().eq(source_id))
|
||||
.load::<MediumRow>(&self.connection)?;
|
||||
|
||||
for row in rows {
|
||||
let medium = self.get_medium_data(row)?;
|
||||
mediums.push(medium);
|
||||
}
|
||||
|
||||
Ok(mediums)
|
||||
}
|
||||
|
||||
/// Get mediums on which this person is performing.
|
||||
pub fn get_mediums_for_person(&self, person_id: &str) -> Result<Vec<Medium>> {
|
||||
let mut mediums: Vec<Medium> = Vec::new();
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ pub enum Action {
|
|||
RecordingExists(String, Sender<Result<bool>>),
|
||||
UpdateMedium(Medium, Sender<Result<()>>),
|
||||
GetMedium(String, Sender<Result<Option<Medium>>>),
|
||||
GetMediumsBySourceId(String, Sender<Result<Vec<Medium>>>),
|
||||
GetMediumsForPerson(String, Sender<Result<Vec<Medium>>>),
|
||||
GetMediumsForEnsemble(String, Sender<Result<Vec<Medium>>>),
|
||||
DeleteMedium(String, Sender<Result<()>>),
|
||||
|
|
@ -132,6 +133,9 @@ impl DbThread {
|
|||
GetMedium(id, sender) => {
|
||||
sender.send(db.get_medium(&id)).unwrap();
|
||||
}
|
||||
GetMediumsBySourceId(id, sender) => {
|
||||
sender.send(db.get_mediums_by_source_id(&id)).unwrap();
|
||||
}
|
||||
GetMediumsForPerson(id, sender) => {
|
||||
sender.send(db.get_mediums_for_person(&id)).unwrap();
|
||||
}
|
||||
|
|
@ -346,6 +350,13 @@ impl DbThread {
|
|||
receiver.await?
|
||||
}
|
||||
|
||||
/// Get all mediums with the specified source ID.
|
||||
pub async fn get_mediums_by_source_id(&self, id: &str) -> Result<Vec<Medium>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.action_sender.send(GetMediumsBySourceId(id.to_owned(), sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
/// Get all mediums on which a person performs.
|
||||
pub async fn get_mediums_for_person(&self, id: &str) -> Result<Vec<Medium>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ futures-channel = "0.3.5"
|
|||
gettext-rs = { version = "0.5.0", features = ["gettext-system"] }
|
||||
gstreamer = "0.16.4"
|
||||
gtk-macros = "0.2.0"
|
||||
log = "0.4.14"
|
||||
musicus_backend = { version = "0.1.0", path = "../backend" }
|
||||
once_cell = "1.5.2"
|
||||
rand = "0.7.3"
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
<gresources>
|
||||
<gresource prefix="/de/johrpan/musicus">
|
||||
<file preprocess="xml-stripblanks">ui/editor.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/import_screen.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/login_dialog.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/main_screen.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/medium_editor.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/medium_preview.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/performance_editor.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/player_bar.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/player_screen.ui</file>
|
||||
|
|
|
|||
177
musicus/res/ui/import_screen.ui
Normal file
177
musicus/res/ui/import_screen.ui
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="libadwaita" version="1.0"/>
|
||||
<object class="GtkBox" id="widget">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwHeaderBar">
|
||||
<property name="show-start-title-buttons">false</property>
|
||||
<property name="show-end-title-buttons">false</property>
|
||||
<property name="title-widget">
|
||||
<object class="AdwWindowTitle" id="window_title">
|
||||
<property name="title" translatable="yes">Import music</property>
|
||||
</object>
|
||||
</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="back_button">
|
||||
<property name="icon-name">go-previous-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="vexpand">True</property>
|
||||
<child>
|
||||
<object class="AdwClamp">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-start">6</property>
|
||||
<property name="margin-end">6</property>
|
||||
<property name="margin-bottom">6</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-top">12</property>
|
||||
<property name="margin-bottom">6</property>
|
||||
<property name="label" translatable="yes">Matching metadata</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<child>
|
||||
<object class="GtkStack" id="matching_stack">
|
||||
<property name="transition-type">crossfade</property>
|
||||
<property name="vhomogeneous">false</property>
|
||||
<property name="interpolate-size">true</property>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="name">loading</property>
|
||||
<property name="child">
|
||||
<object class="GtkListBox">
|
||||
<property name="selection-mode">none</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Loading…</property>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="spinning">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="name">error</property>
|
||||
<property name="child">
|
||||
<object class="GtkListBox">
|
||||
<property name="selection-mode">none</property>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="error_row">
|
||||
<property name="title" translatable="yes">Error while searching for matching metadata</property>
|
||||
<property name="activatable">True</property>
|
||||
<property name="activatable-widget">try_again_button</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="try_again_button">
|
||||
<property name="icon-name">view-refresh-symbolic</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="name">empty</property>
|
||||
<property name="child">
|
||||
<object class="GtkListBox">
|
||||
<property name="selection-mode">none</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">No matching metadata found</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="name">content</property>
|
||||
<property name="child">
|
||||
<object class="GtkListBox" id="matching_list">
|
||||
<property name="selection-mode">none</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-top">12</property>
|
||||
<property name="margin-bottom">6</property>
|
||||
<property name="label" translatable="yes">Manually add metadata</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<child>
|
||||
<object class="GtkListBox">
|
||||
<property name="selection-mode">none</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Select existing medium</property>
|
||||
<property name="activatable">True</property>
|
||||
<property name="activatable-widget">select_button</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="select_button">
|
||||
<property name="label" translatable="yes">Select</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Add a new medium</property>
|
||||
<property name="activatable">True</property>
|
||||
<property name="activatable-widget">add_button</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="add_button">
|
||||
<property name="label" translatable="yes">Add</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
147
musicus/src/import/import_screen.rs
Normal file
147
musicus/src/import/import_screen.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
use super::medium_editor::MediumEditor;
|
||||
use super::medium_preview::MediumPreview;
|
||||
use crate::navigator::{NavigationHandle, Screen};
|
||||
use crate::widgets::Widget;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libadwaita::prelude::*;
|
||||
use log::debug;
|
||||
use musicus_backend::db::Medium;
|
||||
use musicus_backend::import::ImportSession;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A dialog for selecting metadata when importing music.
|
||||
pub struct ImportScreen {
|
||||
handle: NavigationHandle<()>,
|
||||
session: Arc<ImportSession>,
|
||||
widget: gtk::Box,
|
||||
matching_stack: gtk::Stack,
|
||||
error_row: libadwaita::ActionRow,
|
||||
matching_list: gtk::ListBox,
|
||||
}
|
||||
|
||||
impl ImportScreen {
|
||||
/// Find matching mediums on the server.
|
||||
fn load_matches(self: &Rc<Self>) {
|
||||
self.matching_stack.set_visible_child_name("loading");
|
||||
|
||||
let this = self;
|
||||
spawn!(@clone this, async move {
|
||||
match this.handle.backend.db().get_mediums_by_source_id(this.session.source_id()).await {
|
||||
Ok(mediums) => {
|
||||
if !mediums.is_empty() {
|
||||
this.show_matches(mediums);
|
||||
this.matching_stack.set_visible_child_name("content");
|
||||
} else {
|
||||
this.matching_stack.set_visible_child_name("empty");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
this.error_row.set_subtitle(Some(&err.to_string()));
|
||||
this.matching_stack.set_visible_child_name("error");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Populate the list of matches
|
||||
fn show_matches(self: &Rc<Self>, mediums: Vec<Medium>) {
|
||||
if let Some(mut child) = self.matching_list.get_first_child() {
|
||||
loop {
|
||||
let next_child = child.get_next_sibling();
|
||||
self.matching_list.remove(&child);
|
||||
|
||||
match next_child {
|
||||
Some(next_child) => child = next_child,
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let this = self;
|
||||
|
||||
for medium in mediums {
|
||||
let row = libadwaita::ActionRowBuilder::new()
|
||||
.title(&medium.name)
|
||||
.subtitle(&format!("{} Recordings", medium.tracks.len()))
|
||||
.activatable(true)
|
||||
.build();
|
||||
|
||||
row.connect_activated(clone!(@weak this => move |_| {
|
||||
debug!("Medium selected: {}", medium.name);
|
||||
}));
|
||||
|
||||
this.matching_list.append(&row);
|
||||
}
|
||||
}
|
||||
|
||||
/// Select a medium from somewhere and present a preview.
|
||||
fn select_medium(self: &Rc<Self>, medium: Medium) {
|
||||
let this = self;
|
||||
|
||||
spawn!(@clone this, async move {
|
||||
push!(this.handle, MediumPreview, (this.session.clone(), medium)).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Screen<Arc<ImportSession>, ()> for ImportScreen {
|
||||
/// Create a new import screen.
|
||||
fn new(session: Arc<ImportSession>, handle: NavigationHandle<()>) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/import_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::Stack, matching_stack);
|
||||
get_widget!(builder, gtk::Button, try_again_button);
|
||||
get_widget!(builder, libadwaita::ActionRow, error_row);
|
||||
get_widget!(builder, gtk::ListBox, matching_list);
|
||||
get_widget!(builder, gtk::Button, select_button);
|
||||
get_widget!(builder, gtk::Button, add_button);
|
||||
|
||||
let this = Rc::new(Self {
|
||||
handle,
|
||||
session,
|
||||
widget,
|
||||
matching_stack,
|
||||
error_row,
|
||||
matching_list,
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
back_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.handle.pop(None);
|
||||
}));
|
||||
|
||||
try_again_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.load_matches();
|
||||
}));
|
||||
|
||||
select_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
debug!("TODO: Show medium selector.");
|
||||
}));
|
||||
|
||||
add_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
spawn!(@clone this, async move {
|
||||
push!(this.handle, MediumEditor, Arc::clone(&this.session)).await;
|
||||
});
|
||||
}));
|
||||
|
||||
// Initialize the view
|
||||
|
||||
this.load_matches();
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for ImportScreen {
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
mod import_screen;
|
||||
mod medium_editor;
|
||||
mod medium_preview;
|
||||
mod source_selector;
|
||||
mod track_editor;
|
||||
mod track_selector;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use super::import_screen::ImportScreen;
|
||||
use super::medium_editor::MediumEditor;
|
||||
use crate::navigator::{NavigationHandle, Screen};
|
||||
use crate::widgets::Widget;
|
||||
|
|
@ -65,8 +66,10 @@ impl Screen<(), ()> for SourceSelector {
|
|||
spawn!(@clone this, async move {
|
||||
match ImportSession::folder(PathBuf::from(path)).await {
|
||||
Ok(session) => {
|
||||
push!(this.handle, MediumEditor, session).await;
|
||||
this.handle.pop(Some(()));
|
||||
// push!(this.handle, MediumEditor, session).await;
|
||||
// this.handle.pop(Some(()));
|
||||
|
||||
push!(this.handle, ImportScreen, session).await;
|
||||
}
|
||||
Err(err) => {
|
||||
this.status_page.set_description(Some(&err.to_string()));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue