| 1 | use ratatui::{ |
| 2 | Frame, |
| 3 | layout::{Constraint, Rect}, |
| 4 | text::Span, |
| 5 | widgets::{Block, Row}, |
| 6 | }; |
| 7 | |
| 8 | use super::{InputPopup, InputPurpose, Popup, PopupState, frame_popup}; |
| 9 | use crate::{ |
| 10 | action::{Action, Context}, |
| 11 | config::KeyCombo, |
| 12 | mpd::Selection, |
| 13 | screens::Ctx, |
| 14 | ui::list::ListView, |
| 15 | }; |
| 16 | |
| 17 | enum Choice { |
| 18 | Queue, |
| 19 | NewPlaylist, |
| 20 | Playlist(String), |
| 21 | } |
| 22 | |
| 23 | /// Chooses where to add a selection: the queue, a new or an existing playlist. |
| 24 | pub struct AddToPlaylistPopup { |
| 25 | selection: Selection, |
| 26 | choices: Vec<Choice>, |
| 27 | list: ListView, |
| 28 | } |
| 29 | |
| 30 | impl AddToPlaylistPopup { |
| 31 | pub fn new(selection: Selection, ctx: &Ctx) -> Self { |
| 32 | let mut choices = vec![Choice::Queue, Choice::NewPlaylist]; |
| 33 | choices.extend( |
| 34 | ctx.mpd |
| 35 | .playlists |
| 36 | .iter() |
| 37 | .map(|p| Choice::Playlist(p.name.clone())), |
| 38 | ); |
| 39 | Self { |
| 40 | selection, |
| 41 | choices, |
| 42 | list: ListView::default(), |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | fn choose(&mut self, ctx: &mut Ctx) -> PopupState { |
| 47 | let Some(choice) = self |
| 48 | .list |
| 49 | .selected(self.choices.len()) |
| 50 | .and_then(|i| self.choices.get(i)) |
| 51 | else { |
| 52 | return PopupState::Open; |
| 53 | }; |
| 54 | match choice { |
| 55 | Choice::Queue => { |
| 56 | ctx.client.exec_all(self.selection.add_to_queue()); |
| 57 | ctx.notify("Added to the queue"); |
| 58 | } |
| 59 | Choice::NewPlaylist => { |
| 60 | ctx.popup(InputPopup::new( |
| 61 | "New playlist", |
| 62 | "", |
| 63 | InputPurpose::NewPlaylist(self.selection.clone()), |
| 64 | )); |
| 65 | } |
| 66 | Choice::Playlist(name) => { |
| 67 | ctx.client.exec_all(self.selection.add_to_playlist(name)); |
| 68 | ctx.notify(format!("Added to playlist “{name}”")); |
| 69 | } |
| 70 | } |
| 71 | PopupState::Closed |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | impl Popup for AddToPlaylistPopup { |
| 76 | fn draw(&mut self, frame: &mut Frame, area: Rect, ctx: &Ctx) { |
| 77 | let height = self.choices.len().min(15) as u16 + 2; |
| 78 | let inner = frame_popup(frame, area, 44, height, "Add to", ctx); |
| 79 | let theme = ctx.theme(); |
| 80 | let choices = &self.choices; |
| 81 | self.list.render( |
| 82 | frame, |
| 83 | inner, |
| 84 | Block::new(), |
| 85 | None, |
| 86 | &[Constraint::Fill(1)], |
| 87 | choices.len(), |
| 88 | true, |
| 89 | theme, |
| 90 | |i| { |
| 91 | Row::new([match &choices[i] { |
| 92 | Choice::Queue => Span::styled("Current queue", theme.accent()), |
| 93 | Choice::NewPlaylist => Span::styled("New playlist…", theme.accent()), |
| 94 | Choice::Playlist(name) => Span::styled(name.clone(), theme.text()), |
| 95 | }]) |
| 96 | }, |
| 97 | ); |
| 98 | } |
| 99 | |
| 100 | fn handle_key(&mut self, key: KeyCombo, ctx: &mut Ctx) -> PopupState { |
| 101 | match ctx |
| 102 | .config |
| 103 | .keymap |
| 104 | .resolve(&[Context::Popup, Context::Navigation], key) |
| 105 | { |
| 106 | Some(Action::Close) => PopupState::Closed, |
| 107 | Some(Action::Activate | Action::Right) => self.choose(ctx), |
| 108 | Some(action) => { |
| 109 | self.list.navigate(action, self.choices.len()); |
| 110 | PopupState::Open |
| 111 | } |
| 112 | None => PopupState::Open, |
| 113 | } |
| 114 | } |
| 115 | } |