188---add-option-for-manual-input-during-fetch (#214)
PR 1 for #188 Reviewed-on: #214
This commit is contained in:
parent
9d1caffd9c
commit
f2996903b2
@ -4,25 +4,25 @@ use crate::tui::app::{
|
|||||||
AppPublic, AppState, IAppInteractBrowse,
|
AppPublic, AppState, IAppInteractBrowse,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AppBrowse;
|
pub struct BrowseState;
|
||||||
|
|
||||||
impl AppMachine<AppBrowse> {
|
impl AppMachine<BrowseState> {
|
||||||
pub fn browse(inner: AppInner) -> Self {
|
pub fn browse_state(inner: AppInner) -> Self {
|
||||||
AppMachine {
|
AppMachine {
|
||||||
inner,
|
inner,
|
||||||
state: AppBrowse,
|
state: BrowseState,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AppMachine<AppBrowse>> for App {
|
impl From<AppMachine<BrowseState>> for App {
|
||||||
fn from(machine: AppMachine<AppBrowse>) -> Self {
|
fn from(machine: AppMachine<BrowseState>) -> Self {
|
||||||
AppState::Browse(machine)
|
AppState::Browse(machine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a mut AppMachine<AppBrowse>> for AppPublic<'a> {
|
impl<'a> From<&'a mut AppMachine<BrowseState>> for AppPublic<'a> {
|
||||||
fn from(machine: &'a mut AppMachine<AppBrowse>) -> Self {
|
fn from(machine: &'a mut AppMachine<BrowseState>) -> Self {
|
||||||
AppPublic {
|
AppPublic {
|
||||||
inner: (&mut machine.inner).into(),
|
inner: (&mut machine.inner).into(),
|
||||||
state: AppState::Browse(()),
|
state: AppState::Browse(()),
|
||||||
@ -30,7 +30,7 @@ impl<'a> From<&'a mut AppMachine<AppBrowse>> for AppPublic<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IAppInteractBrowse for AppMachine<AppBrowse> {
|
impl IAppInteractBrowse for AppMachine<BrowseState> {
|
||||||
type APP = App;
|
type APP = App;
|
||||||
|
|
||||||
fn quit(mut self) -> Self::APP {
|
fn quit(mut self) -> Self::APP {
|
||||||
@ -63,11 +63,11 @@ impl IAppInteractBrowse for AppMachine<AppBrowse> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show_info_overlay(self) -> Self::APP {
|
fn show_info_overlay(self) -> Self::APP {
|
||||||
AppMachine::info(self.inner).into()
|
AppMachine::info_state(self.inner).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_reload_menu(self) -> Self::APP {
|
fn show_reload_menu(self) -> Self::APP {
|
||||||
AppMachine::reload(self.inner).into()
|
AppMachine::reload_state(self.inner).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn begin_search(mut self) -> Self::APP {
|
fn begin_search(mut self) -> Self::APP {
|
||||||
@ -75,7 +75,7 @@ impl IAppInteractBrowse for AppMachine<AppBrowse> {
|
|||||||
self.inner
|
self.inner
|
||||||
.selection
|
.selection
|
||||||
.reset(self.inner.music_hoard.get_collection());
|
.reset(self.inner.music_hoard.get_collection());
|
||||||
AppMachine::search(self.inner, orig).into()
|
AppMachine::search_state(self.inner, orig).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_musicbrainz(self) -> Self::APP {
|
fn fetch_musicbrainz(self) -> Self::APP {
|
||||||
@ -88,7 +88,7 @@ mod tests {
|
|||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{
|
app::{
|
||||||
machine::tests::{inner, inner_with_mb, music_hoard},
|
machine::tests::{inner, inner_with_mb, music_hoard},
|
||||||
Category, IAppAccess, IAppInteract,
|
Category, IApp, IAppAccess,
|
||||||
},
|
},
|
||||||
lib::interface::musicbrainz::MockIMusicBrainz,
|
lib::interface::musicbrainz::MockIMusicBrainz,
|
||||||
testmod::COLLECTION,
|
testmod::COLLECTION,
|
||||||
@ -100,7 +100,7 @@ mod tests {
|
|||||||
fn quit() {
|
fn quit() {
|
||||||
let music_hoard = music_hoard(vec![]);
|
let music_hoard = music_hoard(vec![]);
|
||||||
|
|
||||||
let browse = AppMachine::browse(inner(music_hoard));
|
let browse = AppMachine::browse_state(inner(music_hoard));
|
||||||
|
|
||||||
let app = browse.quit();
|
let app = browse.quit();
|
||||||
assert!(!app.is_running());
|
assert!(!app.is_running());
|
||||||
@ -109,7 +109,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn increment_decrement() {
|
fn increment_decrement() {
|
||||||
let mut browse = AppMachine::browse(inner(music_hoard(COLLECTION.to_owned())));
|
let mut browse = AppMachine::browse_state(inner(music_hoard(COLLECTION.to_owned())));
|
||||||
let sel = &browse.inner.selection;
|
let sel = &browse.inner.selection;
|
||||||
assert_eq!(sel.category(), Category::Artist);
|
assert_eq!(sel.category(), Category::Artist);
|
||||||
assert_eq!(sel.selected(), Some(0));
|
assert_eq!(sel.selected(), Some(0));
|
||||||
@ -147,21 +147,21 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn show_info_overlay() {
|
fn show_info_overlay() {
|
||||||
let browse = AppMachine::browse(inner(music_hoard(vec![])));
|
let browse = AppMachine::browse_state(inner(music_hoard(vec![])));
|
||||||
let app = browse.show_info_overlay();
|
let app = browse.show_info_overlay();
|
||||||
app.unwrap_info();
|
app.unwrap_info();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn show_reload_menu() {
|
fn show_reload_menu() {
|
||||||
let browse = AppMachine::browse(inner(music_hoard(vec![])));
|
let browse = AppMachine::browse_state(inner(music_hoard(vec![])));
|
||||||
let app = browse.show_reload_menu();
|
let app = browse.show_reload_menu();
|
||||||
app.unwrap_reload();
|
app.unwrap_reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn begin_search() {
|
fn begin_search() {
|
||||||
let browse = AppMachine::browse(inner(music_hoard(vec![])));
|
let browse = AppMachine::browse_state(inner(music_hoard(vec![])));
|
||||||
let app = browse.begin_search();
|
let app = browse.begin_search();
|
||||||
app.unwrap_search();
|
app.unwrap_search();
|
||||||
}
|
}
|
||||||
@ -169,7 +169,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn fetch_musicbrainz() {
|
fn fetch_musicbrainz() {
|
||||||
let mb_api = MockIMusicBrainz::new();
|
let mb_api = MockIMusicBrainz::new();
|
||||||
let browse = AppMachine::browse(inner_with_mb(music_hoard(COLLECTION.to_owned()), mb_api));
|
let browse =
|
||||||
|
AppMachine::browse_state(inner_with_mb(music_hoard(COLLECTION.to_owned()), mb_api));
|
||||||
|
|
||||||
// Use the second artist for this test.
|
// Use the second artist for this test.
|
||||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
@ -179,7 +180,7 @@ mod tests {
|
|||||||
|
|
||||||
// Because of fetch's threaded behaviour, this unit test cannot expect one or the other.
|
// Because of fetch's threaded behaviour, this unit test cannot expect one or the other.
|
||||||
assert!(
|
assert!(
|
||||||
matches!(public.state, AppState::Matches(_))
|
matches!(public.state, AppState::Match(_))
|
||||||
|| matches!(public.state, AppState::Fetch(_))
|
|| matches!(public.state, AppState::Fetch(_))
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -3,29 +3,29 @@ use crate::tui::app::{
|
|||||||
AppPublic, AppState,
|
AppPublic, AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AppCritical {
|
pub struct CriticalState {
|
||||||
string: String,
|
string: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppMachine<AppCritical> {
|
impl AppMachine<CriticalState> {
|
||||||
pub fn critical<S: Into<String>>(inner: AppInner, string: S) -> Self {
|
pub fn critical_state<S: Into<String>>(inner: AppInner, string: S) -> Self {
|
||||||
AppMachine {
|
AppMachine {
|
||||||
inner,
|
inner,
|
||||||
state: AppCritical {
|
state: CriticalState {
|
||||||
string: string.into(),
|
string: string.into(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AppMachine<AppCritical>> for App {
|
impl From<AppMachine<CriticalState>> for App {
|
||||||
fn from(machine: AppMachine<AppCritical>) -> Self {
|
fn from(machine: AppMachine<CriticalState>) -> Self {
|
||||||
AppState::Critical(machine)
|
AppState::Critical(machine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a mut AppMachine<AppCritical>> for AppPublic<'a> {
|
impl<'a> From<&'a mut AppMachine<CriticalState>> for AppPublic<'a> {
|
||||||
fn from(machine: &'a mut AppMachine<AppCritical>) -> Self {
|
fn from(machine: &'a mut AppMachine<CriticalState>) -> Self {
|
||||||
AppPublic {
|
AppPublic {
|
||||||
inner: (&mut machine.inner).into(),
|
inner: (&mut machine.inner).into(),
|
||||||
state: AppState::Critical(&machine.state.string),
|
state: AppState::Critical(&machine.state.string),
|
@ -3,29 +3,29 @@ use crate::tui::app::{
|
|||||||
AppPublic, AppState, IAppInteractError,
|
AppPublic, AppState, IAppInteractError,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AppError {
|
pub struct ErrorState {
|
||||||
string: String,
|
string: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppMachine<AppError> {
|
impl AppMachine<ErrorState> {
|
||||||
pub fn error<S: Into<String>>(inner: AppInner, string: S) -> Self {
|
pub fn error_state<S: Into<String>>(inner: AppInner, string: S) -> Self {
|
||||||
AppMachine {
|
AppMachine {
|
||||||
inner,
|
inner,
|
||||||
state: AppError {
|
state: ErrorState {
|
||||||
string: string.into(),
|
string: string.into(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AppMachine<AppError>> for App {
|
impl From<AppMachine<ErrorState>> for App {
|
||||||
fn from(machine: AppMachine<AppError>) -> Self {
|
fn from(machine: AppMachine<ErrorState>) -> Self {
|
||||||
AppState::Error(machine)
|
AppState::Error(machine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a mut AppMachine<AppError>> for AppPublic<'a> {
|
impl<'a> From<&'a mut AppMachine<ErrorState>> for AppPublic<'a> {
|
||||||
fn from(machine: &'a mut AppMachine<AppError>) -> Self {
|
fn from(machine: &'a mut AppMachine<ErrorState>) -> Self {
|
||||||
AppPublic {
|
AppPublic {
|
||||||
inner: (&mut machine.inner).into(),
|
inner: (&mut machine.inner).into(),
|
||||||
state: AppState::Error(&machine.state.string),
|
state: AppState::Error(&machine.state.string),
|
||||||
@ -33,11 +33,11 @@ impl<'a> From<&'a mut AppMachine<AppError>> for AppPublic<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IAppInteractError for AppMachine<AppError> {
|
impl IAppInteractError for AppMachine<ErrorState> {
|
||||||
type APP = App;
|
type APP = App;
|
||||||
|
|
||||||
fn dismiss_error(self) -> Self::APP {
|
fn dismiss_error(self) -> Self::APP {
|
||||||
AppMachine::browse(self.inner).into()
|
AppMachine::browse_state(self.inner).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dismiss_error() {
|
fn dismiss_error() {
|
||||||
let error = AppMachine::error(inner(music_hoard(vec![])), "get rekt");
|
let error = AppMachine::error_state(inner(music_hoard(vec![])), "get rekt");
|
||||||
let app = error.dismiss_error();
|
let app = error.dismiss_error();
|
||||||
app.unwrap_browse();
|
app.unwrap_browse();
|
||||||
}
|
}
|
@ -15,31 +15,31 @@ use musichoard::collection::{
|
|||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{
|
app::{
|
||||||
machine::{App, AppInner, AppMachine},
|
machine::{App, AppInner, AppMachine},
|
||||||
AppMatchesInfo, AppPublic, AppState, IAppEventFetch, IAppInteractFetch,
|
AppPublic, AppState, IAppEventFetch, IAppInteractFetch, MatchStateInfo,
|
||||||
},
|
},
|
||||||
event::{Event, EventSender},
|
event::{Event, EventSender},
|
||||||
lib::interface::musicbrainz::{self, Error as MbError, IMusicBrainz},
|
lib::interface::musicbrainz::{self, Error as MbError, IMusicBrainz},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::matches::AppMatches;
|
use super::match_state::MatchState;
|
||||||
|
|
||||||
pub struct AppFetch {
|
pub struct FetchState {
|
||||||
fetch_rx: FetchReceiver,
|
fetch_rx: FetchReceiver,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppFetch {
|
impl FetchState {
|
||||||
pub fn new(fetch_rx: FetchReceiver) -> Self {
|
pub fn new(fetch_rx: FetchReceiver) -> Self {
|
||||||
AppFetch { fetch_rx }
|
FetchState { fetch_rx }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type FetchError = MbError;
|
pub type FetchError = MbError;
|
||||||
pub type FetchResult = Result<AppMatchesInfo, FetchError>;
|
pub type FetchResult = Result<MatchStateInfo, FetchError>;
|
||||||
pub type FetchSender = mpsc::Sender<FetchResult>;
|
pub type FetchSender = mpsc::Sender<FetchResult>;
|
||||||
pub type FetchReceiver = mpsc::Receiver<FetchResult>;
|
pub type FetchReceiver = mpsc::Receiver<FetchResult>;
|
||||||
|
|
||||||
impl AppMachine<AppFetch> {
|
impl AppMachine<FetchState> {
|
||||||
fn fetch(inner: AppInner, state: AppFetch) -> Self {
|
fn fetch_state(inner: AppInner, state: FetchState) -> Self {
|
||||||
AppMachine { inner, state }
|
AppMachine { inner, state }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,38 +47,40 @@ impl AppMachine<AppFetch> {
|
|||||||
let coll = inner.music_hoard.get_collection();
|
let coll = inner.music_hoard.get_collection();
|
||||||
let artist = match inner.selection.state_artist(coll) {
|
let artist = match inner.selection.state_artist(coll) {
|
||||||
Some(artist_state) => &coll[artist_state.index],
|
Some(artist_state) => &coll[artist_state.index],
|
||||||
None => return AppMachine::error(inner, "cannot fetch: no artist selected").into(),
|
None => {
|
||||||
|
return AppMachine::error_state(inner, "cannot fetch: no artist selected").into()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (fetch_tx, fetch_rx) = mpsc::channel::<FetchResult>();
|
let (fetch_tx, fetch_rx) = mpsc::channel::<FetchResult>();
|
||||||
Self::spawn_fetch_thread(&inner, artist, fetch_tx);
|
Self::spawn_fetch_thread(&inner, artist, fetch_tx);
|
||||||
|
|
||||||
let fetch = AppFetch::new(fetch_rx);
|
let fetch = FetchState::new(fetch_rx);
|
||||||
AppMachine::app_fetch(inner, fetch, true)
|
AppMachine::app_fetch(inner, fetch, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn app_fetch_next(inner: AppInner, fetch: AppFetch) -> App {
|
pub fn app_fetch_next(inner: AppInner, fetch: FetchState) -> App {
|
||||||
Self::app_fetch(inner, fetch, false)
|
Self::app_fetch(inner, fetch, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app_fetch(inner: AppInner, fetch: AppFetch, first: bool) -> App {
|
fn app_fetch(inner: AppInner, fetch: FetchState, first: bool) -> App {
|
||||||
match fetch.fetch_rx.try_recv() {
|
match fetch.fetch_rx.try_recv() {
|
||||||
Ok(fetch_result) => match fetch_result {
|
Ok(fetch_result) => match fetch_result {
|
||||||
Ok(next_match) => {
|
Ok(next_match) => {
|
||||||
let current = Some(next_match);
|
let current = Some(next_match);
|
||||||
AppMachine::matches(inner, AppMatches::new(current, fetch)).into()
|
AppMachine::match_state(inner, MatchState::new(current, fetch)).into()
|
||||||
}
|
}
|
||||||
Err(fetch_err) => {
|
Err(fetch_err) => {
|
||||||
AppMachine::error(inner, format!("fetch failed: {fetch_err}")).into()
|
AppMachine::error_state(inner, format!("fetch failed: {fetch_err}")).into()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(recv_err) => match recv_err {
|
Err(recv_err) => match recv_err {
|
||||||
TryRecvError::Empty => AppMachine::fetch(inner, fetch).into(),
|
TryRecvError::Empty => AppMachine::fetch_state(inner, fetch).into(),
|
||||||
TryRecvError::Disconnected => {
|
TryRecvError::Disconnected => {
|
||||||
if first {
|
if first {
|
||||||
AppMachine::matches(inner, AppMatches::new(None, fetch)).into()
|
AppMachine::match_state(inner, MatchState::new(None, fetch)).into()
|
||||||
} else {
|
} else {
|
||||||
AppMachine::browse(inner).into()
|
AppMachine::browse_state(inner).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -114,7 +116,7 @@ impl AppMachine<AppFetch> {
|
|||||||
artist: ArtistMeta,
|
artist: ArtistMeta,
|
||||||
) {
|
) {
|
||||||
let result = musicbrainz.lock().unwrap().search_artist(&artist);
|
let result = musicbrainz.lock().unwrap().search_artist(&artist);
|
||||||
let result = result.map(|list| AppMatchesInfo::artist(artist, list));
|
let result = result.map(|list| MatchStateInfo::artist(artist, list));
|
||||||
Self::send_fetch_result(&fetch_tx, &events, result).ok();
|
Self::send_fetch_result(&fetch_tx, &events, result).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +135,7 @@ impl AppMachine<AppFetch> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let result = musicbrainz.search_release_group(&arid, &album);
|
let result = musicbrainz.search_release_group(&arid, &album);
|
||||||
let result = result.map(|list| AppMatchesInfo::album(album, list));
|
let result = result.map(|list| MatchStateInfo::album(album, list));
|
||||||
if Self::send_fetch_result(&fetch_tx, &events, result).is_err() {
|
if Self::send_fetch_result(&fetch_tx, &events, result).is_err() {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -147,7 +149,7 @@ impl AppMachine<AppFetch> {
|
|||||||
fn send_fetch_result(
|
fn send_fetch_result(
|
||||||
fetch_tx: &FetchSender,
|
fetch_tx: &FetchSender,
|
||||||
events: &EventSender,
|
events: &EventSender,
|
||||||
result: Result<AppMatchesInfo, musicbrainz::Error>,
|
result: Result<MatchStateInfo, musicbrainz::Error>,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
// If receiver disconnects just drop the rest.
|
// If receiver disconnects just drop the rest.
|
||||||
fetch_tx.send(result).map_err(|_| ())?;
|
fetch_tx.send(result).map_err(|_| ())?;
|
||||||
@ -160,14 +162,14 @@ impl AppMachine<AppFetch> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AppMachine<AppFetch>> for App {
|
impl From<AppMachine<FetchState>> for App {
|
||||||
fn from(machine: AppMachine<AppFetch>) -> Self {
|
fn from(machine: AppMachine<FetchState>) -> Self {
|
||||||
AppState::Fetch(machine)
|
AppState::Fetch(machine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a mut AppMachine<AppFetch>> for AppPublic<'a> {
|
impl<'a> From<&'a mut AppMachine<FetchState>> for AppPublic<'a> {
|
||||||
fn from(machine: &'a mut AppMachine<AppFetch>) -> Self {
|
fn from(machine: &'a mut AppMachine<FetchState>) -> Self {
|
||||||
AppPublic {
|
AppPublic {
|
||||||
inner: (&mut machine.inner).into(),
|
inner: (&mut machine.inner).into(),
|
||||||
state: AppState::Fetch(()),
|
state: AppState::Fetch(()),
|
||||||
@ -175,15 +177,15 @@ impl<'a> From<&'a mut AppMachine<AppFetch>> for AppPublic<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IAppInteractFetch for AppMachine<AppFetch> {
|
impl IAppInteractFetch for AppMachine<FetchState> {
|
||||||
type APP = App;
|
type APP = App;
|
||||||
|
|
||||||
fn abort(self) -> Self::APP {
|
fn abort(self) -> Self::APP {
|
||||||
AppMachine::browse(self.inner).into()
|
AppMachine::browse_state(self.inner).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IAppEventFetch for AppMachine<AppFetch> {
|
impl IAppEventFetch for AppMachine<FetchState> {
|
||||||
type APP = App;
|
type APP = App;
|
||||||
|
|
||||||
fn fetch_result_ready(self) -> Self::APP {
|
fn fetch_result_ready(self) -> Self::APP {
|
||||||
@ -199,7 +201,7 @@ mod tests {
|
|||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{
|
app::{
|
||||||
machine::tests::{inner, music_hoard},
|
machine::tests::{inner, music_hoard},
|
||||||
AppAlbumMatches, AppArtistMatches, IAppInteract,
|
AlbumMatches, ArtistMatches, IApp,
|
||||||
},
|
},
|
||||||
event::EventReceiver,
|
event::EventReceiver,
|
||||||
lib::interface::musicbrainz::{self, Match, MockIMusicBrainz},
|
lib::interface::musicbrainz::{self, Match, MockIMusicBrainz},
|
||||||
@ -285,7 +287,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
||||||
let result = fetch_rx.try_recv().unwrap();
|
let result = fetch_rx.try_recv().unwrap();
|
||||||
let expected = Ok(AppMatchesInfo::Album(AppAlbumMatches {
|
let expected = Ok(MatchStateInfo::Album(AlbumMatches {
|
||||||
matching: album_1.clone(),
|
matching: album_1.clone(),
|
||||||
list: matches_1.iter().cloned().map(Into::into).collect(),
|
list: matches_1.iter().cloned().map(Into::into).collect(),
|
||||||
}));
|
}));
|
||||||
@ -293,7 +295,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
||||||
let result = fetch_rx.try_recv().unwrap();
|
let result = fetch_rx.try_recv().unwrap();
|
||||||
let expected = Ok(AppMatchesInfo::Album(AppAlbumMatches {
|
let expected = Ok(MatchStateInfo::Album(AlbumMatches {
|
||||||
matching: album_4.clone(),
|
matching: album_4.clone(),
|
||||||
list: matches_4.iter().cloned().map(Into::into).collect(),
|
list: matches_4.iter().cloned().map(Into::into).collect(),
|
||||||
}));
|
}));
|
||||||
@ -343,7 +345,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
||||||
let result = fetch_rx.try_recv().unwrap();
|
let result = fetch_rx.try_recv().unwrap();
|
||||||
let expected = Ok(AppMatchesInfo::Artist(AppArtistMatches {
|
let expected = Ok(MatchStateInfo::Artist(ArtistMatches {
|
||||||
matching: artist.clone(),
|
matching: artist.clone(),
|
||||||
list: matches.iter().cloned().map(Into::into).collect(),
|
list: matches.iter().cloned().map(Into::into).collect(),
|
||||||
}));
|
}));
|
||||||
@ -391,7 +393,7 @@ mod tests {
|
|||||||
handle.join().unwrap();
|
handle.join().unwrap();
|
||||||
|
|
||||||
let result = fetch_rx.try_recv().unwrap();
|
let result = fetch_rx.try_recv().unwrap();
|
||||||
let expected = Ok(AppMatchesInfo::Album(AppAlbumMatches {
|
let expected = Ok(MatchStateInfo::Album(AlbumMatches {
|
||||||
matching: album_1.clone(),
|
matching: album_1.clone(),
|
||||||
list: matches_1.iter().cloned().map(Into::into).collect(),
|
list: matches_1.iter().cloned().map(Into::into).collect(),
|
||||||
}));
|
}));
|
||||||
@ -405,13 +407,13 @@ mod tests {
|
|||||||
let (tx, rx) = mpsc::channel::<FetchResult>();
|
let (tx, rx) = mpsc::channel::<FetchResult>();
|
||||||
|
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let artist = COLLECTION[3].meta.clone();
|
||||||
let fetch_result = Ok(AppMatchesInfo::artist::<Match<ArtistMeta>>(artist, vec![]));
|
let fetch_result = Ok(MatchStateInfo::artist::<Match<ArtistMeta>>(artist, vec![]));
|
||||||
tx.send(fetch_result).unwrap();
|
tx.send(fetch_result).unwrap();
|
||||||
|
|
||||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||||
let fetch = AppFetch::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
let app = AppMachine::app_fetch(inner, fetch, true);
|
||||||
assert!(matches!(app, AppState::Matches(_)));
|
assert!(matches!(app, AppState::Match(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -422,7 +424,7 @@ mod tests {
|
|||||||
tx.send(fetch_result).unwrap();
|
tx.send(fetch_result).unwrap();
|
||||||
|
|
||||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||||
let fetch = AppFetch::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
let app = AppMachine::app_fetch(inner, fetch, true);
|
||||||
assert!(matches!(app, AppState::Error(_)));
|
assert!(matches!(app, AppState::Error(_)));
|
||||||
}
|
}
|
||||||
@ -432,7 +434,7 @@ mod tests {
|
|||||||
let (_tx, rx) = mpsc::channel::<FetchResult>();
|
let (_tx, rx) = mpsc::channel::<FetchResult>();
|
||||||
|
|
||||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||||
let fetch = AppFetch::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
let app = AppMachine::app_fetch(inner, fetch, true);
|
||||||
assert!(matches!(app, AppState::Fetch(_)));
|
assert!(matches!(app, AppState::Fetch(_)));
|
||||||
}
|
}
|
||||||
@ -442,16 +444,16 @@ mod tests {
|
|||||||
let (_, rx) = mpsc::channel::<FetchResult>();
|
let (_, rx) = mpsc::channel::<FetchResult>();
|
||||||
|
|
||||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||||
let fetch = AppFetch::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
let app = AppMachine::app_fetch(inner, fetch, true);
|
||||||
assert!(matches!(app, AppState::Matches(_)));
|
assert!(matches!(app, AppState::Match(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn recv_err_disconnected_next() {
|
fn recv_err_disconnected_next() {
|
||||||
let (_, rx) = mpsc::channel::<FetchResult>();
|
let (_, rx) = mpsc::channel::<FetchResult>();
|
||||||
|
|
||||||
let fetch = AppFetch::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
let app = AppMachine::app_fetch_next(inner(music_hoard(COLLECTION.clone())), fetch);
|
let app = AppMachine::app_fetch_next(inner(music_hoard(COLLECTION.clone())), fetch);
|
||||||
assert!(matches!(app, AppState::Browse(_)));
|
assert!(matches!(app, AppState::Browse(_)));
|
||||||
}
|
}
|
||||||
@ -461,24 +463,24 @@ mod tests {
|
|||||||
let (tx, rx) = mpsc::channel::<FetchResult>();
|
let (tx, rx) = mpsc::channel::<FetchResult>();
|
||||||
|
|
||||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||||
let fetch = AppFetch::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
let app = AppMachine::app_fetch(inner, fetch, true);
|
||||||
assert!(matches!(app, AppState::Fetch(_)));
|
assert!(matches!(app, AppState::Fetch(_)));
|
||||||
|
|
||||||
let artist = COLLECTION[3].meta.clone();
|
let artist = COLLECTION[3].meta.clone();
|
||||||
let fetch_result = Ok(AppMatchesInfo::artist::<Match<ArtistMeta>>(artist, vec![]));
|
let fetch_result = Ok(MatchStateInfo::artist::<Match<ArtistMeta>>(artist, vec![]));
|
||||||
tx.send(fetch_result).unwrap();
|
tx.send(fetch_result).unwrap();
|
||||||
|
|
||||||
let app = app.unwrap_fetch().fetch_result_ready();
|
let app = app.unwrap_fetch().fetch_result_ready();
|
||||||
assert!(matches!(app, AppState::Matches(_)));
|
assert!(matches!(app, AppState::Match(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn abort() {
|
fn abort() {
|
||||||
let (_, rx) = mpsc::channel::<FetchResult>();
|
let (_, rx) = mpsc::channel::<FetchResult>();
|
||||||
|
|
||||||
let fetch = AppFetch::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
let app = AppMachine::fetch(inner(music_hoard(COLLECTION.clone())), fetch);
|
let app = AppMachine::fetch_state(inner(music_hoard(COLLECTION.clone())), fetch);
|
||||||
|
|
||||||
let app = app.abort();
|
let app = app.abort();
|
||||||
assert!(matches!(app, AppState::Browse(_)));
|
assert!(matches!(app, AppState::Browse(_)));
|
@ -3,25 +3,25 @@ use crate::tui::app::{
|
|||||||
AppPublic, AppState, IAppInteractInfo,
|
AppPublic, AppState, IAppInteractInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AppInfo;
|
pub struct InfoState;
|
||||||
|
|
||||||
impl AppMachine<AppInfo> {
|
impl AppMachine<InfoState> {
|
||||||
pub fn info(inner: AppInner) -> Self {
|
pub fn info_state(inner: AppInner) -> Self {
|
||||||
AppMachine {
|
AppMachine {
|
||||||
inner,
|
inner,
|
||||||
state: AppInfo,
|
state: InfoState,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AppMachine<AppInfo>> for App {
|
impl From<AppMachine<InfoState>> for App {
|
||||||
fn from(machine: AppMachine<AppInfo>) -> Self {
|
fn from(machine: AppMachine<InfoState>) -> Self {
|
||||||
AppState::Info(machine)
|
AppState::Info(machine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a mut AppMachine<AppInfo>> for AppPublic<'a> {
|
impl<'a> From<&'a mut AppMachine<InfoState>> for AppPublic<'a> {
|
||||||
fn from(machine: &'a mut AppMachine<AppInfo>) -> Self {
|
fn from(machine: &'a mut AppMachine<InfoState>) -> Self {
|
||||||
AppPublic {
|
AppPublic {
|
||||||
inner: (&mut machine.inner).into(),
|
inner: (&mut machine.inner).into(),
|
||||||
state: AppState::Info(()),
|
state: AppState::Info(()),
|
||||||
@ -29,11 +29,11 @@ impl<'a> From<&'a mut AppMachine<AppInfo>> for AppPublic<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IAppInteractInfo for AppMachine<AppInfo> {
|
impl IAppInteractInfo for AppMachine<InfoState> {
|
||||||
type APP = App;
|
type APP = App;
|
||||||
|
|
||||||
fn hide_info_overlay(self) -> Self::APP {
|
fn hide_info_overlay(self) -> Self::APP {
|
||||||
AppMachine::browse(self.inner).into()
|
AppMachine::browse_state(self.inner).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hide_info_overlay() {
|
fn hide_info_overlay() {
|
||||||
let info = AppMachine::info(inner(music_hoard(vec![])));
|
let info = AppMachine::info_state(inner(music_hoard(vec![])));
|
||||||
let app = info.hide_info_overlay();
|
let app = info.hide_info_overlay();
|
||||||
app.unwrap_browse();
|
app.unwrap_browse();
|
||||||
}
|
}
|
@ -2,13 +2,13 @@ use std::cmp;
|
|||||||
|
|
||||||
use crate::tui::app::{
|
use crate::tui::app::{
|
||||||
machine::{App, AppInner, AppMachine},
|
machine::{App, AppInner, AppMachine},
|
||||||
AppAlbumMatches, AppArtistMatches, AppMatchesInfo, AppPublic, AppPublicMatches, AppState,
|
AlbumMatches, AppPublic, AppState, ArtistMatches, IAppInteractMatch, MatchOption,
|
||||||
IAppInteractMatches, MatchOption, WidgetState,
|
MatchStateInfo, MatchStatePublic, WidgetState,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::fetch::AppFetch;
|
use super::fetch_state::FetchState;
|
||||||
|
|
||||||
impl AppArtistMatches {
|
impl ArtistMatches {
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
self.list.len()
|
self.list.len()
|
||||||
}
|
}
|
||||||
@ -18,7 +18,7 @@ impl AppArtistMatches {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppAlbumMatches {
|
impl AlbumMatches {
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
self.list.len()
|
self.list.len()
|
||||||
}
|
}
|
||||||
@ -28,7 +28,7 @@ impl AppAlbumMatches {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppMatchesInfo {
|
impl MatchStateInfo {
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::Artist(a) => a.len(),
|
Self::Artist(a) => a.len(),
|
||||||
@ -44,20 +44,20 @@ impl AppMatchesInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppMatches {
|
pub struct MatchState {
|
||||||
current: Option<AppMatchesInfo>,
|
current: Option<MatchStateInfo>,
|
||||||
state: WidgetState,
|
state: WidgetState,
|
||||||
fetch: AppFetch,
|
fetch: FetchState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppMatches {
|
impl MatchState {
|
||||||
pub fn new(mut current: Option<AppMatchesInfo>, fetch: AppFetch) -> Self {
|
pub fn new(mut current: Option<MatchStateInfo>, fetch: FetchState) -> Self {
|
||||||
let mut state = WidgetState::default();
|
let mut state = WidgetState::default();
|
||||||
if let Some(ref mut current) = current {
|
if let Some(ref mut current) = current {
|
||||||
state.list.select(Some(0));
|
state.list.select(Some(0));
|
||||||
current.push_cannot_have_mbid();
|
current.push_cannot_have_mbid();
|
||||||
}
|
}
|
||||||
AppMatches {
|
MatchState {
|
||||||
current,
|
current,
|
||||||
state,
|
state,
|
||||||
fetch,
|
fetch,
|
||||||
@ -65,31 +65,31 @@ impl AppMatches {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppMachine<AppMatches> {
|
impl AppMachine<MatchState> {
|
||||||
pub fn matches(inner: AppInner, state: AppMatches) -> Self {
|
pub fn match_state(inner: AppInner, state: MatchState) -> Self {
|
||||||
AppMachine { inner, state }
|
AppMachine { inner, state }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AppMachine<AppMatches>> for App {
|
impl From<AppMachine<MatchState>> for App {
|
||||||
fn from(machine: AppMachine<AppMatches>) -> Self {
|
fn from(machine: AppMachine<MatchState>) -> Self {
|
||||||
AppState::Matches(machine)
|
AppState::Match(machine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a mut AppMachine<AppMatches>> for AppPublic<'a> {
|
impl<'a> From<&'a mut AppMachine<MatchState>> for AppPublic<'a> {
|
||||||
fn from(machine: &'a mut AppMachine<AppMatches>) -> Self {
|
fn from(machine: &'a mut AppMachine<MatchState>) -> Self {
|
||||||
AppPublic {
|
AppPublic {
|
||||||
inner: (&mut machine.inner).into(),
|
inner: (&mut machine.inner).into(),
|
||||||
state: AppState::Matches(AppPublicMatches {
|
state: AppState::Match(MatchStatePublic {
|
||||||
matches: machine.state.current.as_ref().map(Into::into),
|
info: machine.state.current.as_ref().map(Into::into),
|
||||||
state: &mut machine.state.state,
|
state: &mut machine.state.state,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IAppInteractMatches for AppMachine<AppMatches> {
|
impl IAppInteractMatch for AppMachine<MatchState> {
|
||||||
type APP = App;
|
type APP = App;
|
||||||
|
|
||||||
fn prev_match(mut self) -> Self::APP {
|
fn prev_match(mut self) -> Self::APP {
|
||||||
@ -120,7 +120,7 @@ impl IAppInteractMatches for AppMachine<AppMatches> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn abort(self) -> Self::APP {
|
fn abort(self) -> Self::APP {
|
||||||
AppMachine::browse(self.inner).into()
|
AppMachine::browse_state(self.inner).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn artist_match() -> AppMatchesInfo {
|
fn artist_match() -> MatchStateInfo {
|
||||||
let artist = ArtistMeta::new(ArtistId::new("Artist"));
|
let artist = ArtistMeta::new(ArtistId::new("Artist"));
|
||||||
|
|
||||||
let artist_1 = artist.clone();
|
let artist_1 = artist.clone();
|
||||||
@ -164,10 +164,10 @@ mod tests {
|
|||||||
artist_match_2.disambiguation = Some(String::from("some disambiguation"));
|
artist_match_2.disambiguation = Some(String::from("some disambiguation"));
|
||||||
|
|
||||||
let list = vec![artist_match_1.clone(), artist_match_2.clone()];
|
let list = vec![artist_match_1.clone(), artist_match_2.clone()];
|
||||||
AppMatchesInfo::artist(artist, list)
|
MatchStateInfo::artist(artist, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_match() -> AppMatchesInfo {
|
fn album_match() -> MatchStateInfo {
|
||||||
let album = AlbumMeta::new(
|
let album = AlbumMeta::new(
|
||||||
AlbumId::new("Album"),
|
AlbumId::new("Album"),
|
||||||
AlbumDate::new(Some(1990), Some(5), None),
|
AlbumDate::new(Some(1990), Some(5), None),
|
||||||
@ -184,21 +184,21 @@ mod tests {
|
|||||||
let album_match_2 = Match::new(100, album_2);
|
let album_match_2 = Match::new(100, album_2);
|
||||||
|
|
||||||
let list = vec![album_match_1.clone(), album_match_2.clone()];
|
let list = vec![album_match_1.clone(), album_match_2.clone()];
|
||||||
AppMatchesInfo::album(album, list)
|
MatchStateInfo::album(album, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch() -> AppFetch {
|
fn fetch_state() -> FetchState {
|
||||||
let (_, rx) = mpsc::channel();
|
let (_, rx) = mpsc::channel();
|
||||||
AppFetch::new(rx)
|
FetchState::new(rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches(matches_info: Option<AppMatchesInfo>) -> AppMatches {
|
fn match_state(matches_info: Option<MatchStateInfo>) -> MatchState {
|
||||||
AppMatches::new(matches_info, fetch())
|
MatchState::new(matches_info, fetch_state())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_empty() {
|
fn create_empty() {
|
||||||
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches(None));
|
let matches = AppMachine::match_state(inner(music_hoard(vec![])), match_state(None));
|
||||||
|
|
||||||
let widget_state = WidgetState::default();
|
let widget_state = WidgetState::default();
|
||||||
|
|
||||||
@ -207,18 +207,18 @@ mod tests {
|
|||||||
|
|
||||||
let mut app: App = matches.into();
|
let mut app: App = matches.into();
|
||||||
let public = app.get();
|
let public = app.get();
|
||||||
let public_matches = public.state.unwrap_matches();
|
let public_matches = public.state.unwrap_match();
|
||||||
|
|
||||||
assert_eq!(public_matches.matches, None);
|
assert_eq!(public_matches.info, None);
|
||||||
assert_eq!(public_matches.state, &widget_state);
|
assert_eq!(public_matches.state, &widget_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_nonempty() {
|
fn create_nonempty() {
|
||||||
let mut album_match = album_match();
|
let mut album_match = album_match();
|
||||||
let matches = AppMachine::matches(
|
let matches = AppMachine::match_state(
|
||||||
inner(music_hoard(vec![])),
|
inner(music_hoard(vec![])),
|
||||||
matches(Some(album_match.clone())),
|
match_state(Some(album_match.clone())),
|
||||||
);
|
);
|
||||||
album_match.push_cannot_have_mbid();
|
album_match.push_cannot_have_mbid();
|
||||||
|
|
||||||
@ -230,19 +230,19 @@ mod tests {
|
|||||||
|
|
||||||
let mut app: App = matches.into();
|
let mut app: App = matches.into();
|
||||||
let public = app.get();
|
let public = app.get();
|
||||||
let public_matches = public.state.unwrap_matches();
|
let public_matches = public.state.unwrap_match();
|
||||||
|
|
||||||
assert_eq!(public_matches.matches, Some(&album_match));
|
assert_eq!(public_matches.info, Some(&album_match));
|
||||||
assert_eq!(public_matches.state, &widget_state);
|
assert_eq!(public_matches.state, &widget_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches_flow(mut matches_info: AppMatchesInfo) {
|
fn match_state_flow(mut matches_info: MatchStateInfo) {
|
||||||
// tx must exist for rx to return Empty rather than Disconnected.
|
// tx must exist for rx to return Empty rather than Disconnected.
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let app_matches = AppMatches::new(Some(matches_info.clone()), AppFetch::new(rx));
|
let app_matches = MatchState::new(Some(matches_info.clone()), FetchState::new(rx));
|
||||||
|
|
||||||
let matches = AppMachine::matches(inner(music_hoard(vec![])), app_matches);
|
let matches = AppMachine::match_state(inner(music_hoard(vec![])), app_matches);
|
||||||
matches_info.push_cannot_have_mbid();
|
matches_info.push_cannot_have_mbid();
|
||||||
|
|
||||||
let mut widget_state = WidgetState::default();
|
let mut widget_state = WidgetState::default();
|
||||||
@ -251,23 +251,23 @@ mod tests {
|
|||||||
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
|
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
|
||||||
assert_eq!(matches.state.state, widget_state);
|
assert_eq!(matches.state.state, widget_state);
|
||||||
|
|
||||||
let matches = matches.prev_match().unwrap_matches();
|
let matches = matches.prev_match().unwrap_match();
|
||||||
|
|
||||||
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
|
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
|
||||||
assert_eq!(matches.state.state.list.selected(), Some(0));
|
assert_eq!(matches.state.state.list.selected(), Some(0));
|
||||||
|
|
||||||
let matches = matches.next_match().unwrap_matches();
|
let matches = matches.next_match().unwrap_match();
|
||||||
|
|
||||||
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
|
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
|
||||||
assert_eq!(matches.state.state.list.selected(), Some(1));
|
assert_eq!(matches.state.state.list.selected(), Some(1));
|
||||||
|
|
||||||
// Next is CannotHaveMBID
|
// Next is CannotHaveMBID
|
||||||
let matches = matches.next_match().unwrap_matches();
|
let matches = matches.next_match().unwrap_match();
|
||||||
|
|
||||||
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
|
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
|
||||||
assert_eq!(matches.state.state.list.selected(), Some(2));
|
assert_eq!(matches.state.state.list.selected(), Some(2));
|
||||||
|
|
||||||
let matches = matches.next_match().unwrap_matches();
|
let matches = matches.next_match().unwrap_match();
|
||||||
|
|
||||||
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
|
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
|
||||||
assert_eq!(matches.state.state.list.selected(), Some(2));
|
assert_eq!(matches.state.state.list.selected(), Some(2));
|
||||||
@ -278,20 +278,20 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn artist_matches_flow() {
|
fn artist_matches_flow() {
|
||||||
matches_flow(artist_match());
|
match_state_flow(artist_match());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn album_matches_flow() {
|
fn album_matches_flow() {
|
||||||
matches_flow(album_match());
|
match_state_flow(album_match());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn matches_abort() {
|
fn abort() {
|
||||||
let mut album_match = album_match();
|
let mut album_match = album_match();
|
||||||
let matches = AppMachine::matches(
|
let matches = AppMachine::match_state(
|
||||||
inner(music_hoard(vec![])),
|
inner(music_hoard(vec![])),
|
||||||
matches(Some(album_match.clone())),
|
match_state(Some(album_match.clone())),
|
||||||
);
|
);
|
||||||
album_match.push_cannot_have_mbid();
|
album_match.push_cannot_have_mbid();
|
||||||
|
|
||||||
@ -305,10 +305,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn matches_select_empty() {
|
fn select_empty() {
|
||||||
// Note that what really matters in this test is actually that the transmit channel has
|
// Note that what really matters in this test is actually that the transmit channel has
|
||||||
// disconnected and so the receive within AppFetch concludes there are no more matches.
|
// disconnected and so the receive within FetchState concludes there are no more matches.
|
||||||
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches(None));
|
let matches = AppMachine::match_state(inner(music_hoard(vec![])), match_state(None));
|
||||||
matches.select().unwrap_browse();
|
matches.select().unwrap_browse();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,40 +1,40 @@
|
|||||||
mod browse;
|
mod browse_state;
|
||||||
mod critical;
|
mod critical_state;
|
||||||
mod error;
|
mod error_state;
|
||||||
mod fetch;
|
mod fetch_state;
|
||||||
mod info;
|
mod info_state;
|
||||||
mod matches;
|
mod match_state;
|
||||||
mod reload;
|
mod reload_state;
|
||||||
mod search;
|
mod search_state;
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{selection::Selection, AppPublic, AppPublicInner, AppState, IAppAccess, IAppInteract},
|
app::{selection::Selection, AppPublic, AppPublicInner, AppState, IApp, IAppAccess},
|
||||||
event::EventSender,
|
event::EventSender,
|
||||||
lib::{interface::musicbrainz::IMusicBrainz, IMusicHoard},
|
lib::{interface::musicbrainz::IMusicBrainz, IMusicHoard},
|
||||||
};
|
};
|
||||||
|
|
||||||
use browse::AppBrowse;
|
use browse_state::BrowseState;
|
||||||
use critical::AppCritical;
|
use critical_state::CriticalState;
|
||||||
use error::AppError;
|
use error_state::ErrorState;
|
||||||
use fetch::AppFetch;
|
use fetch_state::FetchState;
|
||||||
use info::AppInfo;
|
use info_state::InfoState;
|
||||||
use matches::AppMatches;
|
use match_state::MatchState;
|
||||||
use reload::AppReload;
|
use reload_state::ReloadState;
|
||||||
use search::AppSearch;
|
use search_state::SearchState;
|
||||||
|
|
||||||
use super::IAppBase;
|
use super::IAppBase;
|
||||||
|
|
||||||
pub type App = AppState<
|
pub type App = AppState<
|
||||||
AppMachine<AppBrowse>,
|
AppMachine<BrowseState>,
|
||||||
AppMachine<AppInfo>,
|
AppMachine<InfoState>,
|
||||||
AppMachine<AppReload>,
|
AppMachine<ReloadState>,
|
||||||
AppMachine<AppSearch>,
|
AppMachine<SearchState>,
|
||||||
AppMachine<AppFetch>,
|
AppMachine<FetchState>,
|
||||||
AppMachine<AppMatches>,
|
AppMachine<MatchState>,
|
||||||
AppMachine<AppError>,
|
AppMachine<ErrorState>,
|
||||||
AppMachine<AppCritical>,
|
AppMachine<CriticalState>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
pub struct AppMachine<STATE> {
|
pub struct AppMachine<STATE> {
|
||||||
@ -59,8 +59,8 @@ impl App {
|
|||||||
let init_result = Self::init(&mut music_hoard);
|
let init_result = Self::init(&mut music_hoard);
|
||||||
let inner = AppInner::new(music_hoard, musicbrainz, events);
|
let inner = AppInner::new(music_hoard, musicbrainz, events);
|
||||||
match init_result {
|
match init_result {
|
||||||
Ok(()) => AppMachine::browse(inner).into(),
|
Ok(()) => AppMachine::browse_state(inner).into(),
|
||||||
Err(err) => AppMachine::critical(inner, err.to_string()).into(),
|
Err(err) => AppMachine::critical_state(inner, err.to_string()).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,40 +71,40 @@ impl App {
|
|||||||
|
|
||||||
fn inner_ref(&self) -> &AppInner {
|
fn inner_ref(&self) -> &AppInner {
|
||||||
match self {
|
match self {
|
||||||
AppState::Browse(browse) => &browse.inner,
|
AppState::Browse(browse_state) => &browse_state.inner,
|
||||||
AppState::Info(info) => &info.inner,
|
AppState::Info(info_state) => &info_state.inner,
|
||||||
AppState::Reload(reload) => &reload.inner,
|
AppState::Reload(reload_state) => &reload_state.inner,
|
||||||
AppState::Search(search) => &search.inner,
|
AppState::Search(search_state) => &search_state.inner,
|
||||||
AppState::Fetch(fetch) => &fetch.inner,
|
AppState::Fetch(fetch_state) => &fetch_state.inner,
|
||||||
AppState::Matches(matches) => &matches.inner,
|
AppState::Match(match_state) => &match_state.inner,
|
||||||
AppState::Error(error) => &error.inner,
|
AppState::Error(error_state) => &error_state.inner,
|
||||||
AppState::Critical(critical) => &critical.inner,
|
AppState::Critical(critical_state) => &critical_state.inner,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inner_mut(&mut self) -> &mut AppInner {
|
fn inner_mut(&mut self) -> &mut AppInner {
|
||||||
match self {
|
match self {
|
||||||
AppState::Browse(browse) => &mut browse.inner,
|
AppState::Browse(browse_state) => &mut browse_state.inner,
|
||||||
AppState::Info(info) => &mut info.inner,
|
AppState::Info(info_state) => &mut info_state.inner,
|
||||||
AppState::Reload(reload) => &mut reload.inner,
|
AppState::Reload(reload_state) => &mut reload_state.inner,
|
||||||
AppState::Search(search) => &mut search.inner,
|
AppState::Search(search_state) => &mut search_state.inner,
|
||||||
AppState::Fetch(fetch) => &mut fetch.inner,
|
AppState::Fetch(fetch_state) => &mut fetch_state.inner,
|
||||||
AppState::Matches(matches) => &mut matches.inner,
|
AppState::Match(match_state) => &mut match_state.inner,
|
||||||
AppState::Error(error) => &mut error.inner,
|
AppState::Error(error_state) => &mut error_state.inner,
|
||||||
AppState::Critical(critical) => &mut critical.inner,
|
AppState::Critical(critical_state) => &mut critical_state.inner,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IAppInteract for App {
|
impl IApp for App {
|
||||||
type BS = AppMachine<AppBrowse>;
|
type BrowseState = AppMachine<BrowseState>;
|
||||||
type IS = AppMachine<AppInfo>;
|
type InfoState = AppMachine<InfoState>;
|
||||||
type RS = AppMachine<AppReload>;
|
type ReloadState = AppMachine<ReloadState>;
|
||||||
type SS = AppMachine<AppSearch>;
|
type SearchState = AppMachine<SearchState>;
|
||||||
type FS = AppMachine<AppFetch>;
|
type FetchState = AppMachine<FetchState>;
|
||||||
type MS = AppMachine<AppMatches>;
|
type MatchState = AppMachine<MatchState>;
|
||||||
type ES = AppMachine<AppError>;
|
type ErrorState = AppMachine<ErrorState>;
|
||||||
type CS = AppMachine<AppCritical>;
|
type CriticalState = AppMachine<CriticalState>;
|
||||||
|
|
||||||
fn is_running(&self) -> bool {
|
fn is_running(&self) -> bool {
|
||||||
self.inner_ref().running
|
self.inner_ref().running
|
||||||
@ -117,8 +117,16 @@ impl IAppInteract for App {
|
|||||||
|
|
||||||
fn state(
|
fn state(
|
||||||
self,
|
self,
|
||||||
) -> AppState<Self::BS, Self::IS, Self::RS, Self::SS, Self::FS, Self::MS, Self::ES, Self::CS>
|
) -> AppState<
|
||||||
{
|
Self::BrowseState,
|
||||||
|
Self::InfoState,
|
||||||
|
Self::ReloadState,
|
||||||
|
Self::SearchState,
|
||||||
|
Self::FetchState,
|
||||||
|
Self::MatchState,
|
||||||
|
Self::ErrorState,
|
||||||
|
Self::CriticalState,
|
||||||
|
> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,7 +147,7 @@ impl IAppAccess for App {
|
|||||||
AppState::Reload(reload) => reload.into(),
|
AppState::Reload(reload) => reload.into(),
|
||||||
AppState::Search(search) => search.into(),
|
AppState::Search(search) => search.into(),
|
||||||
AppState::Fetch(fetch) => fetch.into(),
|
AppState::Fetch(fetch) => fetch.into(),
|
||||||
AppState::Matches(matches) => matches.into(),
|
AppState::Match(matches) => matches.into(),
|
||||||
AppState::Error(error) => error.into(),
|
AppState::Error(error) => error.into(),
|
||||||
AppState::Critical(critical) => critical.into(),
|
AppState::Critical(critical) => critical.into(),
|
||||||
}
|
}
|
||||||
@ -179,7 +187,7 @@ mod tests {
|
|||||||
use musichoard::collection::Collection;
|
use musichoard::collection::Collection;
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{AppState, IAppInteract, IAppInteractBrowse},
|
app::{AppState, IApp, IAppInteractBrowse},
|
||||||
lib::{interface::musicbrainz::MockIMusicBrainz, MockIMusicHoard},
|
lib::{interface::musicbrainz::MockIMusicBrainz, MockIMusicHoard},
|
||||||
EventChannel,
|
EventChannel,
|
||||||
};
|
};
|
||||||
@ -222,9 +230,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unwrap_matches(self) -> MS {
|
pub fn unwrap_match(self) -> MS {
|
||||||
match self {
|
match self {
|
||||||
AppState::Matches(matches) => matches,
|
AppState::Match(matches) => matches,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -375,7 +383,7 @@ mod tests {
|
|||||||
|
|
||||||
let (_, rx) = mpsc::channel();
|
let (_, rx) = mpsc::channel();
|
||||||
let inner = app.unwrap_browse().inner;
|
let inner = app.unwrap_browse().inner;
|
||||||
let state = AppFetch::new(rx);
|
let state = FetchState::new(rx);
|
||||||
app = AppMachine { inner, state }.into();
|
app = AppMachine { inner, state }.into();
|
||||||
|
|
||||||
let state = app.state();
|
let state = app.state();
|
||||||
@ -400,20 +408,21 @@ mod tests {
|
|||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
let (_, rx) = mpsc::channel();
|
let (_, rx) = mpsc::channel();
|
||||||
let fetch = AppFetch::new(rx);
|
let fetch = FetchState::new(rx);
|
||||||
app = AppMachine::matches(app.unwrap_browse().inner, AppMatches::new(None, fetch)).into();
|
app =
|
||||||
|
AppMachine::match_state(app.unwrap_browse().inner, MatchState::new(None, fetch)).into();
|
||||||
|
|
||||||
let state = app.state();
|
let state = app.state();
|
||||||
assert!(matches!(state, AppState::Matches(_)));
|
assert!(matches!(state, AppState::Match(_)));
|
||||||
app = state;
|
app = state;
|
||||||
|
|
||||||
app = app.no_op();
|
app = app.no_op();
|
||||||
let state = app.state();
|
let state = app.state();
|
||||||
assert!(matches!(state, AppState::Matches(_)));
|
assert!(matches!(state, AppState::Match(_)));
|
||||||
app = state;
|
app = state;
|
||||||
|
|
||||||
let public = app.get();
|
let public = app.get();
|
||||||
assert!(matches!(public.state, AppState::Matches(_)));
|
assert!(matches!(public.state, AppState::Match(_)));
|
||||||
|
|
||||||
let app = app.force_quit();
|
let app = app.force_quit();
|
||||||
assert!(!app.is_running());
|
assert!(!app.is_running());
|
||||||
@ -424,7 +433,7 @@ mod tests {
|
|||||||
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
|
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
app = AppMachine::error(app.unwrap_browse().inner, "get rekt").into();
|
app = AppMachine::error_state(app.unwrap_browse().inner, "get rekt").into();
|
||||||
|
|
||||||
let state = app.state();
|
let state = app.state();
|
||||||
assert!(matches!(state, AppState::Error(_)));
|
assert!(matches!(state, AppState::Error(_)));
|
||||||
@ -447,7 +456,7 @@ mod tests {
|
|||||||
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
|
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
app = AppMachine::critical(app.unwrap_browse().inner, "get rekt").into();
|
app = AppMachine::critical_state(app.unwrap_browse().inner, "get rekt").into();
|
||||||
|
|
||||||
let state = app.state();
|
let state = app.state();
|
||||||
assert!(matches!(state, AppState::Critical(_)));
|
assert!(matches!(state, AppState::Critical(_)));
|
||||||
|
@ -4,24 +4,24 @@ use crate::tui::app::{
|
|||||||
AppPublic, AppState, IAppInteractReload,
|
AppPublic, AppState, IAppInteractReload,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AppReload;
|
pub struct ReloadState;
|
||||||
|
|
||||||
impl AppMachine<AppReload> {
|
impl AppMachine<ReloadState> {
|
||||||
pub fn reload(inner: AppInner) -> Self {
|
pub fn reload_state(inner: AppInner) -> Self {
|
||||||
AppMachine {
|
AppMachine {
|
||||||
inner,
|
inner,
|
||||||
state: AppReload,
|
state: ReloadState,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AppMachine<AppReload>> for App {
|
impl From<AppMachine<ReloadState>> for App {
|
||||||
fn from(machine: AppMachine<AppReload>) -> Self {
|
fn from(machine: AppMachine<ReloadState>) -> Self {
|
||||||
AppState::Reload(machine)
|
AppState::Reload(machine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> From<&'a mut AppMachine<AppReload>> for AppPublic<'a> {
|
impl<'a> From<&'a mut AppMachine<ReloadState>> for AppPublic<'a> {
|
||||||
fn from(machine: &'a mut AppMachine<AppReload>) -> Self {
|
fn from(machine: &'a mut AppMachine<ReloadState>) -> Self {
|
||||||
AppPublic {
|
AppPublic {
|
||||||
inner: (&mut machine.inner).into(),
|
inner: (&mut machine.inner).into(),
|
||||||
state: AppState::Reload(()),
|
state: AppState::Reload(()),
|
||||||
@ -29,7 +29,7 @@ impl<'a> From<&'a mut AppMachine<AppReload>> for AppPublic<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IAppInteractReload for AppMachine<AppReload> {
|
impl IAppInteractReload for AppMachine<ReloadState> {
|
||||||
type APP = App;
|
type APP = App;
|
||||||
|
|
||||||
fn reload_library(mut self) -> Self::APP {
|
fn reload_library(mut self) -> Self::APP {
|
||||||
@ -51,7 +51,7 @@ impl IAppInteractReload for AppMachine<AppReload> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn hide_reload_menu(self) -> Self::APP {
|
fn hide_reload_menu(self) -> Self::APP {
|
||||||
AppMachine::browse(self.inner).into()
|
AppMachine::browse_state(self.inner).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,16 +59,16 @@ trait IAppInteractReloadPrivate {
|
|||||||
fn refresh(self, previous: KeySelection, result: Result<(), musichoard::Error>) -> App;
|
fn refresh(self, previous: KeySelection, result: Result<(), musichoard::Error>) -> App;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IAppInteractReloadPrivate for AppMachine<AppReload> {
|
impl IAppInteractReloadPrivate for AppMachine<ReloadState> {
|
||||||
fn refresh(mut self, previous: KeySelection, result: Result<(), musichoard::Error>) -> App {
|
fn refresh(mut self, previous: KeySelection, result: Result<(), musichoard::Error>) -> App {
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
self.inner
|
self.inner
|
||||||
.selection
|
.selection
|
||||||
.select_by_id(self.inner.music_hoard.get_collection(), previous);
|
.select_by_id(self.inner.music_hoard.get_collection(), previous);
|
||||||
AppMachine::browse(self.inner).into()
|
AppMachine::browse_state(self.inner).into()
|
||||||
}
|
}
|
||||||
Err(err) => AppMachine::error(self.inner, err.to_string()).into(),
|
Err(err) => AppMachine::error_state(self.inner, err.to_string()).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hide_reload_menu() {
|
fn hide_reload_menu() {
|
||||||
let reload = AppMachine::reload(inner(music_hoard(vec![])));
|
let reload = AppMachine::reload_state(inner(music_hoard(vec![])));
|
||||||
let app = reload.hide_reload_menu();
|
let app = reload.hide_reload_menu();
|
||||||
app.unwrap_browse();
|
app.unwrap_browse();
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|| Ok(()));
|
.return_once(|| Ok(()));
|
||||||
|
|
||||||
let reload = AppMachine::reload(inner(music_hoard));
|
let reload = AppMachine::reload_state(inner(music_hoard));
|
||||||
let app = reload.reload_database();
|
let app = reload.reload_database();
|
||||||
app.unwrap_browse();
|
app.unwrap_browse();
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|| Ok(()));
|
.return_once(|| Ok(()));
|
||||||
|
|
||||||
let reload = AppMachine::reload(inner(music_hoard));
|
let reload = AppMachine::reload_state(inner(music_hoard));
|
||||||
let app = reload.reload_library();
|
let app = reload.reload_library();
|
||||||
app.unwrap_browse();
|
app.unwrap_browse();
|
||||||
}
|
}
|
||||||
@ -123,7 +123,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
|
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
|
||||||
|
|
||||||
let reload = AppMachine::reload(inner(music_hoard));
|
let reload = AppMachine::reload_state(inner(music_hoard));
|
||||||
let app = reload.reload_database();
|
let app = reload.reload_database();
|
||||||
app.unwrap_error();
|
app.unwrap_error();
|
||||||
}
|
}
|
@ -20,22 +20,22 @@ const REPLACE: [&str; 11] = ["-", "-", "-", "-", "-", "'", "'", "\"", "\"", "...
|
|||||||
static AC: Lazy<AhoCorasick> =
|
static AC: Lazy<AhoCorasick> =
|
||||||
Lazy::new(|| AhoCorasick::new(SPECIAL.map(|ch| ch.to_string())).unwrap());
|
Lazy::new(|| AhoCorasick::new(SPECIAL.map(|ch| ch.to_string())).unwrap());
|
||||||
|
|
||||||
pub struct AppSearch {
|
pub struct SearchState {
|
||||||
string: String,
|
string: String,
|
||||||
orig: ListSelection,
|
orig: ListSelection,
|
||||||
memo: Vec<AppSearchMemo>,
|
memo: Vec<SearchStateMemo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AppSearchMemo {
|
struct SearchStateMemo {
|
||||||
index: Option<usize>,
|
index: Option<usize>,
|
||||||
char: bool,
|
char: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppMachine<AppSearch> {
|
impl AppMachine<SearchState> {
|
||||||
pub fn search(inner: AppInner, orig: ListSelection) -> Self {
|
pub fn search_state(inner: AppInner, orig: ListSelection) -> Self {
|
||||||
AppMachine {
|
AppMachine {
|
||||||
inner,
|
inner,
|
||||||
state: AppSearch {
|
state: SearchState {
|
||||||
string: String::new(),
|
string: String::new(),
|
||||||
orig,
|
orig,
|
||||||
memo: vec![],
|
memo: vec![],
|
||||||
@ -44,14 +44,14 @@ impl AppMachine<AppSearch> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AppMachine<AppSearch>> for App {
|
impl From<AppMachine<SearchState>> for App {
|
||||||
fn from(machine: AppMachine<AppSearch>) -> Self {
|
fn from(machine: AppMachine<SearchState>) -> Self {
|
||||||
AppState::Search(machine)
|
AppState::Search(machine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a mut AppMachine<AppSearch>> for AppPublic<'a> {
|
impl<'a> From<&'a mut AppMachine<SearchState>> for AppPublic<'a> {
|
||||||
fn from(machine: &'a mut AppMachine<AppSearch>) -> Self {
|
fn from(machine: &'a mut AppMachine<SearchState>) -> Self {
|
||||||
AppPublic {
|
AppPublic {
|
||||||
inner: (&mut machine.inner).into(),
|
inner: (&mut machine.inner).into(),
|
||||||
state: AppState::Search(&machine.state.string),
|
state: AppState::Search(&machine.state.string),
|
||||||
@ -59,13 +59,13 @@ impl<'a> From<&'a mut AppMachine<AppSearch>> for AppPublic<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IAppInteractSearch for AppMachine<AppSearch> {
|
impl IAppInteractSearch for AppMachine<SearchState> {
|
||||||
type APP = App;
|
type APP = App;
|
||||||
|
|
||||||
fn append_character(mut self, ch: char) -> Self::APP {
|
fn append_character(mut self, ch: char) -> Self::APP {
|
||||||
self.state.string.push(ch);
|
self.state.string.push(ch);
|
||||||
let index = self.inner.selection.selected();
|
let index = self.inner.selection.selected();
|
||||||
self.state.memo.push(AppSearchMemo { index, char: true });
|
self.state.memo.push(SearchStateMemo { index, char: true });
|
||||||
self.incremental_search(false);
|
self.incremental_search(false);
|
||||||
self.into()
|
self.into()
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ impl IAppInteractSearch for AppMachine<AppSearch> {
|
|||||||
fn search_next(mut self) -> Self::APP {
|
fn search_next(mut self) -> Self::APP {
|
||||||
if !self.state.string.is_empty() {
|
if !self.state.string.is_empty() {
|
||||||
let index = self.inner.selection.selected();
|
let index = self.inner.selection.selected();
|
||||||
self.state.memo.push(AppSearchMemo { index, char: false });
|
self.state.memo.push(SearchStateMemo { index, char: false });
|
||||||
self.incremental_search(true);
|
self.incremental_search(true);
|
||||||
}
|
}
|
||||||
self.into()
|
self.into()
|
||||||
@ -91,12 +91,12 @@ impl IAppInteractSearch for AppMachine<AppSearch> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn finish_search(self) -> Self::APP {
|
fn finish_search(self) -> Self::APP {
|
||||||
AppMachine::browse(self.inner).into()
|
AppMachine::browse_state(self.inner).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancel_search(mut self) -> Self::APP {
|
fn cancel_search(mut self) -> Self::APP {
|
||||||
self.inner.selection.select_by_list(self.state.orig);
|
self.inner.selection.select_by_list(self.state.orig);
|
||||||
AppMachine::browse(self.inner).into()
|
AppMachine::browse_state(self.inner).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +120,7 @@ trait IAppInteractSearchPrivate {
|
|||||||
fn normalize_search(search: &str, lowercase: bool, asciify: bool) -> String;
|
fn normalize_search(search: &str, lowercase: bool, asciify: bool) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IAppInteractSearchPrivate for AppMachine<AppSearch> {
|
impl IAppInteractSearchPrivate for AppMachine<SearchState> {
|
||||||
fn incremental_search(&mut self, next: bool) {
|
fn incremental_search(&mut self, next: bool) {
|
||||||
let collection = self.inner.music_hoard.get_collection();
|
let collection = self.inner.music_hoard.get_collection();
|
||||||
let search = &self.state.string;
|
let search = &self.state.string;
|
||||||
@ -254,7 +254,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn artist_incremental_search() {
|
fn artist_incremental_search() {
|
||||||
// Empty collection.
|
// Empty collection.
|
||||||
let mut search = AppMachine::search(inner(music_hoard(vec![])), orig(None));
|
let mut search = AppMachine::search_state(inner(music_hoard(vec![])), orig(None));
|
||||||
assert_eq!(search.inner.selection.selected(), None);
|
assert_eq!(search.inner.selection.selected(), None);
|
||||||
|
|
||||||
search.state.string = String::from("album_artist 'a'");
|
search.state.string = String::from("album_artist 'a'");
|
||||||
@ -263,7 +263,7 @@ mod tests {
|
|||||||
|
|
||||||
// Basic test, first element.
|
// Basic test, first element.
|
||||||
let mut search =
|
let mut search =
|
||||||
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
assert_eq!(search.inner.selection.selected(), Some(0));
|
assert_eq!(search.inner.selection.selected(), Some(0));
|
||||||
|
|
||||||
search.state.string = String::from("");
|
search.state.string = String::from("");
|
||||||
@ -280,7 +280,7 @@ mod tests {
|
|||||||
|
|
||||||
// Basic test, non-first element.
|
// Basic test, non-first element.
|
||||||
let mut search =
|
let mut search =
|
||||||
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
assert_eq!(search.inner.selection.selected(), Some(0));
|
assert_eq!(search.inner.selection.selected(), Some(0));
|
||||||
|
|
||||||
search.state.string = String::from("album_artist ");
|
search.state.string = String::from("album_artist ");
|
||||||
@ -293,7 +293,7 @@ mod tests {
|
|||||||
|
|
||||||
// Non-lowercase.
|
// Non-lowercase.
|
||||||
let mut search =
|
let mut search =
|
||||||
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
assert_eq!(search.inner.selection.selected(), Some(0));
|
assert_eq!(search.inner.selection.selected(), Some(0));
|
||||||
|
|
||||||
search.state.string = String::from("Album_Artist ");
|
search.state.string = String::from("Album_Artist ");
|
||||||
@ -306,7 +306,7 @@ mod tests {
|
|||||||
|
|
||||||
// Non-ascii.
|
// Non-ascii.
|
||||||
let mut search =
|
let mut search =
|
||||||
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
assert_eq!(search.inner.selection.selected(), Some(0));
|
assert_eq!(search.inner.selection.selected(), Some(0));
|
||||||
|
|
||||||
search.state.string = String::from("album_artist ");
|
search.state.string = String::from("album_artist ");
|
||||||
@ -319,7 +319,7 @@ mod tests {
|
|||||||
|
|
||||||
// Non-lowercase, non-ascii.
|
// Non-lowercase, non-ascii.
|
||||||
let mut search =
|
let mut search =
|
||||||
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
assert_eq!(search.inner.selection.selected(), Some(0));
|
assert_eq!(search.inner.selection.selected(), Some(0));
|
||||||
|
|
||||||
search.state.string = String::from("Album_Artist ");
|
search.state.string = String::from("Album_Artist ");
|
||||||
@ -332,7 +332,7 @@ mod tests {
|
|||||||
|
|
||||||
// Stop at name, not sort name.
|
// Stop at name, not sort name.
|
||||||
let mut search =
|
let mut search =
|
||||||
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
assert_eq!(search.inner.selection.selected(), Some(0));
|
assert_eq!(search.inner.selection.selected(), Some(0));
|
||||||
|
|
||||||
search.state.string = String::from("the ");
|
search.state.string = String::from("the ");
|
||||||
@ -345,7 +345,7 @@ mod tests {
|
|||||||
|
|
||||||
// Search next with common prefix.
|
// Search next with common prefix.
|
||||||
let mut search =
|
let mut search =
|
||||||
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
assert_eq!(search.inner.selection.selected(), Some(0));
|
assert_eq!(search.inner.selection.selected(), Some(0));
|
||||||
|
|
||||||
search.state.string = String::from("album_artist");
|
search.state.string = String::from("album_artist");
|
||||||
@ -368,7 +368,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn album_incremental_search() {
|
fn album_incremental_search() {
|
||||||
let mut search =
|
let mut search =
|
||||||
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
search.inner.selection.increment_category();
|
search.inner.selection.increment_category();
|
||||||
assert_eq!(search.inner.selection.category(), Category::Album);
|
assert_eq!(search.inner.selection.category(), Category::Album);
|
||||||
|
|
||||||
@ -392,7 +392,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn track_incremental_search() {
|
fn track_incremental_search() {
|
||||||
let mut search =
|
let mut search =
|
||||||
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
search.inner.selection.increment_category();
|
search.inner.selection.increment_category();
|
||||||
search.inner.selection.increment_category();
|
search.inner.selection.increment_category();
|
||||||
assert_eq!(search.inner.selection.category(), Category::Track);
|
assert_eq!(search.inner.selection.category(), Category::Track);
|
||||||
@ -418,7 +418,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn search() {
|
fn search() {
|
||||||
let search = AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
|
let search =
|
||||||
|
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
|
||||||
assert_eq!(search.inner.selection.selected(), Some(0));
|
assert_eq!(search.inner.selection.selected(), Some(0));
|
||||||
|
|
||||||
let search = search.append_character('a').unwrap_search();
|
let search = search.append_character('a').unwrap_search();
|
||||||
@ -458,7 +459,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn search_next_step_back() {
|
fn search_next_step_back() {
|
||||||
let search = AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
|
let search =
|
||||||
|
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
|
||||||
assert_eq!(search.inner.selection.selected(), Some(0));
|
assert_eq!(search.inner.selection.selected(), Some(0));
|
||||||
|
|
||||||
let search = search.step_back().unwrap_search();
|
let search = search.step_back().unwrap_search();
|
||||||
@ -495,7 +497,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cancel_search() {
|
fn cancel_search() {
|
||||||
let search = AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
|
let search =
|
||||||
|
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
|
||||||
assert_eq!(search.inner.selection.selected(), Some(0));
|
assert_eq!(search.inner.selection.selected(), Some(0));
|
||||||
|
|
||||||
let search = search.append_character('a').unwrap_search();
|
let search = search.append_character('a').unwrap_search();
|
||||||
@ -522,7 +525,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_search() {
|
fn empty_search() {
|
||||||
let search = AppMachine::search(inner(music_hoard(vec![])), orig(None));
|
let search = AppMachine::search_state(inner(music_hoard(vec![])), orig(None));
|
||||||
assert_eq!(search.inner.selection.selected(), None);
|
assert_eq!(search.inner.selection.selected(), None);
|
||||||
|
|
||||||
let search = search.append_character('a').unwrap_search();
|
let search = search.append_character('a').unwrap_search();
|
||||||
@ -552,7 +555,7 @@ mod benches {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
type Search = AppMachine<MockIMusicHoard, AppSearch>;
|
type Search = AppMachine<MockIMusicHoard, SearchState>;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn is_char_sensitive(b: &mut Bencher) {
|
fn is_char_sensitive(b: &mut Bencher) {
|
@ -8,26 +8,37 @@ use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, Collection};
|
|||||||
|
|
||||||
use crate::tui::lib::interface::musicbrainz::Match;
|
use crate::tui::lib::interface::musicbrainz::Match;
|
||||||
|
|
||||||
pub enum AppState<BS, IS, RS, SS, FS, MS, ES, CS> {
|
pub enum AppState<
|
||||||
Browse(BS),
|
BrowseState,
|
||||||
Info(IS),
|
InfoState,
|
||||||
Reload(RS),
|
ReloadState,
|
||||||
Search(SS),
|
SearchState,
|
||||||
Fetch(FS),
|
FetchState,
|
||||||
Matches(MS),
|
MatchState,
|
||||||
Error(ES),
|
ErrorState,
|
||||||
Critical(CS),
|
CriticalState,
|
||||||
|
> {
|
||||||
|
Browse(BrowseState),
|
||||||
|
Info(InfoState),
|
||||||
|
Reload(ReloadState),
|
||||||
|
Search(SearchState),
|
||||||
|
Fetch(FetchState),
|
||||||
|
Match(MatchState),
|
||||||
|
Error(ErrorState),
|
||||||
|
Critical(CriticalState),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IAppInteract {
|
pub trait IApp {
|
||||||
type BS: IAppBase<APP = Self> + IAppInteractBrowse<APP = Self>;
|
type BrowseState: IAppBase<APP = Self> + IAppInteractBrowse<APP = Self>;
|
||||||
type IS: IAppBase<APP = Self> + IAppInteractInfo<APP = Self>;
|
type InfoState: IAppBase<APP = Self> + IAppInteractInfo<APP = Self>;
|
||||||
type RS: IAppBase<APP = Self> + IAppInteractReload<APP = Self>;
|
type ReloadState: IAppBase<APP = Self> + IAppInteractReload<APP = Self>;
|
||||||
type SS: IAppBase<APP = Self> + IAppInteractSearch<APP = Self>;
|
type SearchState: IAppBase<APP = Self> + IAppInteractSearch<APP = Self>;
|
||||||
type FS: IAppBase<APP = Self> + IAppInteractFetch<APP = Self> + IAppEventFetch<APP = Self>;
|
type FetchState: IAppBase<APP = Self>
|
||||||
type MS: IAppBase<APP = Self> + IAppInteractMatches<APP = Self>;
|
+ IAppInteractFetch<APP = Self>
|
||||||
type ES: IAppBase<APP = Self> + IAppInteractError<APP = Self>;
|
+ IAppEventFetch<APP = Self>;
|
||||||
type CS: IAppBase<APP = Self>;
|
type MatchState: IAppBase<APP = Self> + IAppInteractMatch<APP = Self>;
|
||||||
|
type ErrorState: IAppBase<APP = Self> + IAppInteractError<APP = Self>;
|
||||||
|
type CriticalState: IAppBase<APP = Self>;
|
||||||
|
|
||||||
fn is_running(&self) -> bool;
|
fn is_running(&self) -> bool;
|
||||||
fn force_quit(self) -> Self;
|
fn force_quit(self) -> Self;
|
||||||
@ -35,17 +46,26 @@ pub trait IAppInteract {
|
|||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn state(
|
fn state(
|
||||||
self,
|
self,
|
||||||
) -> AppState<Self::BS, Self::IS, Self::RS, Self::SS, Self::FS, Self::MS, Self::ES, Self::CS>;
|
) -> AppState<
|
||||||
|
Self::BrowseState,
|
||||||
|
Self::InfoState,
|
||||||
|
Self::ReloadState,
|
||||||
|
Self::SearchState,
|
||||||
|
Self::FetchState,
|
||||||
|
Self::MatchState,
|
||||||
|
Self::ErrorState,
|
||||||
|
Self::CriticalState,
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IAppBase {
|
pub trait IAppBase {
|
||||||
type APP: IAppInteract;
|
type APP: IApp;
|
||||||
|
|
||||||
fn no_op(self) -> Self::APP;
|
fn no_op(self) -> Self::APP;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IAppInteractBrowse {
|
pub trait IAppInteractBrowse {
|
||||||
type APP: IAppInteract;
|
type APP: IApp;
|
||||||
|
|
||||||
fn quit(self) -> Self::APP;
|
fn quit(self) -> Self::APP;
|
||||||
|
|
||||||
@ -64,13 +84,13 @@ pub trait IAppInteractBrowse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait IAppInteractInfo {
|
pub trait IAppInteractInfo {
|
||||||
type APP: IAppInteract;
|
type APP: IApp;
|
||||||
|
|
||||||
fn hide_info_overlay(self) -> Self::APP;
|
fn hide_info_overlay(self) -> Self::APP;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IAppInteractReload {
|
pub trait IAppInteractReload {
|
||||||
type APP: IAppInteract;
|
type APP: IApp;
|
||||||
|
|
||||||
fn reload_library(self) -> Self::APP;
|
fn reload_library(self) -> Self::APP;
|
||||||
fn reload_database(self) -> Self::APP;
|
fn reload_database(self) -> Self::APP;
|
||||||
@ -78,7 +98,7 @@ pub trait IAppInteractReload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait IAppInteractSearch {
|
pub trait IAppInteractSearch {
|
||||||
type APP: IAppInteract;
|
type APP: IApp;
|
||||||
|
|
||||||
fn append_character(self, ch: char) -> Self::APP;
|
fn append_character(self, ch: char) -> Self::APP;
|
||||||
fn search_next(self) -> Self::APP;
|
fn search_next(self) -> Self::APP;
|
||||||
@ -88,19 +108,19 @@ pub trait IAppInteractSearch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait IAppInteractFetch {
|
pub trait IAppInteractFetch {
|
||||||
type APP: IAppInteract;
|
type APP: IApp;
|
||||||
|
|
||||||
fn abort(self) -> Self::APP;
|
fn abort(self) -> Self::APP;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IAppEventFetch {
|
pub trait IAppEventFetch {
|
||||||
type APP: IAppInteract;
|
type APP: IApp;
|
||||||
|
|
||||||
fn fetch_result_ready(self) -> Self::APP;
|
fn fetch_result_ready(self) -> Self::APP;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IAppInteractMatches {
|
pub trait IAppInteractMatch {
|
||||||
type APP: IAppInteract;
|
type APP: IApp;
|
||||||
|
|
||||||
fn prev_match(self) -> Self::APP;
|
fn prev_match(self) -> Self::APP;
|
||||||
fn next_match(self) -> Self::APP;
|
fn next_match(self) -> Self::APP;
|
||||||
@ -110,7 +130,7 @@ pub trait IAppInteractMatches {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait IAppInteractError {
|
pub trait IAppInteractError {
|
||||||
type APP: IAppInteract;
|
type APP: IApp;
|
||||||
|
|
||||||
fn dismiss_error(self) -> Self::APP;
|
fn dismiss_error(self) -> Self::APP;
|
||||||
}
|
}
|
||||||
@ -146,42 +166,42 @@ impl<T> From<Match<T>> for MatchOption<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct AppArtistMatches {
|
pub struct ArtistMatches {
|
||||||
pub matching: ArtistMeta,
|
pub matching: ArtistMeta,
|
||||||
pub list: Vec<MatchOption<ArtistMeta>>,
|
pub list: Vec<MatchOption<ArtistMeta>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct AppAlbumMatches {
|
pub struct AlbumMatches {
|
||||||
pub matching: AlbumMeta,
|
pub matching: AlbumMeta,
|
||||||
pub list: Vec<MatchOption<AlbumMeta>>,
|
pub list: Vec<MatchOption<AlbumMeta>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum AppMatchesInfo {
|
pub enum MatchStateInfo {
|
||||||
Artist(AppArtistMatches),
|
Artist(ArtistMatches),
|
||||||
Album(AppAlbumMatches),
|
Album(AlbumMatches),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppMatchesInfo {
|
impl MatchStateInfo {
|
||||||
pub fn artist<M: Into<MatchOption<ArtistMeta>>>(matching: ArtistMeta, list: Vec<M>) -> Self {
|
pub fn artist<M: Into<MatchOption<ArtistMeta>>>(matching: ArtistMeta, list: Vec<M>) -> Self {
|
||||||
let list: Vec<MatchOption<ArtistMeta>> = list.into_iter().map(Into::into).collect();
|
let list: Vec<MatchOption<ArtistMeta>> = list.into_iter().map(Into::into).collect();
|
||||||
AppMatchesInfo::Artist(AppArtistMatches { matching, list })
|
MatchStateInfo::Artist(ArtistMatches { matching, list })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn album<M: Into<MatchOption<AlbumMeta>>>(matching: AlbumMeta, list: Vec<M>) -> Self {
|
pub fn album<M: Into<MatchOption<AlbumMeta>>>(matching: AlbumMeta, list: Vec<M>) -> Self {
|
||||||
let list: Vec<MatchOption<AlbumMeta>> = list.into_iter().map(Into::into).collect();
|
let list: Vec<MatchOption<AlbumMeta>> = list.into_iter().map(Into::into).collect();
|
||||||
AppMatchesInfo::Album(AppAlbumMatches { matching, list })
|
MatchStateInfo::Album(AlbumMatches { matching, list })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppPublicMatches<'app> {
|
pub struct MatchStatePublic<'app> {
|
||||||
pub matches: Option<&'app AppMatchesInfo>,
|
pub info: Option<&'app MatchStateInfo>,
|
||||||
pub state: &'app mut WidgetState,
|
pub state: &'app mut WidgetState,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type AppPublicState<'app> =
|
pub type AppPublicState<'app> =
|
||||||
AppState<(), (), (), &'app str, (), AppPublicMatches<'app>, &'app str, &'app str>;
|
AppState<(), (), (), &'app str, (), MatchStatePublic<'app>, &'app str, &'app str>;
|
||||||
|
|
||||||
impl<BS, IS, RS, SS, FS, MS, ES, CS> AppState<BS, IS, RS, SS, FS, MS, ES, CS> {
|
impl<BS, IS, RS, SS, FS, MS, ES, CS> AppState<BS, IS, RS, SS, FS, MS, ES, CS> {
|
||||||
pub fn is_search(&self) -> bool {
|
pub fn is_search(&self) -> bool {
|
||||||
|
@ -5,8 +5,8 @@ use mockall::automock;
|
|||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{
|
app::{
|
||||||
AppState, Delta, IAppInteract, IAppInteractBrowse, IAppInteractError, IAppInteractFetch,
|
AppState, Delta, IApp, IAppInteractBrowse, IAppInteractError, IAppInteractFetch,
|
||||||
IAppInteractInfo, IAppInteractMatches, IAppInteractReload, IAppInteractSearch,
|
IAppInteractInfo, IAppInteractMatch, IAppInteractReload, IAppInteractSearch,
|
||||||
},
|
},
|
||||||
event::{Event, EventError, EventReceiver},
|
event::{Event, EventError, EventReceiver},
|
||||||
};
|
};
|
||||||
@ -14,20 +14,20 @@ use crate::tui::{
|
|||||||
use super::app::{IAppBase, IAppEventFetch};
|
use super::app::{IAppBase, IAppEventFetch};
|
||||||
|
|
||||||
#[cfg_attr(test, automock)]
|
#[cfg_attr(test, automock)]
|
||||||
pub trait IEventHandler<APP: IAppInteract> {
|
pub trait IEventHandler<APP: IApp> {
|
||||||
fn handle_next_event(&self, app: APP) -> Result<APP, EventError>;
|
fn handle_next_event(&self, app: APP) -> Result<APP, EventError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
trait IEventHandlerPrivate<APP: IAppInteract> {
|
trait IEventHandlerPrivate<APP: IApp> {
|
||||||
fn handle_key_event(app: APP, key_event: KeyEvent) -> APP;
|
fn handle_key_event(app: APP, key_event: KeyEvent) -> APP;
|
||||||
fn handle_browse_key_event(app: <APP as IAppInteract>::BS, key_event: KeyEvent) -> APP;
|
fn handle_browse_key_event(app: <APP as IApp>::BrowseState, key_event: KeyEvent) -> APP;
|
||||||
fn handle_info_key_event(app: <APP as IAppInteract>::IS, key_event: KeyEvent) -> APP;
|
fn handle_info_key_event(app: <APP as IApp>::InfoState, key_event: KeyEvent) -> APP;
|
||||||
fn handle_reload_key_event(app: <APP as IAppInteract>::RS, key_event: KeyEvent) -> APP;
|
fn handle_reload_key_event(app: <APP as IApp>::ReloadState, key_event: KeyEvent) -> APP;
|
||||||
fn handle_search_key_event(app: <APP as IAppInteract>::SS, key_event: KeyEvent) -> APP;
|
fn handle_search_key_event(app: <APP as IApp>::SearchState, key_event: KeyEvent) -> APP;
|
||||||
fn handle_fetch_key_event(app: <APP as IAppInteract>::FS, key_event: KeyEvent) -> APP;
|
fn handle_fetch_key_event(app: <APP as IApp>::FetchState, key_event: KeyEvent) -> APP;
|
||||||
fn handle_matches_key_event(app: <APP as IAppInteract>::MS, key_event: KeyEvent) -> APP;
|
fn handle_match_key_event(app: <APP as IApp>::MatchState, key_event: KeyEvent) -> APP;
|
||||||
fn handle_error_key_event(app: <APP as IAppInteract>::ES, key_event: KeyEvent) -> APP;
|
fn handle_error_key_event(app: <APP as IApp>::ErrorState, key_event: KeyEvent) -> APP;
|
||||||
fn handle_critical_key_event(app: <APP as IAppInteract>::CS, key_event: KeyEvent) -> APP;
|
fn handle_critical_key_event(app: <APP as IApp>::CriticalState, key_event: KeyEvent) -> APP;
|
||||||
|
|
||||||
fn handle_fetch_result_ready_event(app: APP) -> APP;
|
fn handle_fetch_result_ready_event(app: APP) -> APP;
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<APP: IAppInteract> IEventHandler<APP> for EventHandler {
|
impl<APP: IApp> IEventHandler<APP> for EventHandler {
|
||||||
fn handle_next_event(&self, app: APP) -> Result<APP, EventError> {
|
fn handle_next_event(&self, app: APP) -> Result<APP, EventError> {
|
||||||
Ok(match self.events.recv()? {
|
Ok(match self.events.recv()? {
|
||||||
Event::Key(key_event) => Self::handle_key_event(app, key_event),
|
Event::Key(key_event) => Self::handle_key_event(app, key_event),
|
||||||
@ -52,7 +52,7 @@ impl<APP: IAppInteract> IEventHandler<APP> for EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
impl<APP: IApp> IEventHandlerPrivate<APP> for EventHandler {
|
||||||
fn handle_key_event(app: APP, key_event: KeyEvent) -> APP {
|
fn handle_key_event(app: APP, key_event: KeyEvent) -> APP {
|
||||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
if key_event.modifiers == KeyModifiers::CONTROL {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
@ -63,47 +63,39 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match app.state() {
|
match app.state() {
|
||||||
AppState::Browse(browse) => {
|
AppState::Browse(browse_state) => {
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_browse_key_event(browse, key_event)
|
Self::handle_browse_key_event(browse_state, key_event)
|
||||||
}
|
}
|
||||||
AppState::Info(info) => {
|
AppState::Info(info_state) => Self::handle_info_key_event(info_state, key_event),
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_info_key_event(info, key_event)
|
AppState::Reload(reload_state) => {
|
||||||
|
Self::handle_reload_key_event(reload_state, key_event)
|
||||||
}
|
}
|
||||||
AppState::Reload(reload) => {
|
AppState::Search(search_state) => {
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_reload_key_event(reload, key_event)
|
Self::handle_search_key_event(search_state, key_event)
|
||||||
}
|
}
|
||||||
AppState::Search(search) => {
|
AppState::Fetch(fetch_state) => Self::handle_fetch_key_event(fetch_state, key_event),
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_search_key_event(search, key_event)
|
AppState::Match(match_state) => Self::handle_match_key_event(match_state, key_event),
|
||||||
}
|
AppState::Error(error_state) => Self::handle_error_key_event(error_state, key_event),
|
||||||
AppState::Fetch(fetch) => {
|
AppState::Critical(critical_state) => {
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_fetch_key_event(fetch, key_event)
|
Self::handle_critical_key_event(critical_state, key_event)
|
||||||
}
|
|
||||||
AppState::Matches(matches) => {
|
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_matches_key_event(matches, key_event)
|
|
||||||
}
|
|
||||||
AppState::Error(error) => {
|
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_error_key_event(error, key_event)
|
|
||||||
}
|
|
||||||
AppState::Critical(critical) => {
|
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_critical_key_event(critical, key_event)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_fetch_result_ready_event(app: APP) -> APP {
|
fn handle_fetch_result_ready_event(app: APP) -> APP {
|
||||||
match app.state() {
|
match app.state() {
|
||||||
AppState::Browse(browse) => browse.no_op(),
|
AppState::Browse(browse_state) => browse_state.no_op(),
|
||||||
AppState::Info(info) => info.no_op(),
|
AppState::Info(info_state) => info_state.no_op(),
|
||||||
AppState::Reload(reload) => reload.no_op(),
|
AppState::Reload(reload_state) => reload_state.no_op(),
|
||||||
AppState::Search(search) => search.no_op(),
|
AppState::Search(search_state) => search_state.no_op(),
|
||||||
AppState::Fetch(fetch) => fetch.fetch_result_ready(),
|
AppState::Fetch(fetch_state) => fetch_state.fetch_result_ready(),
|
||||||
AppState::Matches(matches) => matches.no_op(),
|
AppState::Match(match_state) => match_state.no_op(),
|
||||||
AppState::Error(error) => error.no_op(),
|
AppState::Error(error_state) => error_state.no_op(),
|
||||||
AppState::Critical(critical) => critical.no_op(),
|
AppState::Critical(critical_state) => critical_state.no_op(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_browse_key_event(app: <APP as IAppInteract>::BS, key_event: KeyEvent) -> APP {
|
fn handle_browse_key_event(app: <APP as IApp>::BrowseState, key_event: KeyEvent) -> APP {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
// Exit application on `ESC` or `q`.
|
// Exit application on `ESC` or `q`.
|
||||||
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.quit(),
|
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.quit(),
|
||||||
@ -133,7 +125,7 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_info_key_event(app: <APP as IAppInteract>::IS, key_event: KeyEvent) -> APP {
|
fn handle_info_key_event(app: <APP as IApp>::InfoState, key_event: KeyEvent) -> APP {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
// Toggle overlay.
|
// Toggle overlay.
|
||||||
KeyCode::Esc
|
KeyCode::Esc
|
||||||
@ -146,7 +138,7 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_reload_key_event(app: <APP as IAppInteract>::RS, key_event: KeyEvent) -> APP {
|
fn handle_reload_key_event(app: <APP as IApp>::ReloadState, key_event: KeyEvent) -> APP {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
// Reload keys.
|
// Reload keys.
|
||||||
KeyCode::Char('l') | KeyCode::Char('L') => app.reload_library(),
|
KeyCode::Char('l') | KeyCode::Char('L') => app.reload_library(),
|
||||||
@ -162,7 +154,7 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_search_key_event(app: <APP as IAppInteract>::SS, key_event: KeyEvent) -> APP {
|
fn handle_search_key_event(app: <APP as IApp>::SearchState, key_event: KeyEvent) -> APP {
|
||||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
if key_event.modifiers == KeyModifiers::CONTROL {
|
||||||
return match key_event.code {
|
return match key_event.code {
|
||||||
KeyCode::Char('s') | KeyCode::Char('S') => app.search_next(),
|
KeyCode::Char('s') | KeyCode::Char('S') => app.search_next(),
|
||||||
@ -182,7 +174,7 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_fetch_key_event(app: <APP as IAppInteract>::FS, key_event: KeyEvent) -> APP {
|
fn handle_fetch_key_event(app: <APP as IApp>::FetchState, key_event: KeyEvent) -> APP {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
// Abort.
|
// Abort.
|
||||||
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.abort(),
|
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.abort(),
|
||||||
@ -191,7 +183,7 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_matches_key_event(app: <APP as IAppInteract>::MS, key_event: KeyEvent) -> APP {
|
fn handle_match_key_event(app: <APP as IApp>::MatchState, key_event: KeyEvent) -> APP {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
// Abort.
|
// Abort.
|
||||||
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.abort(),
|
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.abort(),
|
||||||
@ -204,12 +196,12 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_error_key_event(app: <APP as IAppInteract>::ES, _key_event: KeyEvent) -> APP {
|
fn handle_error_key_event(app: <APP as IApp>::ErrorState, _key_event: KeyEvent) -> APP {
|
||||||
// Any key dismisses the error.
|
// Any key dismisses the error.
|
||||||
app.dismiss_error()
|
app.dismiss_error()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_critical_key_event(app: <APP as IAppInteract>::CS, _key_event: KeyEvent) -> APP {
|
fn handle_critical_key_event(app: <APP as IApp>::CriticalState, _key_event: KeyEvent) -> APP {
|
||||||
// No action is allowed.
|
// No action is allowed.
|
||||||
app.no_op()
|
app.no_op()
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ use ratatui::{backend::Backend, Terminal};
|
|||||||
use std::{io, marker::PhantomData};
|
use std::{io, marker::PhantomData};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{IAppAccess, IAppInteract},
|
app::{IApp, IAppAccess},
|
||||||
event::EventError,
|
event::EventError,
|
||||||
handler::IEventHandler,
|
handler::IEventHandler,
|
||||||
listener::IEventListener,
|
listener::IEventListener,
|
||||||
@ -46,12 +46,12 @@ impl From<EventError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tui<B: Backend, UI: IUi, APP: IAppInteract + IAppAccess> {
|
pub struct Tui<B: Backend, UI: IUi, APP: IApp + IAppAccess> {
|
||||||
terminal: Terminal<B>,
|
terminal: Terminal<B>,
|
||||||
_phantom: PhantomData<(UI, APP)>,
|
_phantom: PhantomData<(UI, APP)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Backend, UI: IUi, APP: IAppInteract + IAppAccess> Tui<B, UI, APP> {
|
impl<B: Backend, UI: IUi, APP: IApp + IAppAccess> Tui<B, UI, APP> {
|
||||||
fn init(&mut self) -> Result<(), Error> {
|
fn init(&mut self) -> Result<(), Error> {
|
||||||
self.terminal.hide_cursor()?;
|
self.terminal.hide_cursor()?;
|
||||||
self.terminal.clear()?;
|
self.terminal.clear()?;
|
||||||
|
@ -4,7 +4,7 @@ use musichoard::collection::{
|
|||||||
track::{TrackFormat, TrackQuality},
|
track::{TrackFormat, TrackQuality},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::app::{AppMatchesInfo, MatchOption};
|
use crate::tui::app::{MatchOption, MatchStateInfo};
|
||||||
|
|
||||||
pub struct UiDisplay;
|
pub struct UiDisplay;
|
||||||
|
|
||||||
@ -114,11 +114,11 @@ impl UiDisplay {
|
|||||||
"Matching nothing"
|
"Matching nothing"
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_matching_info(matches: Option<&AppMatchesInfo>) -> String {
|
pub fn display_matching_info(info: Option<&MatchStateInfo>) -> String {
|
||||||
match matches.as_ref() {
|
match info.as_ref() {
|
||||||
Some(kind) => match kind {
|
Some(kind) => match kind {
|
||||||
AppMatchesInfo::Artist(m) => UiDisplay::display_artist_matching(&m.matching),
|
MatchStateInfo::Artist(m) => UiDisplay::display_artist_matching(&m.matching),
|
||||||
AppMatchesInfo::Album(m) => UiDisplay::display_album_matching(&m.matching),
|
MatchStateInfo::Album(m) => UiDisplay::display_album_matching(&m.matching),
|
||||||
},
|
},
|
||||||
None => UiDisplay::display_nothing_matching().to_string(),
|
None => UiDisplay::display_nothing_matching().to_string(),
|
||||||
}
|
}
|
||||||
|
@ -2,29 +2,29 @@ use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta};
|
|||||||
use ratatui::widgets::{List, ListItem};
|
use ratatui::widgets::{List, ListItem};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{AppMatchesInfo, MatchOption, WidgetState},
|
app::{MatchOption, MatchStateInfo, WidgetState},
|
||||||
ui::display::UiDisplay,
|
ui::display::UiDisplay,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct MatchesState<'a, 'b> {
|
pub struct MatchOverlay<'a, 'b> {
|
||||||
pub matching: String,
|
pub matching: String,
|
||||||
pub list: List<'a>,
|
pub list: List<'a>,
|
||||||
pub state: &'b mut WidgetState,
|
pub state: &'b mut WidgetState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> MatchesState<'a, 'b> {
|
impl<'a, 'b> MatchOverlay<'a, 'b> {
|
||||||
pub fn new(matches: Option<&AppMatchesInfo>, state: &'b mut WidgetState) -> Self {
|
pub fn new(info: Option<&MatchStateInfo>, state: &'b mut WidgetState) -> Self {
|
||||||
match matches {
|
match info {
|
||||||
Some(info) => match info {
|
Some(info) => match info {
|
||||||
AppMatchesInfo::Artist(m) => Self::artists(&m.matching, &m.list, state),
|
MatchStateInfo::Artist(m) => Self::artists(&m.matching, &m.list, state),
|
||||||
AppMatchesInfo::Album(m) => Self::albums(&m.matching, &m.list, state),
|
MatchStateInfo::Album(m) => Self::albums(&m.matching, &m.list, state),
|
||||||
},
|
},
|
||||||
None => Self::empty(state),
|
None => Self::empty(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn empty(state: &'b mut WidgetState) -> Self {
|
fn empty(state: &'b mut WidgetState) -> Self {
|
||||||
MatchesState {
|
MatchOverlay {
|
||||||
matching: UiDisplay::display_nothing_matching().to_string(),
|
matching: UiDisplay::display_nothing_matching().to_string(),
|
||||||
list: List::default(),
|
list: List::default(),
|
||||||
state,
|
state,
|
||||||
@ -46,7 +46,7 @@ impl<'a, 'b> MatchesState<'a, 'b> {
|
|||||||
.collect::<Vec<ListItem>>(),
|
.collect::<Vec<ListItem>>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
MatchesState {
|
MatchOverlay {
|
||||||
matching,
|
matching,
|
||||||
list,
|
list,
|
||||||
state,
|
state,
|
||||||
@ -68,7 +68,7 @@ impl<'a, 'b> MatchesState<'a, 'b> {
|
|||||||
.collect::<Vec<ListItem>>(),
|
.collect::<Vec<ListItem>>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
MatchesState {
|
MatchOverlay {
|
||||||
matching,
|
matching,
|
||||||
list,
|
list,
|
||||||
state,
|
state,
|
@ -60,9 +60,9 @@ impl Minibuffer<'_> {
|
|||||||
paragraphs: vec![Paragraph::new("fetching..."), Paragraph::new("q: abort")],
|
paragraphs: vec![Paragraph::new("fetching..."), Paragraph::new("q: abort")],
|
||||||
columns: 2,
|
columns: 2,
|
||||||
},
|
},
|
||||||
AppState::Matches(public) => Minibuffer {
|
AppState::Match(public) => Minibuffer {
|
||||||
paragraphs: vec![
|
paragraphs: vec![
|
||||||
Paragraph::new(UiDisplay::display_matching_info(public.matches)),
|
Paragraph::new(UiDisplay::display_matching_info(public.info)),
|
||||||
Paragraph::new("q: abort"),
|
Paragraph::new("q: abort"),
|
||||||
],
|
],
|
||||||
columns: 2,
|
columns: 2,
|
||||||
|
@ -1,33 +1,33 @@
|
|||||||
mod browse;
|
mod browse_state;
|
||||||
mod display;
|
mod display;
|
||||||
mod error;
|
mod error_state;
|
||||||
mod fetch;
|
mod fetch_state;
|
||||||
mod info;
|
mod info_state;
|
||||||
mod matches;
|
mod match_state;
|
||||||
mod minibuffer;
|
mod minibuffer;
|
||||||
mod overlay;
|
mod overlay;
|
||||||
mod reload;
|
mod reload_state;
|
||||||
mod style;
|
mod style;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
|
|
||||||
use fetch::FetchOverlay;
|
|
||||||
use ratatui::{layout::Rect, widgets::Paragraph, Frame};
|
use ratatui::{layout::Rect, widgets::Paragraph, Frame};
|
||||||
|
|
||||||
use musichoard::collection::{album::Album, Collection};
|
use musichoard::collection::{album::Album, Collection};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{AppMatchesInfo, AppPublicState, AppState, Category, IAppAccess, Selection, WidgetState},
|
app::{AppPublicState, AppState, Category, IAppAccess, MatchStateInfo, Selection, WidgetState},
|
||||||
ui::{
|
ui::{
|
||||||
browse::{
|
browse_state::{
|
||||||
AlbumArea, AlbumState, ArtistArea, ArtistState, FrameArea, TrackArea, TrackState,
|
AlbumArea, AlbumState, ArtistArea, ArtistState, FrameArea, TrackArea, TrackState,
|
||||||
},
|
},
|
||||||
display::UiDisplay,
|
display::UiDisplay,
|
||||||
error::ErrorOverlay,
|
error_state::ErrorOverlay,
|
||||||
info::{AlbumOverlay, ArtistOverlay},
|
fetch_state::FetchOverlay,
|
||||||
matches::MatchesState,
|
info_state::{AlbumOverlay, ArtistOverlay},
|
||||||
|
match_state::MatchOverlay,
|
||||||
minibuffer::Minibuffer,
|
minibuffer::Minibuffer,
|
||||||
overlay::{OverlayBuilder, OverlaySize},
|
overlay::{OverlayBuilder, OverlaySize},
|
||||||
reload::ReloadOverlay,
|
reload_state::ReloadOverlay,
|
||||||
widgets::UiWidget,
|
widgets::UiWidget,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -137,13 +137,13 @@ impl Ui {
|
|||||||
UiWidget::render_overlay_widget("Fetching", fetch_text, area, false, frame)
|
UiWidget::render_overlay_widget("Fetching", fetch_text, area, false, frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_matches_overlay(
|
fn render_match_overlay(
|
||||||
matches: Option<&AppMatchesInfo>,
|
info: Option<&MatchStateInfo>,
|
||||||
state: &mut WidgetState,
|
state: &mut WidgetState,
|
||||||
frame: &mut Frame,
|
frame: &mut Frame,
|
||||||
) {
|
) {
|
||||||
let area = OverlayBuilder::default().build(frame.size());
|
let area = OverlayBuilder::default().build(frame.size());
|
||||||
let st = MatchesState::new(matches, state);
|
let st = MatchOverlay::new(info, state);
|
||||||
UiWidget::render_overlay_list_widget(&st.matching, st.list, st.state, true, area, frame)
|
UiWidget::render_overlay_list_widget(&st.matching, st.list, st.state, true, area, frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,9 +169,7 @@ impl IUi for Ui {
|
|||||||
AppState::Info(()) => Self::render_info_overlay(collection, selection, frame),
|
AppState::Info(()) => Self::render_info_overlay(collection, selection, frame),
|
||||||
AppState::Reload(()) => Self::render_reload_overlay(frame),
|
AppState::Reload(()) => Self::render_reload_overlay(frame),
|
||||||
AppState::Fetch(()) => Self::render_fetch_overlay(frame),
|
AppState::Fetch(()) => Self::render_fetch_overlay(frame),
|
||||||
AppState::Matches(public) => {
|
AppState::Match(public) => Self::render_match_overlay(public.info, public.state, frame),
|
||||||
Self::render_matches_overlay(public.matches, public.state, frame)
|
|
||||||
}
|
|
||||||
AppState::Error(msg) => Self::render_error_overlay("Error", msg, frame),
|
AppState::Error(msg) => Self::render_error_overlay("Error", msg, frame),
|
||||||
AppState::Critical(msg) => Self::render_error_overlay("Critical Error", msg, frame),
|
AppState::Critical(msg) => Self::render_error_overlay("Critical Error", msg, frame),
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -187,7 +185,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{AppPublic, AppPublicInner, AppPublicMatches, Delta, MatchOption},
|
app::{AppPublic, AppPublicInner, Delta, MatchOption, MatchStatePublic},
|
||||||
lib::interface::musicbrainz::Match,
|
lib::interface::musicbrainz::Match,
|
||||||
testmod::COLLECTION,
|
testmod::COLLECTION,
|
||||||
tests::terminal,
|
tests::terminal,
|
||||||
@ -209,8 +207,8 @@ mod tests {
|
|||||||
AppState::Reload(()) => AppState::Reload(()),
|
AppState::Reload(()) => AppState::Reload(()),
|
||||||
AppState::Search(s) => AppState::Search(s),
|
AppState::Search(s) => AppState::Search(s),
|
||||||
AppState::Fetch(()) => AppState::Fetch(()),
|
AppState::Fetch(()) => AppState::Fetch(()),
|
||||||
AppState::Matches(ref mut m) => AppState::Matches(AppPublicMatches {
|
AppState::Match(ref mut m) => AppState::Match(MatchStatePublic {
|
||||||
matches: m.matches,
|
info: m.info,
|
||||||
state: m.state,
|
state: m.state,
|
||||||
}),
|
}),
|
||||||
AppState::Error(s) => AppState::Error(s),
|
AppState::Error(s) => AppState::Error(s),
|
||||||
@ -230,16 +228,16 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn artist_matches(matching: ArtistMeta, list: Vec<Match<ArtistMeta>>) -> AppMatchesInfo {
|
fn artist_matches(matching: ArtistMeta, list: Vec<Match<ArtistMeta>>) -> MatchStateInfo {
|
||||||
let mut list: Vec<MatchOption<ArtistMeta>> = list.into_iter().map(Into::into).collect();
|
let mut list: Vec<MatchOption<ArtistMeta>> = list.into_iter().map(Into::into).collect();
|
||||||
list.push(MatchOption::CannotHaveMbid);
|
list.push(MatchOption::CannotHaveMbid);
|
||||||
AppMatchesInfo::artist(matching, list)
|
MatchStateInfo::artist(matching, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_matches(matching: AlbumMeta, list: Vec<Match<AlbumMeta>>) -> AppMatchesInfo {
|
fn album_matches(matching: AlbumMeta, list: Vec<Match<AlbumMeta>>) -> MatchStateInfo {
|
||||||
let mut list: Vec<MatchOption<AlbumMeta>> = list.into_iter().map(Into::into).collect();
|
let mut list: Vec<MatchOption<AlbumMeta>> = list.into_iter().map(Into::into).collect();
|
||||||
list.push(MatchOption::CannotHaveMbid);
|
list.push(MatchOption::CannotHaveMbid);
|
||||||
AppMatchesInfo::album(matching, list)
|
MatchStateInfo::album(matching, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_test_suite(collection: &Collection, selection: &mut Selection) {
|
fn draw_test_suite(collection: &Collection, selection: &mut Selection) {
|
||||||
@ -322,8 +320,8 @@ mod tests {
|
|||||||
|
|
||||||
let mut app = AppPublic {
|
let mut app = AppPublic {
|
||||||
inner: public_inner(collection, &mut selection),
|
inner: public_inner(collection, &mut selection),
|
||||||
state: AppState::Matches(AppPublicMatches {
|
state: AppState::Match(MatchStatePublic {
|
||||||
matches: None,
|
info: None,
|
||||||
state: &mut widget_state,
|
state: &mut widget_state,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@ -351,8 +349,8 @@ mod tests {
|
|||||||
|
|
||||||
let mut app = AppPublic {
|
let mut app = AppPublic {
|
||||||
inner: public_inner(collection, &mut selection),
|
inner: public_inner(collection, &mut selection),
|
||||||
state: AppState::Matches(AppPublicMatches {
|
state: AppState::Match(MatchStatePublic {
|
||||||
matches: Some(&artist_matches),
|
info: Some(&artist_matches),
|
||||||
state: &mut widget_state,
|
state: &mut widget_state,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@ -385,8 +383,8 @@ mod tests {
|
|||||||
|
|
||||||
let mut app = AppPublic {
|
let mut app = AppPublic {
|
||||||
inner: public_inner(collection, &mut selection),
|
inner: public_inner(collection, &mut selection),
|
||||||
state: AppState::Matches(AppPublicMatches {
|
state: AppState::Match(MatchStatePublic {
|
||||||
matches: Some(&album_matches),
|
info: Some(&album_matches),
|
||||||
state: &mut widget_state,
|
state: &mut widget_state,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user