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)
|
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.
|
/// Get mediums on which this person is performing.
|
||||||
pub fn get_mediums_for_person(&self, person_id: &str) -> Result<Vec<Medium>> {
|
pub fn get_mediums_for_person(&self, person_id: &str) -> Result<Vec<Medium>> {
|
||||||
let mut mediums: Vec<Medium> = Vec::new();
|
let mut mediums: Vec<Medium> = Vec::new();
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ pub enum Action {
|
||||||
RecordingExists(String, Sender<Result<bool>>),
|
RecordingExists(String, Sender<Result<bool>>),
|
||||||
UpdateMedium(Medium, Sender<Result<()>>),
|
UpdateMedium(Medium, Sender<Result<()>>),
|
||||||
GetMedium(String, Sender<Result<Option<Medium>>>),
|
GetMedium(String, Sender<Result<Option<Medium>>>),
|
||||||
|
GetMediumsBySourceId(String, Sender<Result<Vec<Medium>>>),
|
||||||
GetMediumsForPerson(String, Sender<Result<Vec<Medium>>>),
|
GetMediumsForPerson(String, Sender<Result<Vec<Medium>>>),
|
||||||
GetMediumsForEnsemble(String, Sender<Result<Vec<Medium>>>),
|
GetMediumsForEnsemble(String, Sender<Result<Vec<Medium>>>),
|
||||||
DeleteMedium(String, Sender<Result<()>>),
|
DeleteMedium(String, Sender<Result<()>>),
|
||||||
|
|
@ -132,6 +133,9 @@ impl DbThread {
|
||||||
GetMedium(id, sender) => {
|
GetMedium(id, sender) => {
|
||||||
sender.send(db.get_medium(&id)).unwrap();
|
sender.send(db.get_medium(&id)).unwrap();
|
||||||
}
|
}
|
||||||
|
GetMediumsBySourceId(id, sender) => {
|
||||||
|
sender.send(db.get_mediums_by_source_id(&id)).unwrap();
|
||||||
|
}
|
||||||
GetMediumsForPerson(id, sender) => {
|
GetMediumsForPerson(id, sender) => {
|
||||||
sender.send(db.get_mediums_for_person(&id)).unwrap();
|
sender.send(db.get_mediums_for_person(&id)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -346,6 +350,13 @@ impl DbThread {
|
||||||
receiver.await?
|
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.
|
/// Get all mediums on which a person performs.
|
||||||
pub async fn get_mediums_for_person(&self, id: &str) -> Result<Vec<Medium>> {
|
pub async fn get_mediums_for_person(&self, id: &str) -> Result<Vec<Medium>> {
|
||||||
let (sender, receiver) = oneshot::channel();
|
let (sender, receiver) = oneshot::channel();
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ futures-channel = "0.3.5"
|
||||||
gettext-rs = { version = "0.5.0", features = ["gettext-system"] }
|
gettext-rs = { version = "0.5.0", features = ["gettext-system"] }
|
||||||
gstreamer = "0.16.4"
|
gstreamer = "0.16.4"
|
||||||
gtk-macros = "0.2.0"
|
gtk-macros = "0.2.0"
|
||||||
|
log = "0.4.14"
|
||||||
musicus_backend = { version = "0.1.0", path = "../backend" }
|
musicus_backend = { version = "0.1.0", path = "../backend" }
|
||||||
once_cell = "1.5.2"
|
once_cell = "1.5.2"
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@
|
||||||
<gresources>
|
<gresources>
|
||||||
<gresource prefix="/de/johrpan/musicus">
|
<gresource prefix="/de/johrpan/musicus">
|
||||||
<file preprocess="xml-stripblanks">ui/editor.ui</file>
|
<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/login_dialog.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/main_screen.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_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/performance_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/player_bar.ui</file>
|
<file preprocess="xml-stripblanks">ui/player_bar.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/player_screen.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_editor;
|
||||||
|
mod medium_preview;
|
||||||
mod source_selector;
|
mod source_selector;
|
||||||
mod track_editor;
|
mod track_editor;
|
||||||
mod track_selector;
|
mod track_selector;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::import_screen::ImportScreen;
|
||||||
use super::medium_editor::MediumEditor;
|
use super::medium_editor::MediumEditor;
|
||||||
use crate::navigator::{NavigationHandle, Screen};
|
use crate::navigator::{NavigationHandle, Screen};
|
||||||
use crate::widgets::Widget;
|
use crate::widgets::Widget;
|
||||||
|
|
@ -65,8 +66,10 @@ impl Screen<(), ()> for SourceSelector {
|
||||||
spawn!(@clone this, async move {
|
spawn!(@clone this, async move {
|
||||||
match ImportSession::folder(PathBuf::from(path)).await {
|
match ImportSession::folder(PathBuf::from(path)).await {
|
||||||
Ok(session) => {
|
Ok(session) => {
|
||||||
push!(this.handle, MediumEditor, session).await;
|
// push!(this.handle, MediumEditor, session).await;
|
||||||
this.handle.pop(Some(()));
|
// this.handle.pop(Some(()));
|
||||||
|
|
||||||
|
push!(this.handle, ImportScreen, session).await;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
this.status_page.set_description(Some(&err.to_string()));
|
this.status_page.set_description(Some(&err.to_string()));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue