Skip to content

Commit

Permalink
Implement modals and actions
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoRiether committed Feb 10, 2024
1 parent 3d0af4f commit 6cbf988
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 8 deletions.
2 changes: 1 addition & 1 deletion tori/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ impl<'a> App<'a> {
crossterm_event = channel.crossterm_rx.recv() => {
if let Some(ev) = crossterm_event {
let mut state = state.lock().await;
match handle_event(&mut state, ev) {
match handle_event(&mut state, channel.tx.clone(), ev) {
Ok(Some(a)) => channel.tx.send(a).expect("Failed to send action"),
Ok(None) => {}
Err(e) => state.notify_err(e.to_string()),
Expand Down
28 changes: 27 additions & 1 deletion tori/src/events/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,36 @@ pub enum Action {
ScrollDown,
ScrollUp,
Command(Command),
SongAdded { playlist: String, song: String },
RefreshSongs,
RefreshPlaylists,
SelectSong(usize),
SelectPlaylist(usize),
CloseModal,
AddPlaylist {
name: String,
},
RenamePlaylist {
playlist: String,
new_name: String,
},
DeletePlaylist {
playlist: String,
},
AddSongToPlaylist {
playlist: String,
song: String,
},
SongAdded {
playlist: String,
song: String,
},
RenameSong {
playlist: String,
index: usize,
new_name: String,
},
DeleteSong {
playlist: String,
index: usize,
},
}
96 changes: 96 additions & 0 deletions tori/src/update/browse_screen.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::mem;

use tui::style::{Color, Style};

use crate::{
app::modal::{ConfirmationModal, InputModal, Modal},
error::Result,
events::{channel::Tx, Action, Command},
input::Input,
Expand Down Expand Up @@ -141,6 +144,99 @@ fn browse_screen_command(
Focus::Songs => screen.shown_songs.select_last(),
Focus::PlaylistsFilter(_) | Focus::SongsFilter(_) => {}
},

Add => match &screen.focus {
Focus::Playlists | Focus::PlaylistsFilter(_) => {
state.modal = InputModal::new(" Add playlist ")
.style(Style::default().fg(Color::LightBlue))
.on_commit(|name| Action::AddPlaylist { name })
.some_box();
}
Focus::Songs | Focus::SongsFilter(_) => {
let playlist = match screen.selected_playlist() {
None => {
state.notify_err("Can't add a song without a playlist selected");
return Ok(None);
}
Some(p) => p.to_string(),
};

state.modal = InputModal::new(" Add song to playlist ")
.style(Style::default().fg(Color::LightBlue))
.on_commit(move |song| Action::AddSongToPlaylist { song, playlist })
.some_box();
}
},
Rename => {
let playlist = match screen.selected_playlist() {
None => {
state.notify_err("Can't rename a song without a playlist selected");
return Ok(None);
}
Some(p) => p.to_string(),
};
match &screen.focus {
Focus::Playlists | Focus::PlaylistsFilter(_) => {
state.modal = InputModal::new(" Rename playlist ")
.style(Style::default().fg(Color::LightBlue))
.on_commit(move |new_name| Action::RenamePlaylist { playlist, new_name })
.some_box();
}
Focus::Songs | Focus::SongsFilter(_) => {
let index = match screen.shown_songs.selected_item() {
None => {
state.notify_err("Can't rename a song without one selected");
return Ok(None);
}
Some(i) => i,
};

state.modal = InputModal::new(" Rename song ")
.style(Style::default().fg(Color::LightBlue))
.on_commit(move |new_name| Action::RenameSong {
playlist: playlist.clone(),
index,
new_name,
})
.some_box();
}
}
}
Delete => {
let playlist = match screen.selected_playlist() {
None => {
state.notify_err("Can't delete a song without a playlist selected");
return Ok(None);
}
Some(p) => p.to_string(),
};
match &screen.focus {
Focus::Playlists | Focus::PlaylistsFilter(_) => {
state.modal = ConfirmationModal::new(" Delete playlist ")
.style(Style::default().fg(Color::LightRed))
.on_yes(move || Action::DeletePlaylist { playlist })
.some_box();
}
Focus::Songs | Focus::SongsFilter(_) => {
let index = match screen.shown_songs.selected_item() {
None => {
state.notify_err("Can't delete a song without one selected");
return Ok(None);
}
Some(i) => i,
};

state.modal = ConfirmationModal::new(" Delete song ")
.style(Style::default().fg(Color::LightRed))
.on_yes(move || Action::DeleteSong {
playlist: playlist.clone(),
index,
})
.some_box();
}
}
}

_ => {}
}

Expand Down
79 changes: 73 additions & 6 deletions tori/src/update/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ use std::mem;
use crate::{
config::Config,
error::Result,
events::{channel::Tx, Action, Command},
events::{action::Level, channel::Tx, Action, Command},
input::InputResponse,
m3u::playlist_management,
player::Player,
state::{browse_screen::Focus, Screen, State},
};
use crossterm::event::{Event as TermEvent, KeyEvent, KeyEventKind, MouseEventKind};

pub fn handle_event(state: &mut State<'_>, ev: TermEvent) -> Result<Option<Action>> {
pub fn handle_event(state: &mut State<'_>, tx: Tx, ev: TermEvent) -> Result<Option<Action>> {
if let Some(modal) = &mut state.modal {
return modal.handle_event(tx, ev);
}

Ok(match ev {
TermEvent::Key(key) if key.kind != KeyEventKind::Release => match &mut state.screen {
Screen::None => unreachable!(),
Expand Down Expand Up @@ -81,9 +86,72 @@ pub fn update(state: &mut State<'_>, tx: Tx, act: Action) -> Result<Option<Actio
}
}

Notify(level, msg) => {
match level {
Level::Ok => state.notify_ok(msg),
Level::Info => state.notify_info(msg),
Level::Error => state.notify_err(msg),
};
}

CloseModal => {
state.modal = None;
}
AddPlaylist { name } => {
match playlist_management::create_playlist(&name) {
Ok(()) => state.notify_ok(format!("Playlist {name} created")),
Err(e) => state.notify_err(format!("Error creating playlist: {e:?}")),
};
return Ok(Some(Action::RefreshPlaylists));
}
RenamePlaylist { playlist, new_name } => {
match playlist_management::rename_playlist(&playlist, &new_name) {
Ok(()) => state.notify_ok(format!("Playlist {playlist} renamed to {new_name}")),
Err(e) => state.notify_err(format!("Error renaming playlist: {e:?}")),
}
return Ok(Some(Action::RefreshPlaylists));
}
DeletePlaylist { playlist } => {
match playlist_management::delete_playlist(&playlist) {
Ok(()) => state.notify_ok(format!("Playlist {playlist} deleted")),
Err(e) => state.notify_err(format!("Error deleting playlist: {e:?}")),
};
return Ok(Some(Action::RefreshPlaylists));
}
AddSongToPlaylist { playlist, song } => {
tokio::task::spawn(async move {
let res =
playlist_management::add_song(tx.clone(), playlist.clone(), song.clone()).await;
match res {
Ok(()) => tx
.send(Action::Notify(
Level::Info,
format!("Added {song} to {playlist} playlist"),
))
.ok(),
Err(e) => tx.send(Notify(Level::Error, format!("{:?}", e))).ok(),
};
tx.send(RefreshSongs).ok();
});
}
RenameSong {
playlist,
index,
new_name,
} => {
match playlist_management::rename_song(&playlist, index, &new_name) {
Ok(()) => {}
Err(e) => state.notify_err(format!("Error renaming song: {e:?}")),
};
return Ok(Some(Action::RefreshSongs));
}
DeleteSong { playlist, index } => {
match playlist_management::delete_song(&playlist, index) {
Ok(()) => {}
Err(e) => state.notify_err(format!("Error deleting song: {e:?}")),
}
return Ok(Some(Action::RefreshSongs));
}
}

Ok(None)
Expand All @@ -94,7 +162,9 @@ fn handle_command(state: &mut State<'_>, tx: Tx, cmd: Command) -> Result<Option<
match cmd {
Esc | Play | QueueSong | QueueShown | OpenInBrowser | CopyUrl | CopyTitle
| NextSortingMode | SelectLeft | SelectNext | SelectRight | SelectPrev | Search
| GotoStart | GotoEnd => return screen_action(state, tx, Action::Command(cmd)),
| GotoStart | GotoEnd | Add | Rename | Delete => {
return screen_action(state, tx, Action::Command(cmd))
}

Nop => {}
Quit => {
Expand Down Expand Up @@ -154,9 +224,6 @@ fn handle_command(state: &mut State<'_>, tx: Tx, cmd: Command) -> Result<Option<

OpenHelpModal => todo!(),
OpenHotkeyModal => todo!(),
Add => todo!(),
Rename => todo!(),
Delete => todo!(),
PlayFromModal => todo!(),

SwapSongDown => todo!(),
Expand Down

0 comments on commit 6cbf988

Please sign in to comment.