Remove server synchronization code

This commit (tries to) remove all code for synchronyzing to a music
metadata server. Because the intended use cases of the application have
shifted over time, this isn't a central feature anymore. However, it
may well be decided to reintroduce the functionality at some point in
the future.
This commit is contained in:
Elias Projahn 2022-01-23 13:18:37 +01:00
parent 384ca255f3
commit f165c6cae8
48 changed files with 96 additions and 2633 deletions

View file

@ -5,17 +5,5 @@
<default>""</default>
<summary>Path to the music library folder</summary>
</key>
<key name="server-url" type="s">
<default>"https://wolfgang.johrpan.de"</default>
<summary>URL of the Wolfgang server to use</summary>
</key>
<key name="use-server" type="b">
<default>true</default>
<summary>Whether to use the Wolfgang server</summary>
<description>
This setting determines whether the Wolfgang server will be used for
finding new items as well as to upload new additions and edits.
</description>
</key>
</schema>
</schemalist>

View file

@ -3,7 +3,6 @@
<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>
@ -16,7 +15,6 @@
<file preprocess="xml-stripblanks">ui/screen.ui</file>
<file preprocess="xml-stripblanks">ui/section.ui</file>
<file preprocess="xml-stripblanks">ui/selector.ui</file>
<file preprocess="xml-stripblanks">ui/server_dialog.ui</file>
<file preprocess="xml-stripblanks">ui/source_selector.ui</file>
<file preprocess="xml-stripblanks">ui/track_editor.ui</file>
<file preprocess="xml-stripblanks">ui/track_selector.ui</file>

View file

@ -45,12 +45,6 @@
</attributes>
</object>
</child>
<child>
<object class="GtkCheckButton" id="server_check_button">
<property name="label" translatable="yes">Use the Musicus server</property>
<property name="active">True</property>
</object>
</child>
</object>
</child>
<child>

View file

@ -1,223 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0"/>
<object class="GtkStack" id="widget">
<property name="transition-type">crossfade</property>
<child>
<object class="GtkStackPage">
<property name="name">content</property>
<property name="child">
<object class="GtkBox">
<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="GtkLabel">
</object>
</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="yes">Cancel</property>
</object>
</child>
<child type="end">
<object class="GtkButton" id="login_button">
<property name="label" translatable="yes">Login</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="revealed">False</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="vexpand">true</property>
<child>
<object class="AdwClamp">
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="margin-top">18</property>
<property name="margin-bottom">12</property>
<property name="maximum-size">800</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Login to existing account</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkFrame">
<property name="valign">start</property>
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<child>
<object class="AdwActionRow">
<property name="focusable">False</property>
<property name="title" translatable="yes">Username</property>
<property name="activatable-widget">username_entry</property>
<child>
<object class="GtkEntry" id="username_entry">
<property name="valign">center</property>
<property name="hexpand">True</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="focusable">False</property>
<property name="title" translatable="yes">Password</property>
<property name="activatable-widget">password_entry</property>
<child>
<object class="GtkEntry" id="password_entry">
<property name="valign">center</property>
<property name="hexpand">True</property>
<property name="visibility">False</property>
<property name="input-purpose">password</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="register_box">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Create a new account</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkFrame">
<property name="valign">start</property>
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<child>
<object class="AdwActionRow">
<property name="focusable">False</property>
<property name="title" translatable="yes">Register a new account</property>
<property name="activatable-widget">register_button</property>
<child>
<object class="GtkButton" id="register_button">
<property name="label" translatable="yes">Start</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="logout_box">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="visible">false</property>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Logout</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkFrame">
<property name="valign">start</property>
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<child>
<object class="AdwActionRow">
<property name="focusable">False</property>
<property name="title" translatable="yes">Don't use an account</property>
<property name="activatable-widget">logout_button</property>
<child>
<object class="GtkButton" id="logout_button">
<property name="label" translatable="yes">Logout</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">loading</property>
<property name="child">
<object class="GtkBox">
<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="GtkLabel">
<property name="label" translatable="yes">Login</property>
<style>
<class name="title"/>
</style>
</object>
</property>
</object>
</child>
<child>
<object class="GtkSpinner">
<property name="spinning">true</property>
<property name="hexpand">true</property>
<property name="vexpand">true</property>
<property name="halign">center</property>
<property name="valign">center</property>
</object>
</child>
</object>
</property>
</object>
</child>
</object>
</interface>

View file

@ -82,19 +82,6 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="focusable">False</property>
<property name="title" translatable="yes">Publish to the server</property>
<property name="activatable-widget">publish_switch</property>
<child>
<object class="GtkSwitch" id="publish_switch">
<property name="valign">center</property>
<property name="active">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>

View file

@ -29,49 +29,7 @@
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Server connection</property>
<child>
<object class="AdwActionRow" id="url_row">
<property name="focusable">False</property>
<property name="title" translatable="yes">Server URL</property>
<property name="activatable-widget">url_button</property>
<property name="subtitle" translatable="yes">Not set</property>
<child>
<object class="GtkButton" id="url_button">
<property name="label" translatable="yes">Change</property>
<property name="receives-default">True</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow" id="login_row">
<property name="focusable">False</property>
<property name="title" translatable="yes">Login credentials</property>
<property name="activatable-widget">login_button</property>
<property name="subtitle" translatable="yes">Not logged in</property>
<child>
<object class="GtkButton" id="login_button">
<property name="label" translatable="yes">Change</property>
<property name="receives-default">True</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<object class="GtkSizeGroup">
<widgets>
<widget name="select_music_library_path_button"/>
<widget name="url_button"/>
<widget name="login_button"/>
</widgets>
</object>
</interface>

View file

@ -99,19 +99,6 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="focusable">False</property>
<property name="title" translatable="yes">Publish to the server</property>
<property name="activatable-widget">upload_switch</property>
<child>
<object class="GtkSwitch" id="upload_switch">
<property name="valign">center</property>
<property name="active">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>

View file

@ -60,13 +60,6 @@
<property name="placeholder-text" translatable="yes">Search …</property>
</object>
</child>
<child>
<object class="GtkCheckButton" id="server_check_button">
<property name="label" translatable="yes">Use the Musicus server</property>
<property name="halign">start</property>
<property name="active">True</property>
</object>
</child>
</object>
</child>
</object>
@ -118,57 +111,6 @@
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">error</property>
<property name="child">
<object class="GtkBox">
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin-start">18</property>
<property name="margin-end">18</property>
<property name="margin-top">18</property>
<property name="margin-bottom">18</property>
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<child>
<object class="GtkImage">
<property name="opacity">0.5</property>
<property name="pixel-size">80</property>
<property name="icon-name">network-error-symbolic</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="opacity">0.5</property>
<property name="label" translatable="yes">An error occured!</property>
<attributes>
<attribute name="size" value="16384"/>
</attributes>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="opacity">0.5</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>
</child>
<child>
<object class="GtkButton" id="try_again_button">
<property name="label" translatable="yes">Try again</property>
<property name="halign">center</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
</property>
</object>
</child>
</object>
</child>
</object>

View file

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0"/>
<object class="AdwWindow" id="window">
<property name="modal">True</property>
<child>
<object class="GtkBox">
<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="GtkLabel">
<property name="label" translatable="yes">Server</property>
<style>
<class name="title"/>
</style>
</object>
</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="yes">Cancel</property>
</object>
</child>
<child type="end">
<object class="GtkButton" id="set_button">
<property name="label" translatable="yes">Set</property>
<property name="has-default">True</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<child>
<object class="AdwActionRow">
<property name="focusable">False</property>
<property name="title" translatable="yes">URL</property>
<property name="activatable-widget">url_entry</property>
<child>
<object class="GtkEntry" id="url_entry">
<property name="valign">center</property>
<property name="hexpand">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0"/>
<requires lib="gtk" version="4.0" />
<requires lib="libadwaita" version="1.0" />
<object class="GtkStack" id="widget">
<child>
<object class="GtkStackPage">
@ -17,13 +17,13 @@
<object class="GtkLabel">
<property name="label" translatable="yes">Work</property>
<style>
<class name="title"/>
<class name="title" />
</style>
</object>
</property>
<child>
<object class="GtkButton" id="back_button">
<property name="icon-name">go-previous-symbolic</property>
<property name="icon-name">go-previous-symbolic</property>
</object>
</child>
<child type="end">
@ -31,7 +31,7 @@
<property name="sensitive">False</property>
<property name="icon-name">object-select-symbolic</property>
<style>
<class name="suggested-action"/>
<class name="suggested-action" />
</style>
</object>
</child>
@ -64,7 +64,7 @@
<property name="margin-bottom">6</property>
<property name="label" translatable="yes">Overview</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="weight" value="bold" />
</attributes>
</object>
</child>
@ -99,19 +99,6 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="focusable">False</property>
<property name="title" translatable="yes">Publish to the server</property>
<property name="activatable-widget">upload_switch</property>
<child>
<object class="GtkSwitch" id="upload_switch">
<property name="valign">center</property>
<property name="active">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
@ -128,7 +115,7 @@
<property name="hexpand">True</property>
<property name="label" translatable="yes">Instruments</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="weight" value="bold" />
</attributes>
</object>
</child>
@ -141,7 +128,7 @@
</object>
</child>
<child>
<object class="GtkFrame" id="instrument_frame"/>
<object class="GtkFrame" id="instrument_frame" />
</child>
<child>
<object class="GtkBox">
@ -155,7 +142,7 @@
<property name="hexpand">True</property>
<property name="label" translatable="yes">Structure</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="weight" value="bold" />
</attributes>
</object>
</child>
@ -174,7 +161,7 @@
</object>
</child>
<child>
<object class="GtkFrame" id="structure_frame"/>
<object class="GtkFrame" id="structure_frame" />
</child>
</object>
</child>
@ -200,7 +187,7 @@
<object class="GtkLabel">
<property name="label" translatable="yes">Work</property>
<style>
<class name="title"/>
<class name="title" />
</style>
</object>
</property>

View file

@ -1,5 +1,5 @@
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::{Editor, EntryRow, Section, UploadSection, Widget};
use crate::widgets::{Editor, EntryRow, Section, Widget};
use anyhow::Result;
use gettextrs::gettext;
use glib::clone;
@ -16,7 +16,6 @@ pub struct EnsembleEditor {
editor: Editor,
name: EntryRow,
upload: Rc<UploadSection>,
}
impl Screen<Option<Ensemble>, Ensemble> for EnsembleEditor {
@ -33,10 +32,7 @@ impl Screen<Option<Ensemble>, Ensemble> for EnsembleEditor {
list.append(&name.widget);
let section = Section::new(&gettext("General"), &list);
let upload = UploadSection::new(Rc::clone(&handle.backend));
editor.add_content(&section.widget);
editor.add_content(&upload.widget);
let id = match ensemble {
Some(ensemble) => {
@ -51,7 +47,6 @@ impl Screen<Option<Ensemble>, Ensemble> for EnsembleEditor {
id,
editor,
name,
upload,
});
// Connect signals and callbacks
@ -91,7 +86,7 @@ impl EnsembleEditor {
self.editor.set_may_save(!self.name.get_text().is_empty());
}
/// Save the ensemble and possibly upload it to the server.
/// Save the ensemble.
async fn save(&self) -> Result<Ensemble> {
let name = self.name.get_text();
@ -100,15 +95,12 @@ impl EnsembleEditor {
name,
};
if self.upload.get_active() {
self.handle.backend.cl().post_ensemble(&ensemble).await?;
}
self.handle
.backend
.db()
.update_ensemble(ensemble.clone())
.await?;
self.handle.backend.library_changed();
Ok(ensemble)

View file

@ -1,5 +1,5 @@
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::{Editor, EntryRow, Section, UploadSection, Widget};
use crate::widgets::{Editor, EntryRow, Section, Widget};
use anyhow::Result;
use gettextrs::gettext;
use glib::clone;
@ -16,7 +16,6 @@ pub struct InstrumentEditor {
editor: Editor,
name: EntryRow,
upload: Rc<UploadSection>,
}
impl Screen<Option<Instrument>, Instrument> for InstrumentEditor {
@ -33,10 +32,7 @@ impl Screen<Option<Instrument>, Instrument> for InstrumentEditor {
list.append(&name.widget);
let section = Section::new(&gettext("General"), &list);
let upload = UploadSection::new(Rc::clone(&handle.backend));
editor.add_content(&section.widget);
editor.add_content(&upload.widget);
let id = match instrument {
Some(instrument) => {
@ -51,7 +47,6 @@ impl Screen<Option<Instrument>, Instrument> for InstrumentEditor {
id,
editor,
name,
upload,
});
// Connect signals and callbacks
@ -91,7 +86,7 @@ impl InstrumentEditor {
self.editor.set_may_save(!self.name.get_text().is_empty());
}
/// Save the instrument and possibly upload it to the server.
/// Save the instrument.
async fn save(&self) -> Result<Instrument> {
let name = self.name.get_text();
@ -100,19 +95,12 @@ impl InstrumentEditor {
name,
};
if self.upload.get_active() {
self.handle
.backend
.cl()
.post_instrument(&instrument)
.await?;
}
self.handle
.backend
.db()
.update_instrument(instrument.clone())
.await?;
self.handle.backend.library_changed();
Ok(instrument)

View file

@ -1,5 +1,5 @@
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::{Editor, EntryRow, Section, UploadSection, Widget};
use crate::widgets::{Editor, EntryRow, Section, Widget};
use anyhow::Result;
use gettextrs::gettext;
use glib::clone;
@ -17,7 +17,6 @@ pub struct PersonEditor {
editor: Editor,
first_name: EntryRow,
last_name: EntryRow,
upload: Rc<UploadSection>,
}
impl Screen<Option<Person>, Person> for PersonEditor {
@ -37,10 +36,7 @@ impl Screen<Option<Person>, Person> for PersonEditor {
list.append(&last_name.widget);
let section = Section::new(&gettext("General"), &list);
let upload = UploadSection::new(Rc::clone(&handle.backend));
editor.add_content(&section.widget);
editor.add_content(&upload.widget);
let id = match person {
Some(person) => {
@ -58,7 +54,6 @@ impl Screen<Option<Person>, Person> for PersonEditor {
editor,
first_name,
last_name,
upload,
});
// Connect signals and callbacks
@ -104,7 +99,7 @@ impl PersonEditor {
);
}
/// Save the person and possibly upload it to the server.
/// Save the person.
async fn save(self: &Rc<Self>) -> Result<Person> {
let first_name = self.first_name.get_text();
let last_name = self.last_name.get_text();
@ -115,10 +110,6 @@ impl PersonEditor {
last_name,
};
if self.upload.get_active() {
self.handle.backend.cl().post_person(&person).await?;
}
self.handle
.backend
.db()

View file

@ -19,7 +19,6 @@ pub struct RecordingEditor {
info_bar: gtk::InfoBar,
work_row: adw::ActionRow,
comment_entry: gtk::Entry,
upload_switch: gtk::Switch,
performance_list: Rc<List>,
id: String,
work: RefCell<Option<Work>>,
@ -40,12 +39,9 @@ impl Screen<Option<Recording>, Recording> for RecordingEditor {
get_widget!(builder, adw::ActionRow, work_row);
get_widget!(builder, gtk::Button, work_button);
get_widget!(builder, gtk::Entry, comment_entry);
get_widget!(builder, gtk::Switch, upload_switch);
get_widget!(builder, gtk::Frame, performance_frame);
get_widget!(builder, gtk::Button, add_performer_button);
upload_switch.set_active(handle.backend.use_server());
let performance_list = List::new();
performance_frame.set_child(Some(&performance_list.widget));
@ -64,7 +60,6 @@ impl Screen<Option<Recording>, Recording> for RecordingEditor {
info_bar,
work_row,
comment_entry,
upload_switch,
performance_list,
id,
work: RefCell::new(work),
@ -183,7 +178,7 @@ impl RecordingEditor {
self.save_button.set_sensitive(true);
}
/// Save the recording and possibly upload it to the server.
/// Save the recording.
async fn save(self: &Rc<Self>) -> Result<Recording> {
let recording = Recording {
id: self.id.clone(),
@ -196,11 +191,6 @@ impl RecordingEditor {
performances: self.performances.borrow().clone(),
};
let upload = self.upload_switch.state();
if upload {
self.handle.backend.cl().post_recording(&recording).await?;
}
self.handle
.backend
.db()

View file

@ -36,7 +36,6 @@ pub struct WorkEditor {
title_entry: gtk::Entry,
info_bar: gtk::InfoBar,
composer_row: adw::ActionRow,
upload_switch: gtk::Switch,
instrument_list: Rc<List>,
part_list: Rc<List>,
id: String,
@ -59,7 +58,6 @@ impl Screen<Option<Work>, Work> for WorkEditor {
get_widget!(builder, gtk::Entry, title_entry);
get_widget!(builder, gtk::Button, composer_button);
get_widget!(builder, adw::ActionRow, composer_row);
get_widget!(builder, gtk::Switch, upload_switch);
get_widget!(builder, gtk::Frame, instrument_frame);
get_widget!(builder, gtk::Button, add_instrument_button);
get_widget!(builder, gtk::Frame, structure_frame);
@ -92,8 +90,6 @@ impl Screen<Option<Work>, Work> for WorkEditor {
None => (generate_id(), None, Vec::new(), Vec::new()),
};
upload_switch.set_active(handle.backend.use_server());
let this = Rc::new(Self {
handle,
widget,
@ -102,7 +98,6 @@ impl Screen<Option<Work>, Work> for WorkEditor {
info_bar,
title_entry,
composer_row,
upload_switch,
instrument_list,
part_list,
composer: RefCell::new(composer),
@ -317,7 +312,7 @@ impl WorkEditor {
.set_sensitive(!self.title_entry.text().is_empty() && self.composer.borrow().is_some());
}
/// Save the work and possibly upload it to the server.
/// Save the work.
async fn save(self: &Rc<Self>) -> Result<Work> {
let mut section_count: usize = 0;
let mut parts = Vec::new();
@ -348,11 +343,6 @@ impl WorkEditor {
sections,
};
let upload = self.upload_switch.state();
if upload {
self.handle.backend.cl().post_work(&work).await?;
}
self.handle
.backend
.db()

View file

@ -8,7 +8,6 @@ use glib::clone;
use gtk_macros::get_widget;
use musicus_backend::db::Medium;
use musicus_backend::import::ImportSession;
use musicus_backend::Error;
use std::rc::Rc;
use std::sync::Arc;
@ -17,24 +16,19 @@ pub struct ImportScreen {
handle: NavigationHandle<()>,
session: Arc<ImportSession>,
widget: gtk::Box,
server_check_button: gtk::CheckButton,
matching_stack: gtk::Stack,
error_row: adw::ActionRow,
matching_list: gtk::ListBox,
}
impl ImportScreen {
/// Find matching mediums on the server.
/// Find matching mediums in the library.
fn load_matches(self: &Rc<Self>) {
self.matching_stack.set_visible_child_name("loading");
let this = self;
spawn!(@clone this, async move {
let mediums: Result<Vec<Medium>, Error> = if this.server_check_button.is_active() {
this.handle.backend.cl().get_mediums_by_discid(this.session.source_id()).await.map_err(|err| err.into())
} else {
this.handle.backend.db().get_mediums_by_source_id(this.session.source_id()).await.map_err(|err| err.into())
};
let mediums = this.handle.backend.db().get_mediums_by_source_id(this.session.source_id()).await;
match mediums {
Ok(mediums) => {
@ -113,18 +107,14 @@ impl Screen<Arc<ImportSession>, ()> for ImportScreen {
get_widget!(builder, gtk::Stack, matching_stack);
get_widget!(builder, gtk::Button, try_again_button);
get_widget!(builder, adw::ActionRow, error_row);
get_widget!(builder, gtk::CheckButton, server_check_button);
get_widget!(builder, gtk::ListBox, matching_list);
get_widget!(builder, gtk::Button, select_button);
get_widget!(builder, gtk::Button, add_button);
server_check_button.set_active(handle.backend.use_server());
let this = Rc::new(Self {
handle,
session,
widget,
server_check_button,
matching_stack,
error_row,
matching_list,
@ -136,12 +126,6 @@ impl Screen<Arc<ImportSession>, ()> for ImportScreen {
this.handle.pop(None);
}));
this.server_check_button
.connect_toggled(clone!(@weak this => move |_| {
this.handle.backend.set_use_server(this.server_check_button.is_active());
this.load_matches();
}));
try_again_button.connect_clicked(clone!(@weak this => move |_| {
this.load_matches();
}));

View file

@ -18,7 +18,6 @@ pub struct MediumEditor {
widget: gtk::Stack,
done_button: gtk::Button,
name_entry: gtk::Entry,
publish_switch: gtk::Switch,
status_page: adw::StatusPage,
track_set_list: Rc<List>,
track_sets: RefCell<Vec<TrackSetData>>,
@ -38,15 +37,12 @@ impl Screen<(Arc<ImportSession>, Option<Medium>), Medium> for MediumEditor {
get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Button, done_button);
get_widget!(builder, gtk::Entry, name_entry);
get_widget!(builder, gtk::Switch, publish_switch);
get_widget!(builder, gtk::Button, add_button);
get_widget!(builder, gtk::Frame, frame);
get_widget!(builder, adw::StatusPage, status_page);
get_widget!(builder, gtk::Button, try_again_button);
get_widget!(builder, gtk::Button, cancel_button);
publish_switch.set_active(handle.backend.use_server());
let list = List::new();
frame.set_child(Some(&list.widget));
@ -56,7 +52,6 @@ impl Screen<(Arc<ImportSession>, Option<Medium>), Medium> for MediumEditor {
widget,
done_button,
name_entry,
publish_switch,
status_page,
track_set_list: list,
track_sets: RefCell::new(Vec::new()),
@ -100,11 +95,6 @@ impl Screen<(Arc<ImportSession>, Option<Medium>), Medium> for MediumEditor {
});
}));
this.publish_switch
.connect_state_notify(clone!(@weak this => move |_| {
this.handle.backend.set_use_server(this.publish_switch.state());
}));
this.track_set_list.set_make_widget_cb(
clone!(@weak this => @default-panic, move |index| {
let track_set = &this.track_sets.borrow()[index];
@ -188,7 +178,7 @@ impl MediumEditor {
);
}
/// Create the medium and, if necessary, upload it to the server.
/// Create the medium.
async fn save(&self) -> Result<Medium> {
// Convert the track set data to real track sets.
@ -214,11 +204,6 @@ impl MediumEditor {
tracks,
};
let upload = self.publish_switch.state();
if upload {
self.handle.backend.cl().post_medium(&medium).await?;
}
// The medium is not added to the database, because the track paths are not known until the
// medium is actually imported into the music library. This step will be handled by the
// medium preview dialog.

View file

@ -29,10 +29,4 @@ impl NavigatorWindow {
this
}
/// Make the wrapped window transient. This will make the window modal.
pub fn set_transient_for<W: IsA<gtk::Window>>(&self, window: &W) {
self.window.set_modal(true);
self.window.set_transient_for(Some(window));
}
}

View file

@ -1,4 +1,3 @@
use crate::navigator::NavigatorWindow;
use adw::prelude::*;
use gettextrs::gettext;
use glib::clone;
@ -6,21 +5,11 @@ use gtk_macros::get_widget;
use musicus_backend::Backend;
use std::rc::Rc;
mod login;
use login::LoginDialog;
mod server;
use server::ServerDialog;
mod register;
/// A dialog for configuring the app.
pub struct Preferences {
backend: Rc<Backend>,
window: adw::Window,
music_library_path_row: adw::ActionRow,
url_row: adw::ActionRow,
login_row: adw::ActionRow,
}
impl Preferences {
@ -32,10 +21,6 @@ impl Preferences {
get_widget!(builder, adw::Window, window);
get_widget!(builder, adw::ActionRow, music_library_path_row);
get_widget!(builder, gtk::Button, select_music_library_path_button);
get_widget!(builder, adw::ActionRow, url_row);
get_widget!(builder, gtk::Button, url_button);
get_widget!(builder, adw::ActionRow, login_row);
get_widget!(builder, gtk::Button, login_button);
window.set_transient_for(Some(parent));
@ -43,8 +28,6 @@ impl Preferences {
backend,
window,
music_library_path_row,
url_row,
login_row,
});
// Connect signals and callbacks
@ -80,31 +63,6 @@ impl Preferences {
dialog.show();
}));
url_button.connect_clicked(clone!(@strong this => move |_| {
let dialog = ServerDialog::new(this.backend.clone(), &this.window);
dialog.set_selected_cb(clone!(@strong this => move |url| {
this.url_row.set_subtitle(&url);
}));
dialog.show();
}));
login_button.connect_clicked(clone!(@strong this => move |_| {
let window = NavigatorWindow::new(this.backend.clone());
window.set_transient_for(&this.window);
spawn!(@clone this, async move {
if let Some(data) = replace!(window.navigator, LoginDialog, this.backend.get_login_data()).await {
if let Some(data) = data {
this.login_row.set_subtitle(&data.username);
} else {
this.login_row.set_subtitle(&gettext("Not logged in"));
}
}
});
}));
// Initialize
if let Some(path) = this.backend.get_music_library_path() {
@ -112,14 +70,6 @@ impl Preferences {
.set_subtitle(path.to_str().unwrap());
}
if let Some(url) = this.backend.get_server_url() {
this.url_row.set_subtitle(&url);
}
if let Some(data) = this.backend.get_login_data() {
this.login_row.set_subtitle(&data.username);
}
this
}

View file

@ -1,98 +0,0 @@
use super::register::RegisterDialog;
use crate::navigator::{NavigationHandle, Screen};
use crate::push;
use crate::widgets::Widget;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use musicus_backend::client::LoginData;
use std::rc::Rc;
/// A dialog for entering login credentials.
pub struct LoginDialog {
handle: NavigationHandle<Option<LoginData>>,
widget: gtk::Stack,
info_bar: gtk::InfoBar,
username_entry: gtk::Entry,
password_entry: gtk::Entry,
}
impl Screen<Option<LoginData>, Option<LoginData>> for LoginDialog {
fn new(data: Option<LoginData>, handle: NavigationHandle<Option<LoginData>>) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/login_dialog.ui");
get_widget!(builder, gtk::Stack, widget);
get_widget!(builder, gtk::InfoBar, info_bar);
get_widget!(builder, gtk::Button, cancel_button);
get_widget!(builder, gtk::Button, login_button);
get_widget!(builder, gtk::Entry, username_entry);
get_widget!(builder, gtk::Entry, password_entry);
get_widget!(builder, gtk::Box, register_box);
get_widget!(builder, gtk::Button, register_button);
get_widget!(builder, gtk::Box, logout_box);
get_widget!(builder, gtk::Button, logout_button);
if let Some(data) = data {
username_entry.set_text(&data.username);
register_box.hide();
logout_box.show();
}
let this = Rc::new(Self {
handle,
widget,
info_bar,
username_entry,
password_entry,
});
// Connect signals and callbacks
cancel_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.pop(None);
}));
login_button.connect_clicked(clone!(@weak this => move |_| {
this.widget.set_visible_child_name("loading");
let data = LoginData {
username: this.username_entry.text().to_string(),
password: this.password_entry.text().to_string(),
};
spawn!(@clone this, async move {
this.handle.backend.set_login_data(Some(data.clone())).await;
if this.handle.backend.cl().login().await.unwrap() {
this.handle.pop(Some(Some(data)));
} else {
this.widget.set_visible_child_name("content");
this.info_bar.set_revealed(true);
}
});
}));
register_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
if let Some(data) = push!(this.handle, RegisterDialog).await {
this.handle.pop(Some(Some(data)));
}
});
}));
logout_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move {
this.handle.backend.set_login_data(None).await;
this.handle.pop(Some(None));
});
}));
this
}
}
impl Widget for LoginDialog {
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast()
}
}

View file

@ -1,118 +0,0 @@
use crate::navigator::{NavigationHandle, Screen};
use crate::widgets::Widget;
use adw::prelude::*;
use glib::clone;
use gtk_macros::get_widget;
use musicus_backend::client::{LoginData, UserRegistration};
use std::cell::RefCell;
use std::rc::Rc;
/// A dialog for creating a new user account.
pub struct RegisterDialog {
handle: NavigationHandle<LoginData>,
widget: gtk::Stack,
username_entry: gtk::Entry,
email_entry: gtk::Entry,
password_entry: gtk::Entry,
repeat_password_entry: gtk::Entry,
captcha_row: adw::ActionRow,
captcha_entry: gtk::Entry,
captcha_id: RefCell<Option<String>>,
}
impl Screen<(), LoginData> for RegisterDialog {
/// Create a new register dialog.
fn new(_: (), handle: NavigationHandle<LoginData>) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/register_dialog.ui");
get_widget!(builder, gtk::Stack, widget);
get_widget!(builder, gtk::Button, cancel_button);
get_widget!(builder, gtk::Button, register_button);
get_widget!(builder, gtk::Entry, username_entry);
get_widget!(builder, gtk::Entry, email_entry);
get_widget!(builder, gtk::Entry, password_entry);
get_widget!(builder, gtk::Entry, repeat_password_entry);
get_widget!(builder, adw::ActionRow, captcha_row);
get_widget!(builder, gtk::Entry, captcha_entry);
let this = Rc::new(Self {
handle,
widget,
username_entry,
email_entry,
password_entry,
repeat_password_entry,
captcha_row,
captcha_entry,
captcha_id: RefCell::new(None),
});
// Connect signals and callbacks
cancel_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.pop(None);
}));
register_button.connect_clicked(clone!(@weak this => move |_| {
let password = this.password_entry.text().to_string();
let repeat = this.repeat_password_entry.text().to_string();
if password != repeat {
// TODO: Show error and validate other input.
} else {
this.widget.set_visible_child_name("loading");
spawn!(@clone this, async move {
let username = this.username_entry.text().to_string();
let email = this.email_entry.text().to_string();
let captcha_id = this.captcha_id.borrow().clone().unwrap();
let answer = this.captcha_entry.text().to_string();
let email = if email.is_empty() {
None
} else {
Some(email)
};
let registration = UserRegistration {
username: username.clone(),
password: password.clone(),
email,
captcha_id,
answer,
};
// TODO: Handle errors.
if this.handle.backend.cl().register(registration).await.unwrap() {
let data = LoginData {
username,
password,
};
this.handle.pop(Some(data));
} else {
this.widget.set_visible_child_name("content");
}
});
}
}));
// Initialize
spawn!(@clone this, async move {
let captcha = this.handle.backend.cl().get_captcha().await.unwrap();
this.captcha_row.set_title(&captcha.question);
this.captcha_id.replace(Some(captcha.id));
this.widget.set_visible_child_name("content");
});
this
}
}
impl Widget for RegisterDialog {
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast()
}
}

View file

@ -1,65 +0,0 @@
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use musicus_backend::Backend;
use std::cell::RefCell;
use std::rc::Rc;
/// A dialog for setting up the server.
pub struct ServerDialog {
backend: Rc<Backend>,
window: adw::Window,
url_entry: gtk::Entry,
selected_cb: RefCell<Option<Box<dyn Fn(String)>>>,
}
impl ServerDialog {
/// Create a new server dialog.
pub fn new<P: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &P) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/server_dialog.ui");
get_widget!(builder, adw::Window, window);
get_widget!(builder, gtk::Button, cancel_button);
get_widget!(builder, gtk::Button, set_button);
get_widget!(builder, gtk::Entry, url_entry);
window.set_transient_for(Some(parent));
let this = Rc::new(Self {
backend,
window,
url_entry,
selected_cb: RefCell::new(None),
});
// Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| {
this.window.close();
}));
set_button.connect_clicked(clone!(@strong this => move |_| {
let url = this.url_entry.text().to_string();
this.backend.set_server_url(&url);
if let Some(cb) = &*this.selected_cb.borrow() {
cb(url);
}
this.window.close();
}));
this
}
/// The closure to call when the server was set.
pub fn set_selected_cb<F: Fn(String) + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Show the server dialog.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -19,7 +19,7 @@ impl Screen<(), Ensemble> for EnsembleSelector {
fn new(_: (), handle: NavigationHandle<Ensemble>) -> Rc<Self> {
// Create UI
let selector = Selector::<Ensemble>::new(Rc::clone(&handle.backend));
let selector = Selector::<Ensemble>::new();
selector.set_title(&gettext("Select ensemble"));
let this = Rc::new(Self { handle, selector });
@ -38,12 +38,6 @@ impl Screen<(), Ensemble> for EnsembleSelector {
});
}));
this.selector
.set_load_online(clone!(@weak this => @default-panic, move || {
let clone = this;
async move { Ok(clone.handle.backend.cl().get_ensembles().await?) }
}));
this.selector
.set_load_local(clone!(@weak this => @default-panic, move || {
let clone = this;

View file

@ -19,7 +19,7 @@ impl Screen<(), Instrument> for InstrumentSelector {
fn new(_: (), handle: NavigationHandle<Instrument>) -> Rc<Self> {
// Create UI
let selector = Selector::<Instrument>::new(Rc::clone(&handle.backend));
let selector = Selector::<Instrument>::new();
selector.set_title(&gettext("Select instrument"));
let this = Rc::new(Self { handle, selector });
@ -38,12 +38,6 @@ impl Screen<(), Instrument> for InstrumentSelector {
});
}));
this.selector
.set_load_online(clone!(@weak this => @default-panic, move || {
let clone = this;
async move { Ok(clone.handle.backend.cl().get_instruments().await?) }
}));
this.selector
.set_load_local(clone!(@weak this => @default-panic, move || {
let clone = this;

View file

@ -17,7 +17,7 @@ impl Screen<(), Medium> for MediumSelector {
fn new(_: (), handle: NavigationHandle<Medium>) -> Rc<Self> {
// Create UI
let selector = Selector::<PersonOrEnsemble>::new(Rc::clone(&handle.backend));
let selector = Selector::<PersonOrEnsemble>::new();
selector.set_title(&gettext("Select performer"));
let this = Rc::new(Self { handle, selector });
@ -28,26 +28,6 @@ impl Screen<(), Medium> for MediumSelector {
this.handle.pop(None);
}));
this.selector
.set_load_online(clone!(@weak this => @default-panic, move || {
async move {
let mut poes = Vec::new();
let persons = this.handle.backend.cl().get_persons().await?;
let ensembles = this.handle.backend.cl().get_ensembles().await?;
for person in persons {
poes.push(PersonOrEnsemble::Person(person));
}
for ensemble in ensembles {
poes.push(PersonOrEnsemble::Ensemble(ensemble));
}
Ok(poes)
}
}));
this.selector
.set_load_local(clone!(@weak this => @default-panic, move || {
async move {
@ -109,7 +89,7 @@ struct MediumSelectorMediumScreen {
impl Screen<PersonOrEnsemble, Medium> for MediumSelectorMediumScreen {
fn new(poe: PersonOrEnsemble, handle: NavigationHandle<Medium>) -> Rc<Self> {
let selector = Selector::<Medium>::new(Rc::clone(&handle.backend));
let selector = Selector::<Medium>::new();
selector.set_title(&gettext("Select medium"));
selector.set_subtitle(&poe.get_title());

View file

@ -19,7 +19,7 @@ impl Screen<(), Person> for PersonSelector {
fn new(_: (), handle: NavigationHandle<Person>) -> Rc<Self> {
// Create UI
let selector = Selector::<Person>::new(Rc::clone(&handle.backend));
let selector = Selector::<Person>::new();
selector.set_title(&gettext("Select person"));
let this = Rc::new(Self { handle, selector });
@ -38,12 +38,6 @@ impl Screen<(), Person> for PersonSelector {
});
}));
this.selector
.set_load_online(clone!(@weak this => @default-panic, move || {
let clone = this;
async move { Ok(clone.handle.backend.cl().get_persons().await?) }
}));
this.selector
.set_load_local(clone!(@weak this => @default-panic, move || {
let clone = this;

View file

@ -18,7 +18,7 @@ impl Screen<(), Recording> for RecordingSelector {
fn new(_: (), handle: NavigationHandle<Recording>) -> Rc<Self> {
// Create UI
let selector = Selector::<Person>::new(Rc::clone(&handle.backend));
let selector = Selector::<Person>::new();
selector.set_title(&gettext("Select composer"));
let this = Rc::new(Self { handle, selector });
@ -50,11 +50,6 @@ impl Screen<(), Recording> for RecordingSelector {
});
}));
this.selector
.set_load_online(clone!(@weak this => @default-panic, move || {
async move { Ok(this.handle.backend.cl().get_persons().await?) }
}));
this.selector
.set_load_local(clone!(@weak this => @default-panic, move || {
async move { this.handle.backend.db().get_persons().await.unwrap() }
@ -108,7 +103,7 @@ struct RecordingSelectorWorkScreen {
impl Screen<Person, Work> for RecordingSelectorWorkScreen {
fn new(person: Person, handle: NavigationHandle<Work>) -> Rc<Self> {
let selector = Selector::<Work>::new(Rc::clone(&handle.backend));
let selector = Selector::<Work>::new();
selector.set_title(&gettext("Select work"));
selector.set_subtitle(&person.name_fl());
@ -131,11 +126,6 @@ impl Screen<Person, Work> for RecordingSelectorWorkScreen {
});
}));
this.selector
.set_load_online(clone!(@weak this => @default-panic, move || {
async move { Ok(this.handle.backend.cl().get_works(&this.person.id).await?) }
}));
this.selector
.set_load_local(clone!(@weak this => @default-panic, move || {
async move { this.handle.backend.db().get_works(&this.person.id).await.unwrap() }
@ -178,7 +168,7 @@ struct RecordingSelectorRecordingScreen {
impl Screen<Work, Recording> for RecordingSelectorRecordingScreen {
fn new(work: Work, handle: NavigationHandle<Recording>) -> Rc<Self> {
let selector = Selector::<Recording>::new(Rc::clone(&handle.backend));
let selector = Selector::<Recording>::new();
selector.set_title(&gettext("Select recording"));
selector.set_subtitle(&work.get_title());
@ -201,10 +191,6 @@ impl Screen<Work, Recording> for RecordingSelectorRecordingScreen {
});
}));
this.selector.set_load_online(clone!(@weak this => @default-panic, move || {
async move { Ok(this.handle.backend.cl().get_recordings_for_work(&this.work.id).await?) }
}));
this.selector.set_load_local(clone!(@weak this => @default-panic, move || {
async move { this.handle.backend.db().get_recordings_for_work(&this.work.id).await.unwrap() }
}));

View file

@ -2,36 +2,30 @@ use crate::widgets::List;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use musicus_backend::{Backend, Result};
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
/// A screen that presents a list of items. It allows to switch between the server and the local
/// database and to search within the list.
/// A screen that presents a list of items from the library.
pub struct Selector<T: 'static> {
pub widget: gtk::Box,
backend: Rc<Backend>,
title_label: gtk::Label,
subtitle_label: gtk::Label,
search_entry: gtk::SearchEntry,
server_check_button: gtk::CheckButton,
stack: gtk::Stack,
list: Rc<List>,
items: RefCell<Vec<T>>,
back_cb: RefCell<Option<Box<dyn Fn()>>>,
add_cb: RefCell<Option<Box<dyn Fn()>>>,
make_widget: RefCell<Option<Box<dyn Fn(&T) -> gtk::Widget>>>,
load_online: RefCell<Option<Box<dyn Fn() -> Box<dyn Future<Output = Result<Vec<T>>>>>>>,
load_local: RefCell<Option<Box<dyn Fn() -> Box<dyn Future<Output = Vec<T>>>>>>,
filter: RefCell<Option<Box<dyn Fn(&str, &T) -> bool>>>,
}
impl<T> Selector<T> {
/// Create a new selector. `use_server` is used to decide whether to search
/// online initially.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
/// Create a new selector.
pub fn new() -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/selector.ui");
@ -42,28 +36,23 @@ impl<T> Selector<T> {
get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Button, add_button);
get_widget!(builder, gtk::SearchEntry, search_entry);
get_widget!(builder, gtk::CheckButton, server_check_button);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::Frame, frame);
get_widget!(builder, gtk::Button, try_again_button);
let list = List::new();
frame.set_child(Some(&list.widget));
let this = Rc::new(Self {
widget,
backend,
title_label,
subtitle_label,
search_entry,
server_check_button,
stack,
list,
items: RefCell::new(Vec::new()),
back_cb: RefCell::new(None),
add_cb: RefCell::new(None),
make_widget: RefCell::new(None),
load_online: RefCell::new(None),
load_local: RefCell::new(None),
filter: RefCell::new(None),
});
@ -87,18 +76,6 @@ impl<T> Selector<T> {
this.list.invalidate_filter();
}));
this.server_check_button
.connect_toggled(clone!(@strong this => move |_| {
let active = this.server_check_button.is_active();
this.backend.set_use_server(active);
if active {
this.clone().load_online();
} else {
this.clone().load_local();
}
}));
this.list
.set_make_widget_cb(clone!(@strong this => move |index| {
if let Some(cb) = &*this.make_widget.borrow() {
@ -121,16 +98,8 @@ impl<T> Selector<T> {
}
}));
try_again_button.connect_clicked(clone!(@strong this => move |_| {
this.clone().load_online();
}));
// Initialize
if this.backend.use_server() {
this.clone().load_online();
} else {
this.server_check_button.set_active(false);
}
this.clone().load_local();
this
}
@ -156,17 +125,6 @@ impl<T> Selector<T> {
self.add_cb.replace(Some(Box::new(cb)));
}
/// Set the async closure to be called to fetch items from the server. If that results in an
/// error, an error screen is shown allowing to try again.
pub fn set_load_online<F, R>(&self, cb: F)
where
F: (Fn() -> R) + 'static,
R: Future<Output = Result<Vec<T>>> + 'static,
{
self.load_online
.replace(Some(Box::new(move || Box::new(cb()))));
}
/// Set the async closure to be called to get local items.
pub fn set_load_local<F, R>(&self, cb: F)
where
@ -188,26 +146,6 @@ impl<T> Selector<T> {
self.filter.replace(Some(Box::new(filter)));
}
fn load_online(self: Rc<Self>) {
let context = glib::MainContext::default();
let clone = self.clone();
context.spawn_local(async move {
if let Some(cb) = &*self.load_online.borrow() {
self.stack.set_visible_child_name("loading");
match Pin::from(cb()).await {
Ok(items) => {
clone.show_items(items);
}
Err(_) => {
clone.show_items(Vec::new());
clone.stack.set_visible_child_name("error");
}
}
}
});
}
fn load_local(self: Rc<Self>) {
let context = glib::MainContext::default();
let clone = self.clone();

View file

@ -18,7 +18,7 @@ impl Screen<(), Work> for WorkSelector {
fn new(_: (), handle: NavigationHandle<Work>) -> Rc<Self> {
// Create UI
let selector = Selector::<Person>::new(Rc::clone(&handle.backend));
let selector = Selector::<Person>::new();
selector.set_title(&gettext("Select composer"));
let this = Rc::new(Self { handle, selector });
@ -44,11 +44,6 @@ impl Screen<(), Work> for WorkSelector {
});
}));
this.selector
.set_load_online(clone!(@weak this => @default-panic, move || {
async move { Ok(this.handle.backend.cl().get_persons().await?) }
}));
this.selector
.set_load_local(clone!(@weak this => @default-panic, move || {
async move { this.handle.backend.db().get_persons().await.unwrap() }
@ -98,7 +93,7 @@ struct WorkSelectorWorkScreen {
impl Screen<Person, Work> for WorkSelectorWorkScreen {
fn new(person: Person, handle: NavigationHandle<Work>) -> Rc<Self> {
let selector = Selector::<Work>::new(Rc::clone(&handle.backend));
let selector = Selector::<Work>::new();
selector.set_title(&gettext("Select work"));
selector.set_subtitle(&person.name_fl());
@ -121,11 +116,6 @@ impl Screen<Person, Work> for WorkSelectorWorkScreen {
});
}));
this.selector
.set_load_online(clone!(@weak this => @default-panic, move || {
async move { Ok(this.handle.backend.cl().get_works(&this.person.id).await?) }
}));
this.selector
.set_load_local(clone!(@weak this => @default-panic, move || {
async move { this.handle.backend.db().get_works(&this.person.id).await.unwrap() }

View file

@ -21,9 +21,6 @@ pub use screen::*;
pub mod section;
pub use section::*;
pub mod upload_section;
pub use upload_section::*;
mod indexed_list_model;
/// Something that can be represented as a GTK widget.

View file

@ -1,60 +0,0 @@
use super::Section;
use adw::prelude::*;
use gettextrs::gettext;
use glib::clone;
use musicus_backend::Backend;
use std::rc::Rc;
/// A section showing a switch to enable uploading an item.
pub struct UploadSection {
/// The GTK widget of the wrapped section.
pub widget: gtk::Box,
backend: Rc<Backend>,
/// The upload switch.
switch: gtk::Switch,
}
impl UploadSection {
/// Create a new upload section which will be initially switched on.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
let list = gtk::ListBoxBuilder::new()
.selection_mode(gtk::SelectionMode::None)
.build();
let switch = gtk::SwitchBuilder::new()
.active(backend.use_server())
.valign(gtk::Align::Center)
.build();
let row = adw::ActionRowBuilder::new()
.focusable(false)
.title("Upload changes to the server")
.activatable_widget(&switch)
.build();
row.add_suffix(&switch);
list.append(&row);
let section = Section::new(&gettext("Upload"), &list);
let this = Rc::new(Self {
widget: section.widget,
backend,
switch,
});
this.switch
.connect_state_notify(clone!(@weak this => move |_| {
this.backend.set_use_server(this.switch.state());
}));
this
}
/// Return whether the user has enabled the upload switch.
pub fn get_active(&self) -> bool {
self.switch.state()
}
}