From 885651a6e5ce671d222294e6528e9c3dc5daeede Mon Sep 17 00:00:00 2001
From: Kubat <mael.martin31@gmail.com>
Date: Mon, 23 Oct 2023 06:49:05 +0200
Subject: [PATCH] AMADEUS: Update the playlists component, use a vector where
 we control how we search for elements (buggy behaviour for the HashMap...) +
 Handle the case where the user deletes the playlist that is displayed in the
 main panel

---
 amadeus/src/app.rs                            |  10 +-
 amadeus/src/components/mainpanel/playlists.rs | 121 ++++++++++++++----
 amadeus/src/components/sidebar.rs             |  17 ++-
 3 files changed, 116 insertions(+), 32 deletions(-)

diff --git a/amadeus/src/app.rs b/amadeus/src/app.rs
index e1eba7fa..5bd13df5 100644
--- a/amadeus/src/app.rs
+++ b/amadeus/src/app.rs
@@ -243,6 +243,8 @@ impl Amadeus {
                     self.handle_refresh_request(RefreshRequest::Playlist(plt))
                 }
                 playlists::Request::Delete(plt) => Command::batch([
+                    self.sidebar
+                        .update(sidebar::Message::DeletePlaylist(plt.clone())),
                     self.mainpanel
                         .update(playlists::Message::DeletePlaylist(plt.clone()).into())
                         .map(Message::from),
@@ -257,6 +259,9 @@ impl Amadeus {
                 playlists::Request::RemoveFrom(name, id) => {
                     send(remove_from_playlist(cfg, name, KaraFilter::KId(id)))
                 }
+                playlists::Request::ChangeView => {
+                    Command::perform(async {}, |_| Message::MainPanelDisplay(Default::default()))
+                }
             },
 
             // Search/Database
@@ -676,14 +681,11 @@ impl Application for Amadeus {
             }
 
             // A message for the side panel.
+            Message::SidebarMessage(message) => self.sidebar.update(message),
             Message::SideBarResize(size) => {
                 self.sidebar_size = Some(size);
                 Command::none()
             }
-            Message::SidebarMessage(message) => {
-                self.sidebar.update(message);
-                Command::none()
-            }
 
             // Refresh from lektord.
             Message::RefreshRequest(req) => self.handle_refresh_request(req),
diff --git a/amadeus/src/components/mainpanel/playlists.rs b/amadeus/src/components/mainpanel/playlists.rs
index 9b11ea3c..1f3b77aa 100644
--- a/amadeus/src/components/mainpanel/playlists.rs
+++ b/amadeus/src/components/mainpanel/playlists.rs
@@ -1,5 +1,4 @@
 use crate::components::{self, icon, karalist, tip};
-use hashbrown::HashMap;
 use iced::{widget::row, Command, Element};
 use lektor_payloads::{KId, Kara, PlaylistInfo};
 use lektor_utils::log;
@@ -7,7 +6,7 @@ use std::sync::Arc;
 
 #[derive(Default)]
 pub struct State {
-    playlists: HashMap<Arc<str>, (PlaylistInfo, karalist::State)>,
+    playlists: Vec<(Arc<str>, PlaylistInfo, karalist::State)>,
     to_show: Option<Arc<str>>,
 }
 
@@ -16,16 +15,22 @@ pub enum Message {
     /// Reload the content of a playlist.
     Reload(Arc<str>, Vec<Arc<Kara>>),
 
+    /// Update informations about a playlist.
     UpdatePlaylistInfos(Arc<str>, PlaylistInfo),
 
+    /// Delete a playlist by its name.
     DeletePlaylist(Arc<str>),
 
+    /// Keep playlists, delete all the others.
     KeepPlaylists(Vec<Arc<str>>),
 
+    /// Remove a kara from a playlist by its Id.
     RemoveKaraFromPlaylist(Arc<str>, KId),
 
+    /// Add a kara to a playlist.
     AddKaraToPlaylist(Arc<str>, Arc<Kara>),
 
+    /// Message to notify which playlist to show.
     ShowPlaylist(Arc<str>),
 }
 
@@ -45,55 +50,120 @@ pub enum Request {
 
     /// Inner playlist event, wrapper around the [karalist::Request]
     Inner(Arc<str>, karalist::Request),
+
+    /// Message to tell Amadeus to change view. This can be the consequence of deleting a playlist.
+    ChangeView,
 }
 
 impl State {
+    /// Get a mut view to a playlist info, content, etc. If the playlist was not found returns
+    /// [None], otherwise returns [Some].
+    fn get_mut(
+        &mut self,
+        name: impl AsRef<str>,
+    ) -> Option<&mut (Arc<str>, PlaylistInfo, karalist::State)> {
+        self.playlists
+            .iter_mut()
+            .find(|(plt, ..)| plt.as_ref().eq(name.as_ref()))
+    }
+
+    /// Get a const view to a playlist info, content, etc. If the playlist was not found returns
+    /// [None], otherwise returns [Some].
+    fn get(&self, name: impl AsRef<str>) -> Option<&(Arc<str>, PlaylistInfo, karalist::State)> {
+        self.playlists
+            .iter()
+            .find(|(plt, ..)| plt.as_ref().eq(name.as_ref()))
+    }
+
+    /// Remove playlists from the list of playlists, returns whever the playlist was found and was
+    /// removed from the vector.
+    fn remove(&mut self, names: &[impl AsRef<str>]) -> bool {
+        let count = self.playlists.len();
+        self.playlists
+            .retain(|(name, ..)| names.iter().any(|n| n.as_ref().ne(name.as_ref())));
+        count != self.playlists.len()
+    }
+
+    /// Keep playlists from the list of playlists.
+    fn keep(&mut self, names: &[impl AsRef<str>]) {
+        self.playlists
+            .retain(|(name, ..)| names.iter().any(|n| n.as_ref().eq(name.as_ref())));
+    }
+
+    /// Returns whever the container contains the asked playlist or not.
+    fn contains(&self, name: impl AsRef<str>) -> bool {
+        self.playlists
+            .iter()
+            .find(|(plt, ..)| name.as_ref().eq(plt.as_ref()))
+            .is_some()
+    }
+
     pub fn update(&mut self, message: Message) -> Command<Request> {
         match message {
             Message::KeepPlaylists(plts) => {
-                self.playlists.retain(|name, _| plts.contains(name));
-                Command::none()
+                self.keep(&plts);
+                match self.to_show {
+                    Some(ref show) if !self.contains(show) => {
+                        Command::perform(async {}, |_| Request::ChangeView)
+                    }
+                    _ => Command::none(),
+                }
             }
+
             Message::Reload(plt, karas) => {
-                match self.playlists.get_mut(&plt) {
-                    Some((_, plt)) => plt.update(karalist::Message::Reload(karas)),
-                    None => log::error!("failed to update playlist {}", plt.as_ref()),
+                match self.get_mut(&plt) {
+                    Some((.., plt)) => plt.update(karalist::Message::Reload(karas)),
+                    None => log::error!("failed to update playlist {plt}"),
                 }
                 Command::none()
             }
+
             Message::UpdatePlaylistInfos(plt, infos) => {
-                match self.playlists.get_mut(&plt) {
-                    Some((old, _)) => *old = infos,
-                    None => log::error!("failed to update playlist {}", plt.as_ref()),
+                match self.get_mut(&plt) {
+                    Some((_, old, _)) => *old = infos,
+                    None => log::error!("failed to update playlist {plt}"),
                 }
                 Command::none()
             }
+
             Message::RemoveKaraFromPlaylist(plt, id) => {
-                match self.playlists.get_mut(&plt) {
-                    Some((_, plt)) => plt.update(karalist::Message::RemoveId(id)),
-                    None => log::error!(
-                        "failed to delete kara {id:?} from playlist {}",
-                        plt.as_ref()
-                    ),
+                match self.get_mut(&plt) {
+                    Some((.., plt)) => plt.update(karalist::Message::RemoveId(id)),
+                    None => log::error!("failed to delete kara {id:?} from playlist {plt}"),
                 }
                 Command::none()
             }
+
             Message::DeletePlaylist(plt) => {
-                if self.playlists.remove(&plt).is_none() {
-                    log::error!("failed do delete playlist {}", plt.as_ref())
+                if !self.remove(&[plt.as_ref()]) {
+                    log::error!("failed do delete playlist {plt}");
+                    Command::none()
+                } else if self
+                    .to_show
+                    .as_ref()
+                    .map(|show| show.as_ref().eq(plt.as_ref()))
+                    .unwrap_or_default()
+                {
+                    Command::perform(async {}, |_| Request::ChangeView)
+                } else {
+                    Command::none()
                 }
-                Command::none()
             }
+
             Message::AddKaraToPlaylist(plt, kara) => {
-                match self.playlists.get_mut(&plt) {
-                    Some(plt) => plt.1.update(karalist::Message::Add(kara)),
-                    None => log::error!("can't add kara {} to playlist {}", kara.id, plt.as_ref()),
+                match self.get_mut(&plt) {
+                    Some((.., plt)) => plt.update(karalist::Message::Add(kara)),
+                    None => log::error!("can't add kara {} to playlist {plt}", kara.id),
                 }
                 Command::none()
             }
 
             Message::ShowPlaylist(name) => {
-                self.to_show = Some(name);
+                if self.contains(&name) {
+                    self.to_show = Some(name);
+                } else {
+                    log::error!("asked to show playlist {name}, but it wasn't found");
+                }
                 Command::none()
             }
         }
@@ -107,9 +177,8 @@ impl State {
     }
 
     pub fn view(&self, plt: &Arc<str>) -> Element<'_, Request> {
-        self.playlists
-            .get_key_value(plt)
-            .map(|(plt, (_, content))| content.view().map(|req| Request::Inner(plt.clone(), req)))
+        self.get(plt)
+            .map(|(plt, _, content)| content.view().map(|req| Request::Inner(plt.clone(), req)))
             .unwrap_or(components::loading())
     }
 }
diff --git a/amadeus/src/components/sidebar.rs b/amadeus/src/components/sidebar.rs
index 235c6cae..26c7ce98 100644
--- a/amadeus/src/components/sidebar.rs
+++ b/amadeus/src/components/sidebar.rs
@@ -11,9 +11,10 @@ use iced::{
         scrollable::{Direction, Viewport},
         text, vertical_space, Column, Space,
     },
-    Element, Length,
+    Command, Element, Length,
 };
 use lektor_payloads::PlaylistName;
+use lektor_utils::log;
 use std::sync::Arc;
 
 pub struct State {
@@ -38,6 +39,7 @@ pub enum Request {
 pub enum Message {
     Scrolled(Viewport),
     Playlists(Vec<Arc<str>>),
+    DeletePlaylist(Arc<str>),
 }
 
 impl FromIterator<PlaylistName> for Message {
@@ -57,14 +59,25 @@ impl Default for State {
 }
 
 impl State {
-    pub fn update(&mut self, message: Message) {
+    pub fn update<T>(&mut self, message: Message) -> Command<T> {
         match message {
             Message::Scrolled(viewport) => {
                 self.current_scroll_offset = viewport.relative_offset();
+                Command::none()
             }
 
             Message::Playlists(playlists) => {
                 let _ = std::mem::replace(&mut self.playlists, playlists);
+                Command::none()
+            }
+
+            Message::DeletePlaylist(plt) => {
+                let count = self.playlists.len();
+                self.playlists.retain(|name| name.as_ref().ne(plt.as_ref()));
+                if count == self.playlists.len() {
+                    log::error!("failed to delete playlist {plt} from the sidebar");
+                }
+                Command::none()
             }
         }
     }
-- 
GitLab