diff --git a/src/cljs/airsonic_ui/audio/playlist.cljs b/src/cljs/airsonic_ui/audio/playlist.cljs index 5a8d720..f51b34c 100644 --- a/src/cljs/airsonic_ui/audio/playlist.cljs +++ b/src/cljs/airsonic_ui/audio/playlist.cljs @@ -19,7 +19,10 @@ "Allows you to change how the next and previous song are selected") (enqueue-last [this song]) - (enqueue-next [this song])) + (enqueue-next [this song]) + + (move-song [this from-idx to-idx] + "Allows you to move a song in a playlist")) ;; helpers to manage creating playlists @@ -117,7 +120,29 @@ (conj (vary-meta song assoc :playlist/linear-order (inc current-idx))) (concat (subvec songs (inc current-idx))))] (assoc this :items (->> (map-indexed vector reordered) - (into (sorted-map))))))) + (into (sorted-map)))))) + + (move-song [this from-idx to-idx] + ;; we have to decide whether we move all items in-between + ;; one up or one down; this depends on whether we move our + ;; item to the front or to the back + (let [shift-fn (cond + (< from-idx to-idx) inc + (> from-idx to-idx) dec) + start (min from-idx to-idx) + end (inc (max from-idx to-idx)) + steps (range start end) + result (update this :items + (fn [items] + (-> (reduce (fn [result idx] + (assoc result idx (get items (shift-fn idx)))) + items steps) + (assoc to-idx (get items from-idx)))))] + (cond + (= from-idx current-idx) (assoc result :current-idx to-idx) + (<= to-idx current-idx from-idx) (update result :current-idx inc) + (>= to-idx current-idx from-idx) (update result :current-idx dec) + :else result)))) ;; constructor wrapper diff --git a/src/cljs/airsonic_ui/helpers.cljs b/src/cljs/airsonic_ui/helpers.cljs index 73ecb3e..58f14f6 100644 --- a/src/cljs/airsonic_ui/helpers.cljs +++ b/src/cljs/airsonic_ui/helpers.cljs @@ -47,10 +47,3 @@ (if brief? (brief-duration hours minutes seconds) (long-duration hours minutes seconds)))) - -(defn vector-move [coll prev-index new-index] - (let [items (into (subvec coll 0 prev-index) - (subvec coll (inc prev-index)))] - (-> (subvec items 0 new-index) - (conj (get coll prev-index)) - (into (subvec items new-index))))) diff --git a/test/cljs/airsonic_ui/audio/playlist_test.cljs b/test/cljs/airsonic_ui/audio/playlist_test.cljs index 0f50127..58ae6e6 100644 --- a/test/cljs/airsonic_ui/audio/playlist_test.cljs +++ b/test/cljs/airsonic_ui/audio/playlist_test.cljs @@ -250,11 +250,62 @@ (deftest enqueue-next (testing "Should play the song after the currently playing song" - (doseq [playback-mode '(:linear :shuffled) - repeat-mode '(:repeat-none :repeat-all)] + (doseq [playback-mode [:linear :shuffled] + repeat-mode [:repeat-none :repeat-all]] (let [length 5, queue (song-queue length) playlist (playlist/->playlist queue :playback-mode playback-mode :repeat-mode repeat-mode) next-song (song)] (is (same-song? next-song (-> (playlist/enqueue-next playlist next-song) (playlist/next-song) - (playlist/current-song)))))))) + (playlist/current-song)))))))) + +(deftest move-track + (testing "Should correctly set the new order" + (doseq [playback-mode [:linear :shuffled] + repeat-mode [:repeat-none :repeat-all :repeat-single]] + (let [n-songs 10 + queue (song-queue n-songs) + playlist (playlist/->playlist queue :repeat-mode repeat-mode :playback-mode playback-mode)] + (is (same-song? (-> (playlist/next-song playlist) + (playlist/next-song) + (playlist/current-song)) + (-> (playlist/move-song playlist 2 1) + (playlist/next-song) + (playlist/current-song))))))) + (testing "Should update the currently playing track's index" + (testing "when inserting a track before" + (doseq [playback-mode [:linear :shuffled] + repeat-mode [:repeat-none :repeat-all :repeat-single]] + (let [n-songs 10 + queue (song-queue n-songs) + playlist (playlist/->playlist queue :repeat-mode repeat-mode :playback-mode playback-mode)] + (is (= 4 (-> (playlist/set-current-song playlist 3) + (playlist/move-song 5 3) + :current-idx)))))) + (testing "when moving a track behind it" + (doseq [playback-mode [:linear :shuffled] + repeat-mode [:repeat-none :repeat-all :repeat-single]] + (let [n-songs 10 + queue (song-queue n-songs) + playlist (playlist/->playlist queue :repeat-mode repeat-mode :playback-mode playback-mode)] + (is (= 2 (-> (playlist/set-current-song playlist 3) + (playlist/move-song 2 5) + :current-idx)))))) + (testing "when moving it" + (doseq [playback-mode [:linear :shuffled] + repeat-mode [:repeat-none :repeat-all :repeat-single]] + (let [n-songs 10 + queue (song-queue n-songs) + playlist (playlist/->playlist queue :repeat-mode repeat-mode :playback-mode playback-mode)] + (is (= 7 (-> (playlist/set-current-song playlist 3) + (playlist/move-song 3 7) + :current-idx)))))) + (testing "when the current track is outside of the modified range" + (doseq [playback-mode [:linear :shuffled] + repeat-mode [:repeat-none :repeat-all :repeat-single]] + (let [n-songs 10 + queue (song-queue n-songs) + playlist (playlist/->playlist queue :repeat-mode repeat-mode :playback-mode playback-mode)] + (is (= 3 (-> (playlist/set-current-song playlist 3) + (playlist/move-song 4 7) + :current-idx))))))))