From ed4cafaa39ce72ce0c8de39617272d0c13005ff6 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 21 Jun 2019 19:43:18 +0200 Subject: [PATCH 01/99] Revamped the Playlist and PlaylistTab to work on latest master --- src/main/xerus/monstercat/MonsterUtilities.kt | 7 +- src/main/xerus/monstercat/api/Player.kt | 38 +++++++--- src/main/xerus/monstercat/api/Playlist.kt | 73 +++++++++++++++++++ src/main/xerus/monstercat/tabs/TabCatalog.kt | 67 +++++++++++++++-- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 67 +++++++++++++++++ 5 files changed, 230 insertions(+), 22 deletions(-) create mode 100644 src/main/xerus/monstercat/api/Playlist.kt create mode 100644 src/main/xerus/monstercat/tabs/TabPlaylist.kt diff --git a/src/main/xerus/monstercat/MonsterUtilities.kt b/src/main/xerus/monstercat/MonsterUtilities.kt index be87bf1..0951155 100644 --- a/src/main/xerus/monstercat/MonsterUtilities.kt +++ b/src/main/xerus/monstercat/MonsterUtilities.kt @@ -35,11 +35,7 @@ import xerus.monstercat.api.Cache import xerus.monstercat.api.DiscordRPC import xerus.monstercat.api.Player import xerus.monstercat.downloader.TabDownloader -import xerus.monstercat.tabs.BaseTab -import xerus.monstercat.tabs.TabCatalog -import xerus.monstercat.tabs.TabGenres -import xerus.monstercat.tabs.TabSettings -import xerus.monstercat.tabs.TabSound +import xerus.monstercat.tabs.* import java.io.File import java.net.URL import java.net.UnknownHostException @@ -78,6 +74,7 @@ class MonsterUtilities(checkForUpdate: Boolean) : VBox(), JFXMessageDisplay { } } addTab(TabCatalog::class) + addTab(TabPlaylist::class) addTab(TabGenres::class) addTab(TabDownloader::class) addTab(TabSound::class) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 4cdc2ef..00da094 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -97,6 +97,7 @@ object Player: FadingHBox(true, targetHeight = 25) { /** hides the Player and appears again displaying the latest Release */ fun reset() { fadeOut() + Playlist.clear() GlobalScope.launch { val latest = Cache.getReleases().firstOrNull() ?: return@launch while(fading) delay(50) @@ -174,6 +175,8 @@ object Player: FadingHBox(true, targetHeight = 25) { } add(pauseButton.apply { isSelected = false }) add(stopButton) + add(buttonWithId("skipback") { playPrev() }) + add(buttonWithId("skip") { playNext() }) add(volumeSlider) fill(pos = 0) fill() @@ -197,8 +200,14 @@ object Player: FadingHBox(true, targetHeight = 25) { onFx { showBack("Track not found") } return@launch } + playTracks(listOf(track)) + } + } + + fun playFromPlaylist(index: Int){ + val track = Playlist[index] + if (track != null) { playTrack(track) - player?.setOnEndOfMedia { reset() } } } @@ -210,15 +219,26 @@ object Player: FadingHBox(true, targetHeight = 25) { } /** Set the [tracks] as the internal playlist and start playing from the specified [index] */ - fun playTracks(tracks: List, index: Int) { - playTrack(tracks[index]) - onFx { - if(index > 0) - children.add(children.size - 3, buttonWithId("skipback") { playTracks(tracks, index - 1) }) - if(index < tracks.lastIndex) - children.add(children.size - 3, buttonWithId("skip") { playTracks(tracks, index + 1) }) + fun playTracks(tracks: List, index: Int = 0) { + Playlist.setTracks(tracks) + playTrack(Playlist.tracks[index]) + player?.setOnEndOfMedia { playNext() } + } + + fun playNext(){ + val next = Playlist.getNext() + if (next == null){ + reset() + }else{ + playTrack(next) + } + } + + fun playPrev(){ + val prev = Playlist.getPrev() + if (prev != null){ + playTrack(prev) } - player?.setOnEndOfMedia { if(tracks.lastIndex > index) playTracks(tracks, index + 1) else reset() } } } diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt new file mode 100644 index 0000000..6718054 --- /dev/null +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -0,0 +1,73 @@ +package xerus.monstercat.api + +import javafx.collections.FXCollections +import javafx.collections.ObservableList +import mu.KotlinLogging +import xerus.ktutil.javafx.properties.SimpleObservable +import xerus.ktutil.javafx.properties.bindSoft +import xerus.monstercat.api.response.Track +import java.util.* + +object Playlist { + val logger = KotlinLogging.logger { } + + val tracks: ObservableList = FXCollections.observableArrayList() + val history = ArrayDeque() + val currentIndex = SimpleObservable(null).apply { + bindSoft({ + tracks.indexOf(Player.activeTrack.value).takeUnless { i -> i == -1 } + }, Player.activeTrack, tracks) + } + + var repeat = false + var random = false + + operator fun get(index: Int): Track? = tracks.getOrNull(index) + + /** This property prevents songs that come from the [history] from being added to it again, + * which would effectively create an infinite loop */ + var lastPolled: Track? = null + + fun getPrev(): Track? = history.pollLast()?.also { + lastPolled = it + logger.debug("Polled $it from History") + } ?: currentIndex.value?.let { get(it - 1) } + + fun getNext() : Track?{ + return when { + random -> nextSongRandom() + repeat && (nextSong() == null) -> tracks[0] + else -> nextSong() + } + } + + fun addNext(track: Track) = tracks.add(currentIndex.value?.let { it + 1 } ?: 0, track) + + fun add(track: Track) = tracks.add(track) + + fun removeAt(index: Int?) { + if (index != null) tracks.removeAt(index) + else tracks.removeAt(tracks.size - 1) + } + + fun clear(){ + history.clear() + tracks.clear() + } + + fun setTracks(playlist: Collection) { + history.clear() + tracks.setAll(playlist) + } + + fun nextSongRandom(): Track = tracks[(Math.random() * tracks.size).toInt()] + fun nextSong(): Track? { + val cur = currentIndex.value + return when { + cur == null -> tracks.firstOrNull() + cur + 1 < tracks.size -> tracks[cur + 1] + repeat -> tracks.firstOrNull() + else -> return null + } + } +} \ No newline at end of file diff --git a/src/main/xerus/monstercat/tabs/TabCatalog.kt b/src/main/xerus/monstercat/tabs/TabCatalog.kt index 05b9f21..9c2dc6b 100644 --- a/src/main/xerus/monstercat/tabs/TabCatalog.kt +++ b/src/main/xerus/monstercat/tabs/TabCatalog.kt @@ -1,17 +1,15 @@ package xerus.monstercat.tabs import javafx.collections.ListChangeListener -import javafx.scene.control.Label -import javafx.scene.control.TableColumn -import javafx.scene.control.TableRow +import javafx.scene.control.* +import javafx.scene.input.MouseButton import javafx.scene.text.Font +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import xerus.ktutil.collections.ArraySet import xerus.ktutil.containsAny -import xerus.ktutil.javafx.TableColumn -import xerus.ktutil.javafx.fill -import xerus.ktutil.javafx.onFx +import xerus.ktutil.javafx.* import xerus.ktutil.javafx.properties.listen -import xerus.ktutil.javafx.textWidth import xerus.ktutil.javafx.ui.controls.MultiSearchable import xerus.ktutil.javafx.ui.controls.SearchView import xerus.ktutil.javafx.ui.controls.SearchableColumn @@ -19,9 +17,14 @@ import xerus.ktutil.javafx.ui.controls.Type import xerus.ktutil.preferences.multiSeparator import xerus.ktutil.toLocalDate import xerus.monstercat.Settings +import xerus.monstercat.api.APIUtils import xerus.monstercat.api.Player +import xerus.monstercat.api.Playlist +import xerus.monstercat.monsterUtilities import java.time.LocalTime import kotlin.math.absoluteValue +import xerus.ktutil.javafx.MenuItem +import xerus.monstercat.api.response.Track val defaultColumns = arrayOf("Genres", "Artists", "Track", "Length").joinToString(multiSeparator) val availableColumns = arrayOf("ID", "Date", "B", "Genres", "Artists", "Track", "Comp", "Length", "BPM", "Key").joinToString(multiSeparator) @@ -56,11 +59,59 @@ class TabCatalog : TableTab() { it.next(); Settings.VISIBLECATALOGCOLUMNS.putMulti(*it.addedSubList.map { it.text }.toTypedArray()) }) table.setOnMouseClicked { me -> - if(me.clickCount == 2) { + if(me.clickCount == 2 && me.button == MouseButton.PRIMARY) { val selected = table.selectionModel.selectedItem ?: return@setOnMouseClicked Player.play(selected[cols.findUnsafe("Track")].trim(), selected[cols.findUnsafe("Artist")]) + }else if(me.clickCount == 1 && me.button == MouseButton.MIDDLE){ + val selected = table.selectionModel.selectedItem ?: return@setOnMouseClicked + GlobalScope.launch { + val track = APIUtils.find(selected[cols.findUnsafe("Track")].trim(), selected[cols.findUnsafe("Artist")]) + if (track != null) Playlist.add(track) + else monsterUtilities.showMessage("The requested song could not be found.", "Cannot add to playlist", Alert.AlertType.WARNING) + } } } + + table.selectionModel.selectionMode = SelectionMode.MULTIPLE + val rightClickMenu = ContextMenu() + val item1 = MenuItem("Play") { + val selected = table.selectionModel.selectedItems + GlobalScope.launch { + Playlist.clear() + val tracklist = arrayListOf() + for (item in selected){ + val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) + if (track != null) tracklist.add(track) + else logger.error("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") + } + Player.playTracks(tracklist) + } + } + val item2 = MenuItem("Add to playlist") { + val selected = table.selectionModel.selectedItems + GlobalScope.launch { + for (item in selected){ + val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) + if (track != null) Playlist.add(track) + else logger.error("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") + } + } + } + val item3 = MenuItem("Play next") { + val selected = table.selectionModel.selectedItems + GlobalScope.launch { + for (item in selected.asReversed()){ + val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) + if (track != null) Playlist.addNext(track) + else logger.error("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") + } + } + } + val item4 = MenuItem("Select All") { + table.selectionModel.selectAll() + } + rightClickMenu.items.addAll(item1, item2, item3, item4) + table.contextMenu = rightClickMenu } private fun setColumns(columns: List) { diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt new file mode 100644 index 0000000..135f06b --- /dev/null +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -0,0 +1,67 @@ +package xerus.monstercat.tabs + +import javafx.beans.value.ObservableValue +import javafx.scene.control.* +import javafx.scene.input.MouseButton +import javafx.util.Callback +import xerus.ktutil.javafx.MenuItem +import xerus.ktutil.javafx.fill +import xerus.ktutil.javafx.properties.ImmutableObservable +import xerus.monstercat.api.* +import xerus.monstercat.api.response.Track + +class TabPlaylist : VTab() { + var table = TableView().apply { + columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY + } + + init { + table.items = Playlist.tracks + + table.columns.addAll(TableColumn("Artists").apply { + cellValueFactory = Callback, ObservableValue> { p -> + ImmutableObservable(p.value.artistsTitle) + } + }, TableColumn("Title").apply { + cellValueFactory = Callback, ObservableValue> { p -> + ImmutableObservable(p.value.title) + } + }) + + table.selectionModel.selectionMode = SelectionMode.SINGLE + + table.setOnMouseClicked { me -> + if (me.button == MouseButton.PRIMARY && me.clickCount == 2) { + Player.playFromPlaylist(table.selectionModel.selectedIndex) + } + if (me.button == MouseButton.MIDDLE && me.clickCount == 1) { + Playlist.removeAt(table.selectionModel.selectedIndex) + } + } + + table.placeholder = Label("Your playlist is empty.") + + val rightClickMenu = ContextMenu() + val item1 = MenuItem("Play") { + Player.playFromPlaylist(table.selectionModel.selectedIndex) + } + val item2 = MenuItem("Play Next") { + useSelectedTrack { Playlist.addNext(it) } + } + val item3 = MenuItem("Remove") { + Playlist.removeAt(table.selectionModel.selectedIndex) + } + val item4 = MenuItem("Clear playlist") { + Playlist.clear() + } + rightClickMenu.items.addAll(item1, item2, item3, item4) + table.contextMenu = rightClickMenu + + fill(table) + } + + inline fun useSelectedTrack(action: (Track) -> Unit) { + action(table.selectionModel.selectedItem) + } + +} \ No newline at end of file From 3495225118d9d97c1bf693fa8e81cd1b47f82de6 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 21 Jun 2019 19:49:16 +0200 Subject: [PATCH 02/99] Added Shuffle and Repeat buttons --- src/main/xerus/monstercat/api/Player.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 00da094..665fab0 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -164,6 +164,12 @@ object Player: FadingHBox(true, targetHeight = 25) { prefWidth = 100.0 valueProperty().listen { updateVolume() } } + private val shuffleButton = ToggleButton().id("shuffle").onClick { + Playlist.random = isSelected + } + private val repeatButton = ToggleButton().id("repeat").onClick { + Playlist.repeat = isSelected + } private var coverUrl: String? = null private fun playing(text: String) { @@ -177,6 +183,8 @@ object Player: FadingHBox(true, targetHeight = 25) { add(stopButton) add(buttonWithId("skipback") { playPrev() }) add(buttonWithId("skip") { playNext() }) + add(shuffleButton.apply { isSelected = false }) + add(repeatButton.apply { isSelected = false }) add(volumeSlider) fill(pos = 0) fill() From b62cdb7414d924cc55978c137de0557b334f5a09 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 21 Jun 2019 19:58:10 +0200 Subject: [PATCH 03/99] Skip song as a failsafe if it isn't found --- src/main/xerus/monstercat/api/Player.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 665fab0..aa21d9d 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -29,6 +29,9 @@ import xerus.ktutil.square import xerus.monstercat.Settings import xerus.monstercat.api.response.Release import xerus.monstercat.api.response.Track +import java.util.* +import java.util.concurrent.TimeUnit +import kotlin.concurrent.schedule import kotlin.math.pow object Player: FadingHBox(true, targetHeight = 25) { @@ -90,7 +93,14 @@ object Player: FadingHBox(true, targetHeight = 25) { addButton { reset() }.id("back") fill(pos = 0) fill() - add(closeButton) + if (Playlist.tracks.size < 2){ + add(closeButton) + }else{ + add(buttonWithId("skip") { playNext() }) + Timer("SkipErroredSong", false).schedule(5000) { // fixme : hardcoded delay in ms + playNext() + } + } } } From 83ec9efcc89686058a358e6cb89cea917e02f25d Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Sat, 22 Jun 2019 00:15:11 +0200 Subject: [PATCH 04/99] Added fork as library for shuffle and repeat icons --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 32dce8a..ca3d098 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,7 +51,7 @@ repositories { dependencies { implementation(kotlin("reflect")) - implementation("com.github.Xerus2000.util", "javafx", "6bc6adce50de31606732f9a14342690a96308901") + implementation("com.github.defvs.util", "javafx", "7fe4d7dbb28ee286640f980a9b8f621b77d7c9f9") implementation("org.controlsfx", "controlsfx", "8.40.+") implementation("ch.qos.logback", "logback-classic", "1.2.+") From 9a24a887daa422cca9fff40b9348a4454e6f0563 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Sat, 22 Jun 2019 00:15:50 +0200 Subject: [PATCH 05/99] Fix shuffle and repeat not keeping their values after Track changed --- src/main/xerus/monstercat/api/Player.kt | 16 +++++++++------- src/main/xerus/monstercat/api/Playlist.kt | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index aa21d9d..f4c8df0 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -30,7 +30,6 @@ import xerus.monstercat.Settings import xerus.monstercat.api.response.Release import xerus.monstercat.api.response.Track import java.util.* -import java.util.concurrent.TimeUnit import kotlin.concurrent.schedule import kotlin.math.pow @@ -107,7 +106,6 @@ object Player: FadingHBox(true, targetHeight = 25) { /** hides the Player and appears again displaying the latest Release */ fun reset() { fadeOut() - Playlist.clear() GlobalScope.launch { val latest = Cache.getReleases().firstOrNull() ?: return@launch while(fading) delay(50) @@ -142,6 +140,7 @@ object Player: FadingHBox(true, targetHeight = 25) { playing("Loading $track") player?.run { play() + setOnEndOfMedia { playNext() } setOnReady { label.text = "Now Playing: $track" val total = totalDuration.toMillis() @@ -169,13 +168,16 @@ object Player: FadingHBox(true, targetHeight = 25) { } private val pauseButton = ToggleButton().id("play-pause").onClick { if(isSelected) player?.pause() else player?.play() } - private val stopButton = buttonWithId("stop") { reset() } + private val stopButton = buttonWithId("stop") { + reset() + Playlist.clear() + } private val volumeSlider = Slider(0.0, 1.0, Settings.PLAYERVOLUME()).scrollable(0.05).apply { prefWidth = 100.0 valueProperty().listen { updateVolume() } } private val shuffleButton = ToggleButton().id("shuffle").onClick { - Playlist.random = isSelected + Playlist.shuffle = isSelected } private val repeatButton = ToggleButton().id("repeat").onClick { Playlist.repeat = isSelected @@ -193,8 +195,8 @@ object Player: FadingHBox(true, targetHeight = 25) { add(stopButton) add(buttonWithId("skipback") { playPrev() }) add(buttonWithId("skip") { playNext() }) - add(shuffleButton.apply { isSelected = false }) - add(repeatButton.apply { isSelected = false }) + add(shuffleButton.apply { isSelected = Playlist.shuffle }) + add(repeatButton.apply { isSelected = Playlist.repeat }) add(volumeSlider) fill(pos = 0) fill() @@ -240,7 +242,6 @@ object Player: FadingHBox(true, targetHeight = 25) { fun playTracks(tracks: List, index: Int = 0) { Playlist.setTracks(tracks) playTrack(Playlist.tracks[index]) - player?.setOnEndOfMedia { playNext() } } fun playNext(){ @@ -249,6 +250,7 @@ object Player: FadingHBox(true, targetHeight = 25) { reset() }else{ playTrack(next) + } } diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 6718054..7c31074 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -20,7 +20,7 @@ object Playlist { } var repeat = false - var random = false + var shuffle = false operator fun get(index: Int): Track? = tracks.getOrNull(index) @@ -35,7 +35,7 @@ object Playlist { fun getNext() : Track?{ return when { - random -> nextSongRandom() + shuffle -> nextSongRandom() repeat && (nextSong() == null) -> tracks[0] else -> nextSong() } From 25c7351c6dccf5eaf33e01cb0c83de719b167c63 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Sat, 22 Jun 2019 00:25:44 +0200 Subject: [PATCH 06/99] Fix pressing next while track is loading returns to first track --- src/main/xerus/monstercat/api/Player.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index f4c8df0..49de0ac 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -137,6 +137,9 @@ object Player: FadingHBox(true, targetHeight = 25) { logger.debug("Loading $track from $hash") activePlayer.value = MediaPlayer(Media("https://s3.amazonaws.com/data.monstercat.com/blobs/$hash")) updateVolume() + onFx { + activeTrack.value = track + } playing("Loading $track") player?.run { play() @@ -146,9 +149,6 @@ object Player: FadingHBox(true, targetHeight = 25) { val total = totalDuration.toMillis() seekBar.progressProperty().dependOn(currentTimeProperty()) { it.toMillis() / total } seekBar.transitionToHeight(Settings.PLAYERSEEKBARHEIGHT(), 1.0) - onFx { - activeTrack.value = track - } } setOnError { logger.warn("Error loading $track: $error", error) From 2a4b91fcb534b98c9e8e7ac3104e5881a040e6e8 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Sat, 22 Jun 2019 00:26:06 +0200 Subject: [PATCH 07/99] Fix player not resetting when playlist is cleared --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 135f06b..f30e272 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -53,6 +53,7 @@ class TabPlaylist : VTab() { } val item4 = MenuItem("Clear playlist") { Playlist.clear() + Player.reset() } rightClickMenu.items.addAll(item1, item2, item3, item4) table.contextMenu = rightClickMenu From be22b8016fecc9cb023cb050735432a49824265d Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Sat, 22 Jun 2019 00:29:06 +0200 Subject: [PATCH 08/99] Added keyboard actions to catalog tab. Enter plays the track(s), + adds to the playlist --- src/main/xerus/monstercat/tabs/TabCatalog.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/xerus/monstercat/tabs/TabCatalog.kt b/src/main/xerus/monstercat/tabs/TabCatalog.kt index 9c2dc6b..7b2a7c8 100644 --- a/src/main/xerus/monstercat/tabs/TabCatalog.kt +++ b/src/main/xerus/monstercat/tabs/TabCatalog.kt @@ -2,6 +2,7 @@ package xerus.monstercat.tabs import javafx.collections.ListChangeListener import javafx.scene.control.* +import javafx.scene.input.KeyCode import javafx.scene.input.MouseButton import javafx.scene.text.Font import kotlinx.coroutines.GlobalScope @@ -71,6 +72,19 @@ class TabCatalog : TableTab() { } } } + table.setOnKeyPressed { + if (it.code == KeyCode.ENTER){ + val selected = table.selectionModel.selectedItem ?: return@setOnKeyPressed + Player.play(selected[cols.findUnsafe("Track")].trim(), selected[cols.findUnsafe("Artist")]) + }else if(it.code == KeyCode.PLUS || it.code == KeyCode.ADD){ + val selected = table.selectionModel.selectedItem ?: return@setOnKeyPressed + GlobalScope.launch { + val track = APIUtils.find(selected[cols.findUnsafe("Track")].trim(), selected[cols.findUnsafe("Artist")]) + if (track != null) Playlist.add(track) + else monsterUtilities.showMessage("The requested song could not be found.", "Cannot add to playlist", Alert.AlertType.WARNING) + } + } + } table.selectionModel.selectionMode = SelectionMode.MULTIPLE val rightClickMenu = ContextMenu() From e7fc26e47be383760e133af00a432b5511c37124 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Sat, 22 Jun 2019 00:34:13 +0200 Subject: [PATCH 09/99] Added keyboard actions to playlist tab --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index f30e272..841bf09 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -2,6 +2,7 @@ package xerus.monstercat.tabs import javafx.beans.value.ObservableValue import javafx.scene.control.* +import javafx.scene.input.KeyCode import javafx.scene.input.MouseButton import javafx.util.Callback import xerus.ktutil.javafx.MenuItem @@ -39,6 +40,16 @@ class TabPlaylist : VTab() { } } + table.setOnKeyPressed { ke -> + if (ke.code == KeyCode.DELETE){ + Playlist.removeAt(table.selectionModel.selectedIndex) + }else if (ke.code == KeyCode.ENTER){ + Player.playFromPlaylist(table.selectionModel.selectedIndex) + }else if (ke.code == KeyCode.ADD || ke.code == KeyCode.PLUS){ + useSelectedTrack { Playlist.addNext(it) } + } + } + table.placeholder = Label("Your playlist is empty.") val rightClickMenu = ContextMenu() From f76deb53f7a2cc1e894a62cf4ba8d015f5b39585 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Sat, 22 Jun 2019 16:24:57 +0200 Subject: [PATCH 10/99] Added tooltips to player buttons --- src/main/xerus/monstercat/api/Player.kt | 28 ++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 49de0ac..6acda48 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -2,10 +2,7 @@ package xerus.monstercat.api import javafx.event.EventHandler import javafx.geometry.Pos -import javafx.scene.control.Label -import javafx.scene.control.ProgressBar -import javafx.scene.control.Slider -import javafx.scene.control.ToggleButton +import javafx.scene.control.* import javafx.scene.image.ImageView import javafx.scene.input.MouseButton import javafx.scene.input.MouseEvent @@ -95,7 +92,9 @@ object Player: FadingHBox(true, targetHeight = 25) { if (Playlist.tracks.size < 2){ add(closeButton) }else{ - add(buttonWithId("skip") { playNext() }) + add(buttonWithId("skip") { playNext() }).apply { + tooltip = Tooltip("Skip") + } Timer("SkipErroredSong", false).schedule(5000) { // fixme : hardcoded delay in ms playNext() } @@ -168,19 +167,30 @@ object Player: FadingHBox(true, targetHeight = 25) { } private val pauseButton = ToggleButton().id("play-pause").onClick { if(isSelected) player?.pause() else player?.play() } + .apply { + tooltip = Tooltip("Pause / Play") + } private val stopButton = buttonWithId("stop") { reset() Playlist.clear() + }.apply { + tooltip = Tooltip("Stop playing") } private val volumeSlider = Slider(0.0, 1.0, Settings.PLAYERVOLUME()).scrollable(0.05).apply { prefWidth = 100.0 valueProperty().listen { updateVolume() } + }.apply { + tooltip = Tooltip("Volume") } private val shuffleButton = ToggleButton().id("shuffle").onClick { Playlist.shuffle = isSelected + }.apply { + tooltip = Tooltip("Shuffle") } private val repeatButton = ToggleButton().id("repeat").onClick { Playlist.repeat = isSelected + }.apply { + tooltip = Tooltip("Repeat all") } private var coverUrl: String? = null @@ -193,8 +203,12 @@ object Player: FadingHBox(true, targetHeight = 25) { } add(pauseButton.apply { isSelected = false }) add(stopButton) - add(buttonWithId("skipback") { playPrev() }) - add(buttonWithId("skip") { playNext() }) + add(buttonWithId("skipback") { playPrev() }).apply { + tooltip = Tooltip("Previous") + } + add(buttonWithId("skip") { playNext() }).apply { + tooltip = Tooltip("Next") + } add(shuffleButton.apply { isSelected = Playlist.shuffle }) add(repeatButton.apply { isSelected = Playlist.repeat }) add(volumeSlider) From 3b966ff87878c74fe7458e8259fa249c051ffb93 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Wed, 26 Jun 2019 21:22:02 +0200 Subject: [PATCH 11/99] Show currently played song in the playlist Currently played song is highlighted in green. Should be themeable and not hardcoded. --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 841bf09..f6a4d3f 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -1,5 +1,6 @@ package xerus.monstercat.tabs +import javafx.beans.value.ChangeListener import javafx.beans.value.ObservableValue import javafx.scene.control.* import javafx.scene.input.KeyCode @@ -10,6 +11,9 @@ import xerus.ktutil.javafx.fill import xerus.ktutil.javafx.properties.ImmutableObservable import xerus.monstercat.api.* import xerus.monstercat.api.response.Track +import com.sun.xml.internal.fastinfoset.alphabet.BuiltInRestrictedAlphabets.table + + class TabPlaylist : VTab() { var table = TableView().apply { @@ -29,6 +33,18 @@ class TabPlaylist : VTab() { } }) + table.setRowFactory { + TableRow().apply { + Playlist.currentIndex.addListener { _, _, newValue -> + style = if (index == newValue) { + "-fx-background-color: #1f6601" + } else { + "-fx-background-color: transparent" + } + } + } + } + table.selectionModel.selectionMode = SelectionMode.SINGLE table.setOnMouseClicked { me -> From 89f294ddff7ea9951210a601ff24b8d03f186cae Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Wed, 26 Jun 2019 21:22:36 +0200 Subject: [PATCH 12/99] Disallow duplicates in playlist to fix currentIndex --- src/main/xerus/monstercat/api/Playlist.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 7c31074..1376670 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -41,9 +41,15 @@ object Playlist { } } - fun addNext(track: Track) = tracks.add(currentIndex.value?.let { it + 1 } ?: 0, track) + fun addNext(track: Track) = tracks.run { + remove(track) + add(currentIndex.value?.let { it + 1 } ?: 0, track) + } - fun add(track: Track) = tracks.add(track) + fun add(track: Track): Boolean = tracks.run { + remove(track) + return add(track) + } fun removeAt(index: Int?) { if (index != null) tracks.removeAt(index) From 0962c60c08e8a740584d5fce9919d49b698bc608 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Wed, 26 Jun 2019 21:48:57 +0200 Subject: [PATCH 13/99] Optimize imports for Playlist Tab --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index f6a4d3f..10ad48d 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -1,6 +1,6 @@ package xerus.monstercat.tabs -import javafx.beans.value.ChangeListener +import javafx.beans.Observable import javafx.beans.value.ObservableValue import javafx.scene.control.* import javafx.scene.input.KeyCode @@ -9,10 +9,9 @@ import javafx.util.Callback import xerus.ktutil.javafx.MenuItem import xerus.ktutil.javafx.fill import xerus.ktutil.javafx.properties.ImmutableObservable -import xerus.monstercat.api.* +import xerus.monstercat.api.Player +import xerus.monstercat.api.Playlist import xerus.monstercat.api.response.Track -import com.sun.xml.internal.fastinfoset.alphabet.BuiltInRestrictedAlphabets.table - class TabPlaylist : VTab() { From 82a9c915ceab2f9f22557ed7c61a44f09a6f6559 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 28 Jun 2019 18:50:57 +0200 Subject: [PATCH 14/99] Swapped for-loop for a forEach --- src/main/xerus/monstercat/tabs/TabCatalog.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabCatalog.kt b/src/main/xerus/monstercat/tabs/TabCatalog.kt index 7b2a7c8..529cab0 100644 --- a/src/main/xerus/monstercat/tabs/TabCatalog.kt +++ b/src/main/xerus/monstercat/tabs/TabCatalog.kt @@ -93,7 +93,7 @@ class TabCatalog : TableTab() { GlobalScope.launch { Playlist.clear() val tracklist = arrayListOf() - for (item in selected){ + selected.forEach { item -> val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) if (track != null) tracklist.add(track) else logger.error("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") @@ -104,7 +104,7 @@ class TabCatalog : TableTab() { val item2 = MenuItem("Add to playlist") { val selected = table.selectionModel.selectedItems GlobalScope.launch { - for (item in selected){ + selected.forEach { item -> val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) if (track != null) Playlist.add(track) else logger.error("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") @@ -114,7 +114,7 @@ class TabCatalog : TableTab() { val item3 = MenuItem("Play next") { val selected = table.selectionModel.selectedItems GlobalScope.launch { - for (item in selected.asReversed()){ + selected.asReversed().forEach { item -> val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) if (track != null) Playlist.addNext(track) else logger.error("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") From 65bf11ff8a401830c7f11cdbb11f147258f60fff Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 28 Jun 2019 18:51:14 +0200 Subject: [PATCH 15/99] Added a separator and grouped menuItems for TabCatalog --- src/main/xerus/monstercat/tabs/TabCatalog.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabCatalog.kt b/src/main/xerus/monstercat/tabs/TabCatalog.kt index 529cab0..262de88 100644 --- a/src/main/xerus/monstercat/tabs/TabCatalog.kt +++ b/src/main/xerus/monstercat/tabs/TabCatalog.kt @@ -121,10 +121,17 @@ class TabCatalog : TableTab() { } } } - val item4 = MenuItem("Select All") { + val playlistItems = { + arrayOf(item1, item2, item3) + } + val selectAllItem = MenuItem("Select All") { table.selectionModel.selectAll() } - rightClickMenu.items.addAll(item1, item2, item3, item4) + rightClickMenu.items.addAll( + *playlistItems(), + SeparatorMenuItem(), + selectAllItem + ) table.contextMenu = rightClickMenu } From 4a436ccdb1f8121b21e6d64e63b6d7b90d7b94db Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 28 Jun 2019 18:51:42 +0200 Subject: [PATCH 16/99] Added Context Menu for TabDownloader (SongView) --- .../xerus/monstercat/downloader/SongView.kt | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/src/main/xerus/monstercat/downloader/SongView.kt b/src/main/xerus/monstercat/downloader/SongView.kt index fbdc69e..96f086f 100644 --- a/src/main/xerus/monstercat/downloader/SongView.kt +++ b/src/main/xerus/monstercat/downloader/SongView.kt @@ -3,12 +3,7 @@ package xerus.monstercat.downloader import javafx.beans.value.ObservableValue import javafx.collections.ListChangeListener import javafx.scene.Node -import javafx.scene.control.CheckBox -import javafx.scene.control.CheckBoxTreeItem -import javafx.scene.control.ContextMenu -import javafx.scene.control.Tooltip -import javafx.scene.control.TreeCell -import javafx.scene.control.TreeView +import javafx.scene.control.* import javafx.scene.control.cell.CheckBoxTreeCell import javafx.scene.image.Image import javafx.scene.image.ImageView @@ -25,11 +20,7 @@ import xerus.ktutil.javafx.properties.addOneTimeListener import xerus.ktutil.javafx.properties.dependOn import xerus.ktutil.javafx.properties.listen import xerus.ktutil.javafx.ui.FilterableTreeItem -import xerus.monstercat.api.APIConnection -import xerus.monstercat.api.Cache -import xerus.monstercat.api.ConnectValidity -import xerus.monstercat.api.Covers -import xerus.monstercat.api.Player +import xerus.monstercat.api.* import xerus.monstercat.api.response.MusicItem import xerus.monstercat.api.response.Release import xerus.monstercat.api.response.Track @@ -101,7 +92,60 @@ class SongView(private val sorter: ObservableValue) : arrayOf(MenuItem("Expand all") { expandAll() }, MenuItem("Collapse all") { expandAll(false) }) } - contextMenu = ContextMenu(*defaultItems()) + val item1 = MenuItem("Play") { + val selected = selectionModel.selectedItem ?: return@MenuItem + GlobalScope.launch { + Playlist.clear() + val value = selected.value + when(value) { + is Release -> Player.play(value) + is Track -> Player.playTrack(value) + } + } + } + val item2 = MenuItem("Add to playlist") { + val selected = selectionModel.selectedItem ?: return@MenuItem + GlobalScope.launch { + val value = selected.value + when(value) { + is Release -> { + value.tracks.forEach { track -> + Playlist.add(track) + } + } + is Track -> Playlist.add(value) + } + } + } + val item3 = MenuItem("Play next") { + val selected = selectionModel.selectedItem ?: return@MenuItem + GlobalScope.launch { + val value = selected.value + when(value) { + is Release -> { + value.tracks.asReversed().forEach { track -> + Playlist.addNext(track) + } + } + is Track -> Playlist.addNext(value) + } + } + } + val playlistItems = { + arrayOf(item1, item2, item3) + } + contextMenu = ContextMenu( + *playlistItems(), + SeparatorMenuItem(), + *defaultItems() + ) + setOnContextMenuRequested { + val value = selectionModel.selectedItem.value + val enable = (value is Track || value is Release) + playlistItems().forEach { item -> + item.isDisable = !enable + } + } onReady { APIConnection.connectValidity.addListener { _, old, new -> if(old != new && new == ConnectValidity.GOLD) From 35b7292e54953cb9bdb6230151c0abbafdcdb335 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 28 Jun 2019 22:13:26 +0200 Subject: [PATCH 17/99] Fix issues with TabPlaylist table, showing weird colors after a scroll --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 10ad48d..2ac6145 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -9,6 +9,7 @@ import javafx.util.Callback import xerus.ktutil.javafx.MenuItem import xerus.ktutil.javafx.fill import xerus.ktutil.javafx.properties.ImmutableObservable +import xerus.ktutil.javafx.properties.listen import xerus.monstercat.api.Player import xerus.monstercat.api.Playlist import xerus.monstercat.api.response.Track @@ -41,6 +42,13 @@ class TabPlaylist : VTab() { "-fx-background-color: transparent" } } + itemProperty().listen { + style = if (index == Playlist.currentIndex.value) { + "-fx-background-color: #1f6601" + } else { + "-fx-background-color: transparent" + } + } } } From 831d4aa60aa76cff426ff72f8770234ae1b47816 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 28 Jun 2019 22:25:27 +0200 Subject: [PATCH 18/99] Change loglevel for context menu in TabCatalog --- src/main/xerus/monstercat/tabs/TabCatalog.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabCatalog.kt b/src/main/xerus/monstercat/tabs/TabCatalog.kt index 262de88..1395256 100644 --- a/src/main/xerus/monstercat/tabs/TabCatalog.kt +++ b/src/main/xerus/monstercat/tabs/TabCatalog.kt @@ -96,7 +96,7 @@ class TabCatalog : TableTab() { selected.forEach { item -> val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) if (track != null) tracklist.add(track) - else logger.error("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") + else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") } Player.playTracks(tracklist) } @@ -107,7 +107,7 @@ class TabCatalog : TableTab() { selected.forEach { item -> val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) if (track != null) Playlist.add(track) - else logger.error("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") + else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") } } } @@ -117,7 +117,7 @@ class TabCatalog : TableTab() { selected.asReversed().forEach { item -> val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) if (track != null) Playlist.addNext(track) - else logger.error("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") + else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") } } } From 4a9aa6801fdc58a7a34a1a6ab4b166b20ce0b63f Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 28 Jun 2019 22:25:51 +0200 Subject: [PATCH 19/99] Fix keyboard controls only working for 1 song in TabCatalog --- src/main/xerus/monstercat/tabs/TabCatalog.kt | 23 +++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabCatalog.kt b/src/main/xerus/monstercat/tabs/TabCatalog.kt index 1395256..5713854 100644 --- a/src/main/xerus/monstercat/tabs/TabCatalog.kt +++ b/src/main/xerus/monstercat/tabs/TabCatalog.kt @@ -74,14 +74,25 @@ class TabCatalog : TableTab() { } table.setOnKeyPressed { if (it.code == KeyCode.ENTER){ - val selected = table.selectionModel.selectedItem ?: return@setOnKeyPressed - Player.play(selected[cols.findUnsafe("Track")].trim(), selected[cols.findUnsafe("Artist")]) + val selected = table.selectionModel.selectedItems + GlobalScope.launch { + Playlist.clear() + val tracklist = arrayListOf() + selected.forEach { item -> + val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) + if (track != null) tracklist.add(track) + else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") + } + Player.playTracks(tracklist) + } }else if(it.code == KeyCode.PLUS || it.code == KeyCode.ADD){ - val selected = table.selectionModel.selectedItem ?: return@setOnKeyPressed + val selected = table.selectionModel.selectedItems GlobalScope.launch { - val track = APIUtils.find(selected[cols.findUnsafe("Track")].trim(), selected[cols.findUnsafe("Artist")]) - if (track != null) Playlist.add(track) - else monsterUtilities.showMessage("The requested song could not be found.", "Cannot add to playlist", Alert.AlertType.WARNING) + selected.forEach { item -> + val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) + if (track != null) Playlist.add(track) + else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") + } } } } From bb7908f8c315b71eeb10580f7b38e9f8c51f95d3 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Mon, 1 Jul 2019 17:56:27 +0200 Subject: [PATCH 20/99] fix(playlist): Next random Track should not be the same one. --- src/main/xerus/monstercat/api/Playlist.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 1376670..705f57f 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -66,7 +66,10 @@ object Playlist { tracks.setAll(playlist) } - fun nextSongRandom(): Track = tracks[(Math.random() * tracks.size).toInt()] + fun nextSongRandom(): Track { + val index = (Math.random() * tracks.size).toInt() + return if(index == currentIndex.value && tracks.size > 1) nextSongRandom() else tracks[index] + } fun nextSong(): Track? { val cur = currentIndex.value return when { From f3d4c7d1d5dd6b0c7e0678b9239895150be03e26 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Mon, 1 Jul 2019 18:50:41 +0200 Subject: [PATCH 21/99] feat(playlist): Added buttons to load/save playlist to Connect --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 2ac6145..318b5fc 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -5,8 +5,11 @@ import javafx.beans.value.ObservableValue import javafx.scene.control.* import javafx.scene.input.KeyCode import javafx.scene.input.MouseButton +import javafx.scene.layout.HBox import javafx.util.Callback import xerus.ktutil.javafx.MenuItem +import xerus.ktutil.javafx.add +import xerus.ktutil.javafx.addButton import xerus.ktutil.javafx.fill import xerus.ktutil.javafx.properties.ImmutableObservable import xerus.ktutil.javafx.properties.listen @@ -92,6 +95,16 @@ class TabPlaylist : VTab() { rightClickMenu.items.addAll(item1, item2, item3, item4) table.contextMenu = rightClickMenu + val buttons = HBox() + buttons.add(Label("From Monstercat.com :")) + buttons.addButton("Open..."){ + // TODO : Open dialog + } + buttons.addButton("Save..."){ + // TODO : Save dialog + } + + add(buttons) fill(table) } From d48ba9f8dab57095f295a71e4d805231dad67d70 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Mon, 1 Jul 2019 18:51:00 +0200 Subject: [PATCH 22/99] feat(playlist-api): Base of ConnectPlaylist --- .../monstercat/api/response/ConnectPlaylist.kt | 16 ++++++++++++++++ .../monstercat/api/response/ListResponse.kt | 1 + 2 files changed, 17 insertions(+) create mode 100644 src/main/xerus/monstercat/api/response/ConnectPlaylist.kt diff --git a/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt b/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt new file mode 100644 index 0000000..00a35cd --- /dev/null +++ b/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt @@ -0,0 +1,16 @@ +package xerus.monstercat.api.response + +import com.google.api.client.util.Key + +data class ConnectPlaylist( + @Key("_id") var id: String = "", + @Key var name: String = "", + @Key var public: Boolean = false, + + @Key var tracks: List = ArrayList() +) { + fun init(): ConnectPlaylist { + name = name.trim() + return this + } +} diff --git a/src/main/xerus/monstercat/api/response/ListResponse.kt b/src/main/xerus/monstercat/api/response/ListResponse.kt index 90f930b..6e75e9e 100644 --- a/src/main/xerus/monstercat/api/response/ListResponse.kt +++ b/src/main/xerus/monstercat/api/response/ListResponse.kt @@ -11,6 +11,7 @@ open class ListResponse { class ReleaseResponse : ListResponse() class TrackResponse : ListResponse() +class PlaylistResponse : ListResponse() class ReleaseList : ArrayList() class TrackList : ArrayList() \ No newline at end of file From f6f798a425e04b32ee5a8920115c4167fb7d5a1c Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Tue, 2 Jul 2019 17:38:04 +0200 Subject: [PATCH 23/99] feat(playlist-api): Playlist loading from account or URL --- .../xerus/monstercat/api/APIConnection.kt | 8 +- src/main/xerus/monstercat/api/Playlist.kt | 10 ++ .../api/response/ConnectPlaylist.kt | 3 +- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 132 +++++++++++++++++- 4 files changed, 142 insertions(+), 11 deletions(-) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index cc16690..ea427ba 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -21,10 +21,7 @@ import xerus.ktutil.helpers.HTTPQuery import xerus.ktutil.javafx.properties.SimpleObservable import xerus.ktutil.javafx.properties.listen import xerus.monstercat.Sheets -import xerus.monstercat.api.response.ReleaseResponse -import xerus.monstercat.api.response.Session -import xerus.monstercat.api.response.TrackResponse -import xerus.monstercat.api.response.declaredKeys +import xerus.monstercat.api.response.* import xerus.monstercat.downloader.CONNECTSID import xerus.monstercat.downloader.QUALITY import java.io.IOException @@ -74,6 +71,9 @@ class APIConnection(vararg path: String) : HTTPQuery() { fun getTracks() = parseJSON(TrackResponse::class.java)?.results + fun getPlaylists()= + parseJSON(PlaylistResponse::class.java)?.results?.map { it.init() } + /** Aborts this connection and thus terminates the InputStream if active */ fun abort() { httpGet?.abort() diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 705f57f..1004125 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -5,8 +5,10 @@ import javafx.collections.ObservableList import mu.KotlinLogging import xerus.ktutil.javafx.properties.SimpleObservable import xerus.ktutil.javafx.properties.bindSoft +import xerus.monstercat.api.response.ConnectPlaylist import xerus.monstercat.api.response.Track import java.util.* +import kotlin.collections.ArrayList object Playlist { val logger = KotlinLogging.logger { } @@ -51,6 +53,8 @@ object Playlist { return add(track) } + fun addAll(tracks: ArrayList) = this.tracks.addAll(tracks) + fun removeAt(index: Int?) { if (index != null) tracks.removeAt(index) else tracks.removeAt(tracks.size - 1) @@ -79,4 +83,10 @@ object Playlist { else -> return null } } + + fun loadPlaylist(playlist: ArrayList) { + clear() + Player.reset() + addAll(playlist) + } } \ No newline at end of file diff --git a/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt b/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt index 00a35cd..773b0d6 100644 --- a/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt +++ b/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt @@ -7,10 +7,11 @@ data class ConnectPlaylist( @Key var name: String = "", @Key var public: Boolean = false, - @Key var tracks: List = ArrayList() + @Key var tracks: ArrayList = arrayListOf() ) { fun init(): ConnectPlaylist { name = name.trim() return this } + } diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 318b5fc..452d0f9 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -2,20 +2,30 @@ package xerus.monstercat.tabs import javafx.beans.Observable import javafx.beans.value.ObservableValue +import javafx.collections.FXCollections import javafx.scene.control.* import javafx.scene.input.KeyCode import javafx.scene.input.MouseButton import javafx.scene.layout.HBox +import javafx.scene.layout.VBox import javafx.util.Callback -import xerus.ktutil.javafx.MenuItem -import xerus.ktutil.javafx.add -import xerus.ktutil.javafx.addButton -import xerus.ktutil.javafx.fill +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import xerus.ktutil.javafx.* import xerus.ktutil.javafx.properties.ImmutableObservable import xerus.ktutil.javafx.properties.listen +import xerus.ktutil.javafx.ui.App +import xerus.monstercat.api.APIConnection +import xerus.monstercat.api.ConnectValidity import xerus.monstercat.api.Player import xerus.monstercat.api.Playlist +import xerus.monstercat.api.response.ConnectPlaylist +import xerus.monstercat.api.response.Settings import xerus.monstercat.api.response.Track +import xerus.monstercat.downloader.CONNECTSID +import xerus.monstercat.downloader.DownloaderSettings +import xerus.monstercat.monsterUtilities +import java.lang.Exception class TabPlaylist : VTab() { @@ -98,16 +108,126 @@ class TabPlaylist : VTab() { val buttons = HBox() buttons.add(Label("From Monstercat.com :")) buttons.addButton("Open..."){ - // TODO : Open dialog + openPlaylistDialog() } buttons.addButton("Save..."){ - // TODO : Save dialog + savePlaylistDialog() } add(buttons) fill(table) } + private fun openPlaylistDialog(){ + val connection = APIConnection("playlist").fields(ConnectPlaylist::class) + + val playlists = FXCollections.observableArrayList() + + val parent = VBox() + val stage = App.stage.createStage("Open playlist from Monstercat.com", parent) + + val connectTable = TableView().apply { + columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY + columns.addAll(TableColumn("Name").apply { + cellValueFactory = Callback, ObservableValue> { p -> + ImmutableObservable(p.value.name) + } + }, TableColumn("Size").apply { + cellValueFactory = Callback, ObservableValue> { p -> + ImmutableObservable(p.value.tracks.size.toString()) + } + }) + + items = playlists + + selectionModel.selectionMode = SelectionMode.SINGLE + + if (APIConnection.connectValidity.value != ConnectValidity.NOGOLD && APIConnection.connectValidity.value != ConnectValidity.GOLD){ + placeholder = Label("Please connect using connect.sid in the downloader tab.") + }else{ + placeholder = Label("Loading...") + GlobalScope.async { + val results = connection.getPlaylists() + if (results != null && results.isNotEmpty()) + playlists.addAll(results) + else + onFx { + placeholder = Label("No playlists were found on your account.") + } + } + } + } + + val urlField = TextField("").apply { + promptText = "Enter the playlist's URL" + } + + var fromUrl = false + + val buttons = HBox().apply { + addButton("Load") { + if (fromUrl){ + val playlistId = urlField.text.substringAfterLast("/") + if (playlistId.length == 24){ + GlobalScope.async { + val apiConnection = APIConnection("playlist", playlistId, "tracks") + try { + val tracks = apiConnection.getTracks() + if (tracks != null && tracks.isNotEmpty()) { + Playlist.loadPlaylist(tracks) + onFx { stage.close() } + } + }catch (e: Exception){ + onFx { + monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlist were found at ${urlField.text}.") + } + } + } + }else{ + monsterUtilities.showAlert(Alert.AlertType.WARNING, "Playlist URL invalid", content = "${urlField.text} is not a valid URL.") + } + }else { + if (connectTable.selectionModel.selectedItem != null) { + GlobalScope.async { + val apiConnection = APIConnection("playlist", connectTable.selectionModel.selectedItem.id, "tracks") + val tracks = apiConnection.getTracks()!! + Playlist.loadPlaylist(tracks) + } + stage.close() + } + } + } + addButton("From URL..."){}.apply { + onClick { + fromUrl = !fromUrl + + parent.children.removeAt(0) + if (fromUrl) { + text = "From account..." + parent.children.add(0, urlField) + parent.fill(pos = 1) + } else { + text = "From URL..." + parent.children.removeAt(0) + parent.fill(connectTable, 0) + } + } + } + addButton("Cancel") { + stage.close() + } + } + + // parent.add(urlField) + parent.add(buttons) + parent.fill(connectTable, 0) + stage.show() + } + + private fun savePlaylistDialog(){ + // TODO : Playlist saving + } + inline fun useSelectedTrack(action: (Track) -> Unit) { action(table.selectionModel.selectedItem) } From 69972f7dac09193feff88be69eda614e4011e492 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Tue, 2 Jul 2019 17:48:35 +0200 Subject: [PATCH 24/99] style(player): Reformat button constructor --- src/main/xerus/monstercat/api/Player.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 6acda48..366d717 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -166,10 +166,9 @@ object Player: FadingHBox(true, targetHeight = 25) { } } - private val pauseButton = ToggleButton().id("play-pause").onClick { if(isSelected) player?.pause() else player?.play() } - .apply { - tooltip = Tooltip("Pause / Play") - } + private val pauseButton = ToggleButton().id("play-pause") + .onClick { if (isSelected) player?.pause() else player?.play() } + .apply { tooltip = Tooltip("Pause / Play") } private val stopButton = buttonWithId("stop") { reset() Playlist.clear() From 0e7760eb51b8e47b3ab5f27c6e12bf7d15193260 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Tue, 2 Jul 2019 17:54:11 +0200 Subject: [PATCH 25/99] refactor(player): Simplify playTracks code --- src/main/xerus/monstercat/api/Player.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 366d717..56c39c6 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -254,7 +254,7 @@ object Player: FadingHBox(true, targetHeight = 25) { /** Set the [tracks] as the internal playlist and start playing from the specified [index] */ fun playTracks(tracks: List, index: Int = 0) { Playlist.setTracks(tracks) - playTrack(Playlist.tracks[index]) + playFromPlaylist(index) } fun playNext(){ From c64be580d1619f73ee1323adfb9f8687567641f8 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Tue, 2 Jul 2019 18:00:00 +0200 Subject: [PATCH 26/99] refactor(downloader): Cleanup menu items of SongView --- .../xerus/monstercat/downloader/SongView.kt | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/main/xerus/monstercat/downloader/SongView.kt b/src/main/xerus/monstercat/downloader/SongView.kt index 96f086f..4a4a9b1 100644 --- a/src/main/xerus/monstercat/downloader/SongView.kt +++ b/src/main/xerus/monstercat/downloader/SongView.kt @@ -88,11 +88,7 @@ class SongView(private val sorter: ObservableValue) : } } - val defaultItems = { - arrayOf(MenuItem("Expand all") { expandAll() }, - MenuItem("Collapse all") { expandAll(false) }) - } - val item1 = MenuItem("Play") { + val menuPlay = MenuItem("Play") { val selected = selectionModel.selectedItem ?: return@MenuItem GlobalScope.launch { Playlist.clear() @@ -103,7 +99,7 @@ class SongView(private val sorter: ObservableValue) : } } } - val item2 = MenuItem("Add to playlist") { + val menuAdd = MenuItem("Add to playlist") { val selected = selectionModel.selectedItem ?: return@MenuItem GlobalScope.launch { val value = selected.value @@ -117,7 +113,7 @@ class SongView(private val sorter: ObservableValue) : } } } - val item3 = MenuItem("Play next") { + val menuAddNext = MenuItem("Play next") { val selected = selectionModel.selectedItem ?: return@MenuItem GlobalScope.launch { val value = selected.value @@ -131,20 +127,18 @@ class SongView(private val sorter: ObservableValue) : } } } - val playlistItems = { - arrayOf(item1, item2, item3) - } contextMenu = ContextMenu( - *playlistItems(), + menuPlay, menuAdd, menuAddNext, SeparatorMenuItem(), - *defaultItems() + MenuItem("Expand all") { expandAll() }, + MenuItem("Collapse all") { expandAll(false) } ) setOnContextMenuRequested { val value = selectionModel.selectedItem.value val enable = (value is Track || value is Release) - playlistItems().forEach { item -> - item.isDisable = !enable - } + menuPlay.isDisable = !enable + menuAdd.isDisable = !enable + menuAddNext.isDisable = !enable } onReady { APIConnection.connectValidity.addListener { _, old, new -> From dd82e72be0b870f3132099b78dce66959db3bc4a Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Tue, 2 Jul 2019 18:30:13 +0200 Subject: [PATCH 27/99] feat(playlist): Add helper function to add a list --- src/main/xerus/monstercat/api/Playlist.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 705f57f..917a218 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -7,6 +7,7 @@ import xerus.ktutil.javafx.properties.SimpleObservable import xerus.ktutil.javafx.properties.bindSoft import xerus.monstercat.api.response.Track import java.util.* +import kotlin.collections.ArrayList object Playlist { val logger = KotlinLogging.logger { } @@ -79,4 +80,12 @@ object Playlist { else -> return null } } + + fun addAll(tracks: ArrayList, asNext: Boolean = false) { + if (asNext) tracks.reverse() + tracks.forEach { track -> + if (asNext) addNext(track) + else add(track) + } + } } \ No newline at end of file From 602ac9706f2b48ed0a1a3c0835892da026a49b42 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Tue, 2 Jul 2019 18:31:28 +0200 Subject: [PATCH 28/99] refactor: Global MenuItem refactoring in Catalog and Playlist tabs --- src/main/xerus/monstercat/tabs/TabCatalog.kt | 101 +++++++----------- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 29 ++--- 2 files changed, 56 insertions(+), 74 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabCatalog.kt b/src/main/xerus/monstercat/tabs/TabCatalog.kt index 5713854..4068005 100644 --- a/src/main/xerus/monstercat/tabs/TabCatalog.kt +++ b/src/main/xerus/monstercat/tabs/TabCatalog.kt @@ -1,6 +1,7 @@ package xerus.monstercat.tabs import javafx.collections.ListChangeListener +import javafx.collections.ObservableList import javafx.scene.control.* import javafx.scene.input.KeyCode import javafx.scene.input.MouseButton @@ -61,14 +62,14 @@ class TabCatalog : TableTab() { }) table.setOnMouseClicked { me -> if(me.clickCount == 2 && me.button == MouseButton.PRIMARY) { - val selected = table.selectionModel.selectedItem ?: return@setOnMouseClicked - Player.play(selected[cols.findUnsafe("Track")].trim(), selected[cols.findUnsafe("Artist")]) + val selected = table.selectionModel.selectedItems ?: return@setOnMouseClicked + GlobalScope.launch { + Player.playTracks(getSongs(selected)) + } }else if(me.clickCount == 1 && me.button == MouseButton.MIDDLE){ - val selected = table.selectionModel.selectedItem ?: return@setOnMouseClicked + val selected = table.selectionModel.selectedItems ?: return@setOnMouseClicked GlobalScope.launch { - val track = APIUtils.find(selected[cols.findUnsafe("Track")].trim(), selected[cols.findUnsafe("Artist")]) - if (track != null) Playlist.add(track) - else monsterUtilities.showMessage("The requested song could not be found.", "Cannot add to playlist", Alert.AlertType.WARNING) + Playlist.addAll(getSongs(selected)) } } } @@ -76,76 +77,56 @@ class TabCatalog : TableTab() { if (it.code == KeyCode.ENTER){ val selected = table.selectionModel.selectedItems GlobalScope.launch { - Playlist.clear() - val tracklist = arrayListOf() - selected.forEach { item -> - val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) - if (track != null) tracklist.add(track) - else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") - } - Player.playTracks(tracklist) + Player.playTracks(getSongs(selected)) } }else if(it.code == KeyCode.PLUS || it.code == KeyCode.ADD){ val selected = table.selectionModel.selectedItems GlobalScope.launch { - selected.forEach { item -> - val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) - if (track != null) Playlist.add(track) - else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") - } + Playlist.addAll(getSongs(selected)) } } } table.selectionModel.selectionMode = SelectionMode.MULTIPLE + val rightClickMenu = ContextMenu() - val item1 = MenuItem("Play") { - val selected = table.selectionModel.selectedItems - GlobalScope.launch { - Playlist.clear() - val tracklist = arrayListOf() - selected.forEach { item -> - val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) - if (track != null) tracklist.add(track) - else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") - } - Player.playTracks(tracklist) - } - } - val item2 = MenuItem("Add to playlist") { - val selected = table.selectionModel.selectedItems - GlobalScope.launch { - selected.forEach { item -> - val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) - if (track != null) Playlist.add(track) - else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") - } - } - } - val item3 = MenuItem("Play next") { - val selected = table.selectionModel.selectedItems - GlobalScope.launch { - selected.asReversed().forEach { item -> - val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) - if (track != null) Playlist.addNext(track) - else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") - } - } - } - val playlistItems = { - arrayOf(item1, item2, item3) - } - val selectAllItem = MenuItem("Select All") { - table.selectionModel.selectAll() - } rightClickMenu.items.addAll( - *playlistItems(), + MenuItem("Play") { + val selected = table.selectionModel.selectedItems + GlobalScope.launch { + Player.playTracks(getSongs(selected)) + } + }, + MenuItem("Add to playlist") { + val selected = table.selectionModel.selectedItems + GlobalScope.launch { + Playlist.addAll(getSongs(selected)) + } + }, + MenuItem("Play next") { + val selected = table.selectionModel.selectedItems + GlobalScope.launch { + Playlist.addAll(getSongs(selected), true) + } + }, SeparatorMenuItem(), - selectAllItem + MenuItem("Select All") { + table.selectionModel.selectAll() + } ) table.contextMenu = rightClickMenu } + private suspend fun getSongs(songList: ObservableList>): ArrayList { + val tracklist = arrayListOf() + songList.forEach { item -> + val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) + if (track != null) tracklist.add(track) + else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") + } + return tracklist + } + private fun setColumns(columns: List) { val visibleColumns = Settings.VISIBLECATALOGCOLUMNS() val newColumns = ArrayList, *>>(columns.size) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 2ac6145..e7c6243 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -76,20 +76,21 @@ class TabPlaylist : VTab() { table.placeholder = Label("Your playlist is empty.") val rightClickMenu = ContextMenu() - val item1 = MenuItem("Play") { - Player.playFromPlaylist(table.selectionModel.selectedIndex) - } - val item2 = MenuItem("Play Next") { - useSelectedTrack { Playlist.addNext(it) } - } - val item3 = MenuItem("Remove") { - Playlist.removeAt(table.selectionModel.selectedIndex) - } - val item4 = MenuItem("Clear playlist") { - Playlist.clear() - Player.reset() - } - rightClickMenu.items.addAll(item1, item2, item3, item4) + rightClickMenu.items.addAll( + MenuItem("Play") { + Player.playFromPlaylist(table.selectionModel.selectedIndex) + }, + MenuItem("Play Next") { + useSelectedTrack { Playlist.addNext(it) } + }, + MenuItem("Remove") { + Playlist.removeAt(table.selectionModel.selectedIndex) + }, + MenuItem("Clear playlist") { + Playlist.clear() + Player.reset() + } + ) table.contextMenu = rightClickMenu fill(table) From 2f5a7e8ce3bf9ac0aae50906cb6a7614a436028c Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Tue, 2 Jul 2019 18:51:55 +0200 Subject: [PATCH 29/99] refactor(player): Rename error function when track cannot be loaded --- src/main/xerus/monstercat/api/Player.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 56c39c6..234f322 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -27,6 +27,7 @@ import xerus.monstercat.Settings import xerus.monstercat.api.response.Release import xerus.monstercat.api.response.Track import java.util.* +import java.util.concurrent.TimeUnit import kotlin.concurrent.schedule import kotlin.math.pow @@ -83,7 +84,7 @@ object Player: FadingHBox(true, targetHeight = 25) { } /** Shows [text] in the [label] and adds a back Button that calls [reset] when clicked */ - private fun showBack(text: String) { + private fun showError(text: String) { checkFx { showText(text) addButton { reset() }.id("back") @@ -95,7 +96,7 @@ object Player: FadingHBox(true, targetHeight = 25) { add(buttonWithId("skip") { playNext() }).apply { tooltip = Tooltip("Skip") } - Timer("SkipErroredSong", false).schedule(5000) { // fixme : hardcoded delay in ms + Timer().schedule(TimeUnit.SECONDS.toMillis(5)) { playNext() } } @@ -130,7 +131,7 @@ object Player: FadingHBox(true, targetHeight = 25) { fun playTrack(track: Track) { disposePlayer() val hash = track.streamHash ?: run { - showBack("$track is currently not available for streaming!") + showError("$track is currently not available for streaming!") return } logger.debug("Loading $track from $hash") @@ -151,7 +152,7 @@ object Player: FadingHBox(true, targetHeight = 25) { } setOnError { logger.warn("Error loading $track: $error", error) - showBack("Error loading $track: ${error.message?.substringAfter(": ")}") + showError("Error loading $track: ${error.message?.substringAfter(": ")}") } } } @@ -230,7 +231,7 @@ object Player: FadingHBox(true, targetHeight = 25) { disposePlayer() val track = APIUtils.find(title, artists) if(track == null) { - onFx { showBack("Track not found") } + onFx { showError("Track not found") } return@launch } playTracks(listOf(track)) From e2694bfdb821291d86cd20f13dfb21c38d383214 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Tue, 2 Jul 2019 18:55:22 +0200 Subject: [PATCH 30/99] style(player): Reformat buttons constructors --- src/main/xerus/monstercat/api/Player.kt | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 234f322..8f401cd 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -167,31 +167,23 @@ object Player: FadingHBox(true, targetHeight = 25) { } } - private val pauseButton = ToggleButton().id("play-pause") - .onClick { if (isSelected) player?.pause() else player?.play() } - .apply { tooltip = Tooltip("Pause / Play") } + private val pauseButton = ToggleButton().id("play-pause").onClick { + if (isSelected) player?.pause() else player?.play() + }.apply { tooltip = Tooltip("Pause / Play") } private val stopButton = buttonWithId("stop") { reset() Playlist.clear() - }.apply { - tooltip = Tooltip("Stop playing") - } + }.apply { tooltip = Tooltip("Stop playing") } private val volumeSlider = Slider(0.0, 1.0, Settings.PLAYERVOLUME()).scrollable(0.05).apply { prefWidth = 100.0 valueProperty().listen { updateVolume() } - }.apply { - tooltip = Tooltip("Volume") - } + }.apply { tooltip = Tooltip("Volume") } private val shuffleButton = ToggleButton().id("shuffle").onClick { Playlist.shuffle = isSelected - }.apply { - tooltip = Tooltip("Shuffle") - } + }.apply { tooltip = Tooltip("Shuffle") } private val repeatButton = ToggleButton().id("repeat").onClick { Playlist.repeat = isSelected - }.apply { - tooltip = Tooltip("Repeat all") - } + }.apply { tooltip = Tooltip("Repeat all") } private var coverUrl: String? = null private fun playing(text: String) { From 978b796f3c595bd73ee310eb6bebcbc3a81f2a96 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Tue, 2 Jul 2019 18:57:43 +0200 Subject: [PATCH 31/99] feat(player): Update Util to latest version with icons --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index ac9db39..48a65fa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,7 @@ repositories { dependencies { implementation(kotlin("reflect")) - implementation("com.github.Xerus2000.util", "javafx", "6fd9c81d25c6ac7e2315e42dc88b3556fc6b9ef7") + implementation("com.github.Xerus2000.util", "javafx", "990d8cb89119d146dc984abcd88207b490306c8e") implementation("org.controlsfx", "controlsfx", "8.40.+") implementation("ch.qos.logback", "logback-classic", "1.2.+") From 7660b501f1ce1fa0c1038089fc246e23af288433 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Wed, 3 Jul 2019 17:56:38 +0200 Subject: [PATCH 32/99] style(player): Better styling for player controls constructors --- src/main/xerus/monstercat/api/Player.kt | 39 ++++++++++++++----------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 8f401cd..e4b6b30 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -167,23 +167,28 @@ object Player: FadingHBox(true, targetHeight = 25) { } } - private val pauseButton = ToggleButton().id("play-pause").onClick { - if (isSelected) player?.pause() else player?.play() - }.apply { tooltip = Tooltip("Pause / Play") } - private val stopButton = buttonWithId("stop") { - reset() - Playlist.clear() - }.apply { tooltip = Tooltip("Stop playing") } - private val volumeSlider = Slider(0.0, 1.0, Settings.PLAYERVOLUME()).scrollable(0.05).apply { - prefWidth = 100.0 - valueProperty().listen { updateVolume() } - }.apply { tooltip = Tooltip("Volume") } - private val shuffleButton = ToggleButton().id("shuffle").onClick { - Playlist.shuffle = isSelected - }.apply { tooltip = Tooltip("Shuffle") } - private val repeatButton = ToggleButton().id("repeat").onClick { - Playlist.repeat = isSelected - }.apply { tooltip = Tooltip("Repeat all") } + private val pauseButton = ToggleButton().id("play-pause") + .onClick { if (isSelected) player?.pause() else player?.play() } + .apply { tooltip = Tooltip("Pause / Play") } + private val stopButton = Button().id("stop") + .onClick { + reset() + Playlist.clear() + } + .apply { tooltip = Tooltip("Stop playing") } + private val volumeSlider = Slider(0.0, 1.0, Settings.PLAYERVOLUME()) + .scrollable(0.05) + .apply { + prefWidth = 100.0 + valueProperty().listen { updateVolume() } + tooltip = Tooltip("Volume") + } + private val shuffleButton = ToggleButton().id("shuffle") + .onClick { Playlist.shuffle = isSelected } + .apply { tooltip = Tooltip("Shuffle") } + private val repeatButton = ToggleButton().id("repeat") + .onClick { Playlist.repeat = isSelected } + .apply { tooltip = Tooltip("Repeat all") } private var coverUrl: String? = null private fun playing(text: String) { From 5fa32ad16f38559b16ee10814492c0bd287f1081 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Sat, 6 Jul 2019 20:01:53 +0200 Subject: [PATCH 33/99] fix(playlist-api): Merge conflict with branch playlist --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 8fe4bff..d7fca72 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -175,7 +175,8 @@ class TabPlaylist : VTab() { try { val tracks = apiConnection.getTracks() if (tracks != null && tracks.isNotEmpty()) { - Playlist.loadPlaylist(tracks) + Player.reset() + Playlist.setTracks(tracks) onFx { stage.close() } } }catch (e: Exception){ @@ -191,8 +192,9 @@ class TabPlaylist : VTab() { if (connectTable.selectionModel.selectedItem != null) { GlobalScope.async { val apiConnection = APIConnection("playlist", connectTable.selectionModel.selectedItem.id, "tracks") + Player.reset() val tracks = apiConnection.getTracks()!! - Playlist.loadPlaylist(tracks) + Playlist.setTracks(tracks) } stage.close() } From 59473e2519a197e058221bb23d39cfd4309d797d Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Mon, 8 Jul 2019 00:20:55 +0200 Subject: [PATCH 34/99] fix(playlist-api): Use tracks from Cache instead of fresh ones Cache tracks are linked to releases while freshly downloaded ones from the playlist are not. That causes issues with coverart-for-all branch. --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index d7fca72..a44a140 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -15,10 +15,7 @@ import xerus.ktutil.javafx.* import xerus.ktutil.javafx.properties.ImmutableObservable import xerus.ktutil.javafx.properties.listen import xerus.ktutil.javafx.ui.App -import xerus.monstercat.api.APIConnection -import xerus.monstercat.api.ConnectValidity -import xerus.monstercat.api.Player -import xerus.monstercat.api.Playlist +import xerus.monstercat.api.* import xerus.monstercat.api.response.ConnectPlaylist import xerus.monstercat.api.response.Settings import xerus.monstercat.api.response.Track @@ -167,18 +164,30 @@ class TabPlaylist : VTab() { val buttons = HBox().apply { addButton("Load") { + suspend fun loadPlaylist(apiConnection: APIConnection){ + val tracks = apiConnection.getTracks() + tracks?.forEachIndexed { index, track -> + val found = Cache.getTracks().find { + it.id == track.id + } + if (found != null) + tracks[index] = found + else + tracks.removeAt(index) + } + if (tracks != null && tracks.isNotEmpty()) { + Player.reset() + Playlist.setTracks(tracks) + onFx { stage.close() } + } + } if (fromUrl){ val playlistId = urlField.text.substringAfterLast("/") if (playlistId.length == 24){ GlobalScope.async { val apiConnection = APIConnection("playlist", playlistId, "tracks") try { - val tracks = apiConnection.getTracks() - if (tracks != null && tracks.isNotEmpty()) { - Player.reset() - Playlist.setTracks(tracks) - onFx { stage.close() } - } + loadPlaylist(apiConnection) }catch (e: Exception){ onFx { monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlist were found at ${urlField.text}.") @@ -192,11 +201,8 @@ class TabPlaylist : VTab() { if (connectTable.selectionModel.selectedItem != null) { GlobalScope.async { val apiConnection = APIConnection("playlist", connectTable.selectionModel.selectedItem.id, "tracks") - Player.reset() - val tracks = apiConnection.getTracks()!! - Playlist.setTracks(tracks) + loadPlaylist(apiConnection) } - stage.close() } } } From 890afb0a1b1f4f9233db93bdc4c577ca3f874c4b Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Mon, 8 Jul 2019 14:09:17 +0200 Subject: [PATCH 35/99] refactor(playlist-api) --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index a44a140..c2f76fe 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -119,12 +119,30 @@ class TabPlaylist : VTab() { private fun openPlaylistDialog(){ val connection = APIConnection("playlist").fields(ConnectPlaylist::class) - val playlists = FXCollections.observableArrayList() - val parent = VBox() val stage = App.stage.createStage("Open playlist from Monstercat.com", parent) + suspend fun loadPlaylist(apiConnection: APIConnection){ + val tracks = apiConnection.getTracks() + tracks?.forEachIndexed { index, track -> + val found = Cache.getTracks().find { + it.id == track.id + } + if (found != null) + tracks[index] = found + else + tracks.removeAt(index) + } + if (tracks != null && tracks.isNotEmpty()) { + Player.reset() + Playlist.setTracks(tracks) + onFx { stage.close() } + } + } + val connectTable = TableView().apply { + val playlists = FXCollections.observableArrayList() + columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY columns.addAll(TableColumn("Name").apply { cellValueFactory = Callback, ObservableValue> { p -> @@ -140,8 +158,19 @@ class TabPlaylist : VTab() { selectionModel.selectionMode = SelectionMode.SINGLE + setOnMouseClicked { + if (it.button == MouseButton.PRIMARY && it.clickCount == 2) + if (selectionModel.selectedItem != null) { + GlobalScope.async { + val apiConnection = APIConnection("playlist", selectionModel.selectedItem.id, "tracks") + loadPlaylist(apiConnection) + } + } + } + if (APIConnection.connectValidity.value != ConnectValidity.NOGOLD && APIConnection.connectValidity.value != ConnectValidity.GOLD){ placeholder = Label("Please connect using connect.sid in the downloader tab.") + }else{ placeholder = Label("Loading...") GlobalScope.async { @@ -164,23 +193,6 @@ class TabPlaylist : VTab() { val buttons = HBox().apply { addButton("Load") { - suspend fun loadPlaylist(apiConnection: APIConnection){ - val tracks = apiConnection.getTracks() - tracks?.forEachIndexed { index, track -> - val found = Cache.getTracks().find { - it.id == track.id - } - if (found != null) - tracks[index] = found - else - tracks.removeAt(index) - } - if (tracks != null && tracks.isNotEmpty()) { - Player.reset() - Playlist.setTracks(tracks) - onFx { stage.close() } - } - } if (fromUrl){ val playlistId = urlField.text.substringAfterLast("/") if (playlistId.length == 24){ From 7798353477bd9b516faa6b61ae7232d505a8a386 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 12 Jul 2019 18:56:36 +0200 Subject: [PATCH 36/99] feat(api-connection): PUT and POST Http requests --- .../xerus/monstercat/api/APIConnection.kt | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index ea427ba..bf6a183 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -9,8 +9,8 @@ import mu.KotlinLogging import org.apache.http.HttpResponse import org.apache.http.client.config.CookieSpecs import org.apache.http.client.config.RequestConfig -import org.apache.http.client.methods.CloseableHttpResponse -import org.apache.http.client.methods.HttpGet +import org.apache.http.client.methods.* +import org.apache.http.entity.StringEntity import org.apache.http.impl.client.BasicCookieStore import org.apache.http.impl.client.CloseableHttpClient import org.apache.http.impl.client.HttpClientBuilder @@ -77,20 +77,34 @@ class APIConnection(vararg path: String) : HTTPQuery() { /** Aborts this connection and thus terminates the InputStream if active */ fun abort() { httpGet?.abort() + httpPost?.abort() + httpPut?.abort() } // Direct Requesting private var httpGet: HttpGet? = null - fun execute() { + fun get() { httpGet = HttpGet(uri) response = execute(httpGet!!) } + private var httpPost: HttpPost? = null + fun post(request : HttpPost) { + httpPost = request + response = execute(httpPost!!) + } + + private var httpPut: HttpPut? = null + fun put(request: HttpPut) { + httpPut = request + response = execute(httpPut!!) + } + private var response: HttpResponse? = null fun getResponse(): HttpResponse { if(response == null) - execute() + get() return response!! } @@ -118,7 +132,7 @@ class APIConnection(vararg path: String) : HTTPQuery() { CONNECTSID.listen { updateConnectsid(it) } } - fun execute(httpGet: HttpGet): CloseableHttpResponse { + fun execute(httpGet: HttpUriRequest): CloseableHttpResponse { logger.trace { "Connecting to ${httpGet.uri}" } return httpClient.execute(httpGet) } From e3a9e449ee0b24cd141a585b3205e6f40b591738 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 12 Jul 2019 18:57:08 +0200 Subject: [PATCH 37/99] feat(api-connection): Playlist POST (create new) and PUT (update) --- .../xerus/monstercat/api/APIConnection.kt | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index bf6a183..a8bbdab 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -74,6 +74,60 @@ class APIConnection(vararg path: String) : HTTPQuery() { fun getPlaylists()= parseJSON(PlaylistResponse::class.java)?.results?.map { it.init() } + fun editPlaylist(tracks: List? = null, name: String? = null, public: Boolean? = null, deleted: Boolean? = null) { + val request = HttpPut(uri).apply { + setHeader("Accept", "application/json") + setHeader("Content-type", "application/json") + var content = "" + if (name != null) + content += "\"name\" : \"$name\", " + if (public != null) + content += "\"public\" : $public, " + if (deleted != null) + content += "\"deleted\" : $deleted, " + if (tracks != null) { + content += "\"tracks\": [" + tracks.forEach { track -> + content += "{\"trackId\":\"${track.id}\",\"releaseId\":\"${track.release.id}\"}" + if (track != tracks.last()) + content += "," + } + content += "], " + } + content = content.removeSuffix(", ") + content = "{ $content }" + logger.debug("JSON of POST request : $content") + entity = StringEntity(content) + } + put(request) + } + + fun createPlaylist(name: String, tracks: List){ + val request = HttpPost(uri).apply { + setHeader("Accept", "application/json") + setHeader("Content-type", "application/json") + + var content = "" + + content += "\"name\" : \"$name\", " + + content += "\"tracks\": [" + tracks.forEach { track -> + content += "{\"trackId\":\"${track.id}\",\"releaseId\":\"${track.release.id}\"}" + if (track != tracks.last()) + content += "," + } + content += "], " + + content = content.removeSuffix(", ") + content = "{ $content }" + + logger.debug("JSON of PUT request : $content") + entity = StringEntity(content) + } + post(request) + } + /** Aborts this connection and thus terminates the InputStream if active */ fun abort() { httpGet?.abort() From adcf49c9171a5e7647ebcd1248d6f55ceecc72e6 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 12 Jul 2019 18:57:51 +0200 Subject: [PATCH 38/99] feat(playlist-api): Save playlist - Create new one or update existing --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 126 ++++++++++++++---- 1 file changed, 101 insertions(+), 25 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index c2f76fe..332ab56 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -1,6 +1,5 @@ package xerus.monstercat.tabs -import javafx.beans.Observable import javafx.beans.value.ObservableValue import javafx.collections.FXCollections import javafx.scene.control.* @@ -17,10 +16,7 @@ import xerus.ktutil.javafx.properties.listen import xerus.ktutil.javafx.ui.App import xerus.monstercat.api.* import xerus.monstercat.api.response.ConnectPlaylist -import xerus.monstercat.api.response.Settings import xerus.monstercat.api.response.Track -import xerus.monstercat.downloader.CONNECTSID -import xerus.monstercat.downloader.DownloaderSettings import xerus.monstercat.monsterUtilities import java.lang.Exception @@ -109,37 +105,36 @@ class TabPlaylist : VTab() { openPlaylistDialog() } buttons.addButton("Save..."){ - savePlaylistDialog() + savePlaylistDialog(Playlist.tracks) } add(buttons) fill(table) } + private suspend fun loadPlaylist(apiConnection: APIConnection){ + val tracks = apiConnection.getTracks() + tracks?.forEachIndexed { index, track -> + val found = Cache.getTracks().find { + it.id == track.id + } + if (found != null) + tracks[index] = found + else + tracks.removeAt(index) + } + if (tracks != null && tracks.isNotEmpty()) { + Player.reset() + Playlist.setTracks(tracks) + } + } + private fun openPlaylistDialog(){ val connection = APIConnection("playlist").fields(ConnectPlaylist::class) val parent = VBox() val stage = App.stage.createStage("Open playlist from Monstercat.com", parent) - suspend fun loadPlaylist(apiConnection: APIConnection){ - val tracks = apiConnection.getTracks() - tracks?.forEachIndexed { index, track -> - val found = Cache.getTracks().find { - it.id == track.id - } - if (found != null) - tracks[index] = found - else - tracks.removeAt(index) - } - if (tracks != null && tracks.isNotEmpty()) { - Player.reset() - Playlist.setTracks(tracks) - onFx { stage.close() } - } - } - val connectTable = TableView().apply { val playlists = FXCollections.observableArrayList() @@ -164,6 +159,7 @@ class TabPlaylist : VTab() { GlobalScope.async { val apiConnection = APIConnection("playlist", selectionModel.selectedItem.id, "tracks") loadPlaylist(apiConnection) + onFx { stage.close() } } } } @@ -200,6 +196,7 @@ class TabPlaylist : VTab() { val apiConnection = APIConnection("playlist", playlistId, "tracks") try { loadPlaylist(apiConnection) + onFx { stage.close() } }catch (e: Exception){ onFx { monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlist were found at ${urlField.text}.") @@ -214,6 +211,7 @@ class TabPlaylist : VTab() { GlobalScope.async { val apiConnection = APIConnection("playlist", connectTable.selectionModel.selectedItem.id, "tracks") loadPlaylist(apiConnection) + onFx { stage.close() } } } } @@ -245,8 +243,86 @@ class TabPlaylist : VTab() { stage.show() } - private fun savePlaylistDialog(){ - // TODO : Playlist saving + private fun savePlaylistDialog(playlist: List){ + val connection = APIConnection("playlist").fields(ConnectPlaylist::class) + + val parent = VBox() + val stage = App.stage.createStage("Save as...", parent) + + var existing = false + + val nameField = TextField("").apply { promptText = "Enter the playlist's name" } + + + val connectTable = TableView().apply { + val playlists = FXCollections.observableArrayList() + + columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY + columns.addAll(TableColumn("Name").apply { + cellValueFactory = Callback, ObservableValue> { p -> + ImmutableObservable(p.value.name) + } + }, TableColumn("Size").apply { + cellValueFactory = Callback, ObservableValue> { p -> + ImmutableObservable(p.value.tracks.size.toString()) + } + }) + + items = playlists + + selectionModel.selectionMode = SelectionMode.SINGLE + + if (APIConnection.connectValidity.value != ConnectValidity.NOGOLD && APIConnection.connectValidity.value != ConnectValidity.GOLD){ + placeholder = Label("Please connect using connect.sid in the downloader tab.") + + }else{ + placeholder = Label("Loading...") + GlobalScope.async { + val results = connection.getPlaylists() + if (results != null && results.isNotEmpty()) + playlists.addAll(results) + else + onFx { + placeholder = Label("No playlists were found on your account.") + } + } + } + } + + val buttons = HBox().apply { + addButton("Already existing playlist..."){}.apply{ + onClick { + existing = !existing + + parent.children.removeAt(1) + if (existing){ + text = "New playlist..." + parent.fill(connectTable, 1) + }else{ + text = "Already existing playlist..." + parent.children.add(1, nameField) + } + stage.sizeToScene() + } + } + addButton("Save") { + if (existing){ + val saveConnection = APIConnection("playlist", connectTable.selectionModel.selectedItem.id) + saveConnection.editPlaylist(playlist) + }else{ + val saveConnection = APIConnection("playlist") + saveConnection.createPlaylist(if (nameField.text.isNullOrEmpty()) "Unnamed playlist" else nameField.text, playlist) + } + stage.close() + } + addButton("Cancel") { + stage.close() + } + } + parent.add(Label("Total : ${playlist.size} tracks")) + parent.add(nameField) + parent.add(buttons) + stage.show() } inline fun useSelectedTrack(action: (Track) -> Unit) { From 03e2f424803af5dcf65c10c7fa01b369ec5f2465 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Fri, 12 Jul 2019 19:00:26 +0200 Subject: [PATCH 39/99] refactor(api-connection): Remove useless logging --- src/main/xerus/monstercat/api/APIConnection.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index a8bbdab..955493f 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -96,7 +96,6 @@ class APIConnection(vararg path: String) : HTTPQuery() { } content = content.removeSuffix(", ") content = "{ $content }" - logger.debug("JSON of POST request : $content") entity = StringEntity(content) } put(request) @@ -122,7 +121,6 @@ class APIConnection(vararg path: String) : HTTPQuery() { content = content.removeSuffix(", ") content = "{ $content }" - logger.debug("JSON of PUT request : $content") entity = StringEntity(content) } post(request) From 363824a8dbe724ce172257b20ab8b1548b5378cf Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 21 Jul 2019 19:37:53 +0200 Subject: [PATCH 40/99] refactor(playlist) --- src/main/xerus/monstercat/api/Playlist.kt | 12 +++++------- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 1 - 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 917a218..ccbb471 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -34,15 +34,13 @@ object Playlist { logger.debug("Polled $it from History") } ?: currentIndex.value?.let { get(it - 1) } - fun getNext() : Track?{ - return when { - shuffle -> nextSongRandom() - repeat && (nextSong() == null) -> tracks[0] - else -> nextSong() - } + fun getNext() = when { + shuffle -> nextSongRandom() + repeat && (nextSong() == null) -> tracks[0] + else -> nextSong() } - fun addNext(track: Track) = tracks.run { + fun addNext(track: Track) = tracks.apply { remove(track) add(currentIndex.value?.let { it + 1 } ?: 0, track) } diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index e7c6243..908ffa1 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -1,6 +1,5 @@ package xerus.monstercat.tabs -import javafx.beans.Observable import javafx.beans.value.ObservableValue import javafx.scene.control.* import javafx.scene.input.KeyCode From 2e73539c3199a9b761f757cf4991394df51744d8 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 21 Jul 2019 19:42:40 +0200 Subject: [PATCH 41/99] style(downloader-tab): remove contextMenu indentation --- src/main/xerus/monstercat/downloader/SongView.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/xerus/monstercat/downloader/SongView.kt b/src/main/xerus/monstercat/downloader/SongView.kt index 38bb55d..985e27a 100644 --- a/src/main/xerus/monstercat/downloader/SongView.kt +++ b/src/main/xerus/monstercat/downloader/SongView.kt @@ -127,12 +127,7 @@ class SongView(private val sorter: ObservableValue): } } } - contextMenu = ContextMenu( - menuPlay, menuAdd, menuAddNext, - SeparatorMenuItem(), - MenuItem("Expand all") { expandAll() }, - MenuItem("Collapse all") { expandAll(false) } - ) + contextMenu = ContextMenu(menuPlay, menuAdd, menuAddNext, SeparatorMenuItem(), MenuItem("Expand all") { expandAll() }, MenuItem("Collapse all") { expandAll(false) }) setOnContextMenuRequested { val value = selectionModel.selectedItem.value val enable = (value is Track || value is Release) From f5bb7f6818dacbd9e98c3cb76da5989f60dd880d Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 21 Jul 2019 19:44:23 +0200 Subject: [PATCH 42/99] refactor(downloader-tab): remove useless coroutine --- src/main/xerus/monstercat/downloader/SongView.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/xerus/monstercat/downloader/SongView.kt b/src/main/xerus/monstercat/downloader/SongView.kt index 985e27a..9eec639 100644 --- a/src/main/xerus/monstercat/downloader/SongView.kt +++ b/src/main/xerus/monstercat/downloader/SongView.kt @@ -90,13 +90,11 @@ class SongView(private val sorter: ObservableValue): val menuPlay = MenuItem("Play") { val selected = selectionModel.selectedItem ?: return@MenuItem - GlobalScope.launch { - Playlist.clear() - val value = selected.value - when(value) { - is Release -> Player.play(value) - is Track -> Player.playTrack(value) - } + Playlist.clear() + val value = selected.value + when(value) { + is Release -> Player.play(value) + is Track -> Player.playTrack(value) } } val menuAdd = MenuItem("Add to playlist") { From 42e81f91991a2d136268666a511726526b168b86 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 21 Jul 2019 19:49:03 +0200 Subject: [PATCH 43/99] refactor(catalog-tab): remove mouse even clickcount for middle click --- src/main/xerus/monstercat/tabs/TabCatalog.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/xerus/monstercat/tabs/TabCatalog.kt b/src/main/xerus/monstercat/tabs/TabCatalog.kt index 4068005..76f79c1 100644 --- a/src/main/xerus/monstercat/tabs/TabCatalog.kt +++ b/src/main/xerus/monstercat/tabs/TabCatalog.kt @@ -66,7 +66,7 @@ class TabCatalog : TableTab() { GlobalScope.launch { Player.playTracks(getSongs(selected)) } - }else if(me.clickCount == 1 && me.button == MouseButton.MIDDLE){ + }else if(me.button == MouseButton.MIDDLE){ val selected = table.selectionModel.selectedItems ?: return@setOnMouseClicked GlobalScope.launch { Playlist.addAll(getSongs(selected)) From ed468378735fc5902e5b164730c01943f5347547 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 21 Jul 2019 20:02:03 +0200 Subject: [PATCH 44/99] refactor(catalog-tab): move repetitive code to an inline function --- src/main/xerus/monstercat/tabs/TabCatalog.kt | 30 +++++++++----------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabCatalog.kt b/src/main/xerus/monstercat/tabs/TabCatalog.kt index 76f79c1..94fe0cb 100644 --- a/src/main/xerus/monstercat/tabs/TabCatalog.kt +++ b/src/main/xerus/monstercat/tabs/TabCatalog.kt @@ -60,30 +60,28 @@ class TabCatalog : TableTab() { table.visibleLeafColumns.addListener(ListChangeListener { it.next(); Settings.VISIBLECATALOGCOLUMNS.putMulti(*it.addedSubList.map { it.text }.toTypedArray()) }) + + fun playTracks(add: Boolean){ + val selected = table.selectionModel.selectedItems ?: return + GlobalScope.launch { + if (!add) + Player.playTracks(getSongs(selected)) + else + Playlist.addAll(getSongs(selected)) + } + } table.setOnMouseClicked { me -> if(me.clickCount == 2 && me.button == MouseButton.PRIMARY) { - val selected = table.selectionModel.selectedItems ?: return@setOnMouseClicked - GlobalScope.launch { - Player.playTracks(getSongs(selected)) - } + playTracks(false) }else if(me.button == MouseButton.MIDDLE){ - val selected = table.selectionModel.selectedItems ?: return@setOnMouseClicked - GlobalScope.launch { - Playlist.addAll(getSongs(selected)) - } + playTracks(true) } } table.setOnKeyPressed { if (it.code == KeyCode.ENTER){ - val selected = table.selectionModel.selectedItems - GlobalScope.launch { - Player.playTracks(getSongs(selected)) - } + playTracks(false) }else if(it.code == KeyCode.PLUS || it.code == KeyCode.ADD){ - val selected = table.selectionModel.selectedItems - GlobalScope.launch { - Playlist.addAll(getSongs(selected)) - } + playTracks(true) } } From 844c9bdc5333ba1fa02db6d856be0906c3f13ee1 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 21 Jul 2019 20:18:30 +0200 Subject: [PATCH 45/99] refactor(playlist-tab) * Move everything into table.apply{ } * if/else inside the string for CSS of currently played track * DRY mouse and keyboard events --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 84 +++++++++---------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 908ffa1..b10a45f 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -15,14 +15,10 @@ import xerus.monstercat.api.response.Track class TabPlaylist : VTab() { - var table = TableView().apply { + private var table = TableView().apply { columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY - } - - init { - table.items = Playlist.tracks - - table.columns.addAll(TableColumn("Artists").apply { + items = Playlist.tracks + columns.addAll(TableColumn("Artists").apply { cellValueFactory = Callback, ObservableValue> { p -> ImmutableObservable(p.value.artistsTitle) } @@ -31,72 +27,72 @@ class TabPlaylist : VTab() { ImmutableObservable(p.value.title) } }) - - table.setRowFactory { + + setRowFactory { TableRow().apply { - Playlist.currentIndex.addListener { _, _, newValue -> - style = if (index == newValue) { - "-fx-background-color: #1f6601" - } else { - "-fx-background-color: transparent" - } + Playlist.currentIndex.listen { + style = "-fx-background-color: ${if (index == it) "#1f6601" else "transparent"}" } itemProperty().listen { - style = if (index == Playlist.currentIndex.value) { - "-fx-background-color: #1f6601" - } else { - "-fx-background-color: transparent" - } + style = "-fx-background-color: ${if (index == Playlist.currentIndex.value) "#1f6601" else "transparent"}" } } } - - table.selectionModel.selectionMode = SelectionMode.SINGLE - - table.setOnMouseClicked { me -> + + selectionModel.selectionMode = SelectionMode.SINGLE + + fun removeFromPlaylist() = useSelectedIndex { Playlist.removeAt(it) } + fun playFromPlaylist() = useSelectedIndex { Player.playFromPlaylist(it) } + fun playNextPlaylist() = useSelectedTrack { Playlist.addNext(it) } + + setOnMouseClicked { me -> if (me.button == MouseButton.PRIMARY && me.clickCount == 2) { - Player.playFromPlaylist(table.selectionModel.selectedIndex) + playFromPlaylist() } if (me.button == MouseButton.MIDDLE && me.clickCount == 1) { - Playlist.removeAt(table.selectionModel.selectedIndex) + removeFromPlaylist() } } - - table.setOnKeyPressed { ke -> + setOnKeyPressed { ke -> if (ke.code == KeyCode.DELETE){ - Playlist.removeAt(table.selectionModel.selectedIndex) + removeFromPlaylist() }else if (ke.code == KeyCode.ENTER){ - Player.playFromPlaylist(table.selectionModel.selectedIndex) + playFromPlaylist() }else if (ke.code == KeyCode.ADD || ke.code == KeyCode.PLUS){ - useSelectedTrack { Playlist.addNext(it) } + playNextPlaylist() } } - - table.placeholder = Label("Your playlist is empty.") - - val rightClickMenu = ContextMenu() - rightClickMenu.items.addAll( + + placeholder = Label("Your playlist is empty.") + + contextMenu = ContextMenu().apply { + items.addAll( MenuItem("Play") { - Player.playFromPlaylist(table.selectionModel.selectedIndex) + playFromPlaylist() }, MenuItem("Play Next") { - useSelectedTrack { Playlist.addNext(it) } + playNextPlaylist() }, MenuItem("Remove") { - Playlist.removeAt(table.selectionModel.selectedIndex) + removeFromPlaylist() }, MenuItem("Clear playlist") { Playlist.clear() Player.reset() } - ) - table.contextMenu = rightClickMenu - - fill(table) + ) + } + } + + init { + fill(table) } - inline fun useSelectedTrack(action: (Track) -> Unit) { + private inline fun useSelectedTrack(action: (Track) -> Unit) { action(table.selectionModel.selectedItem) } + private inline fun useSelectedIndex(action: (Int) -> Unit){ + action(table.selectionModel.selectedIndex) + } } \ No newline at end of file From 327db231f2247ef99388205febe06fc94d83f647 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 21 Jul 2019 20:20:19 +0200 Subject: [PATCH 46/99] refactor(playlist-tab): inline ContextMenu --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index b10a45f..c60c77e 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -65,23 +65,15 @@ class TabPlaylist : VTab() { placeholder = Label("Your playlist is empty.") - contextMenu = ContextMenu().apply { - items.addAll( - MenuItem("Play") { - playFromPlaylist() - }, - MenuItem("Play Next") { - playNextPlaylist() - }, - MenuItem("Remove") { - removeFromPlaylist() - }, - MenuItem("Clear playlist") { - Playlist.clear() - Player.reset() - } - ) - } + contextMenu = ContextMenu( + MenuItem("Play") { playFromPlaylist() }, + MenuItem("Play Next") { playNextPlaylist() }, + MenuItem("Remove") { removeFromPlaylist() }, + MenuItem("Clear playlist") { + Playlist.clear() + Player.reset() + } + ) } init { From f325450b15d444974b4fc39b7c9fd6e27c66c4ee Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 21 Jul 2019 20:23:56 +0200 Subject: [PATCH 47/99] refactor(playlist-tab): use custom getter for selectedTrack --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index c60c77e..2901adb 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -41,9 +41,9 @@ class TabPlaylist : VTab() { selectionModel.selectionMode = SelectionMode.SINGLE - fun removeFromPlaylist() = useSelectedIndex { Playlist.removeAt(it) } - fun playFromPlaylist() = useSelectedIndex { Player.playFromPlaylist(it) } - fun playNextPlaylist() = useSelectedTrack { Playlist.addNext(it) } + fun removeFromPlaylist() = Playlist.removeAt(selectedIndex) + fun playFromPlaylist() = Player.playFromPlaylist(selectedIndex) + fun playNextPlaylist() = Playlist.addNext(selectedTrack) setOnMouseClicked { me -> if (me.button == MouseButton.PRIMARY && me.clickCount == 2) { @@ -80,11 +80,9 @@ class TabPlaylist : VTab() { fill(table) } - private inline fun useSelectedTrack(action: (Track) -> Unit) { - action(table.selectionModel.selectedItem) - } - private inline fun useSelectedIndex(action: (Int) -> Unit){ - action(table.selectionModel.selectedIndex) - } + private val selectedTrack: Track + get() = table.selectionModel.selectedItem + private val selectedIndex: Int + get() = table.selectionModel.selectedIndex } \ No newline at end of file From 348cb5dc6a91e276053311572e42cbcaf18efd02 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 21 Jul 2019 22:24:33 +0200 Subject: [PATCH 48/99] fix(playlist-tab): remove and replace problematic code after merge --- .../xerus/monstercat/api/APIConnection.kt | 16 ++------------ src/main/xerus/monstercat/tabs/TabPlaylist.kt | 22 ++++++++----------- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index 1d09572..97937f6 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -99,7 +99,7 @@ class APIConnection(vararg path: String) : HTTPQuery() { content = "{ $content }" entity = StringEntity(content) } - put(request) + execute(request) } fun createPlaylist(name: String, tracks: List){ @@ -124,7 +124,7 @@ class APIConnection(vararg path: String) : HTTPQuery() { entity = StringEntity(content) } - post(request) + execute(request) } @@ -141,18 +141,6 @@ class APIConnection(vararg path: String) : HTTPQuery() { response = executeRequest(request, context) } - private var httpPost: HttpPost? = null - fun post(request : HttpPost) { - httpPost = request - response = execute(httpPost!!) - } - - private var httpPut: HttpPut? = null - fun put(request: HttpPut) { - httpPut = request - response = execute(httpPut!!) - } - private var response: HttpResponse? = null fun getResponse(): HttpResponse { if(response == null) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index abbcc62..d7d62e5 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -84,8 +84,6 @@ class TabPlaylist : VTab() { } init { - fill(table) - val buttons = HBox() buttons.add(Label("From Monstercat.com :")) buttons.addButton("Open..."){ @@ -96,6 +94,7 @@ class TabPlaylist : VTab() { } add(buttons) + fill(table) } private suspend fun loadPlaylist(apiConnection: APIConnection){ @@ -116,7 +115,7 @@ class TabPlaylist : VTab() { } private fun openPlaylistDialog(){ - val connection = APIConnection("playlist").fields(ConnectPlaylist::class) + val connection = APIConnection("api", "playlist").fields(ConnectPlaylist::class) val parent = VBox() val stage = App.stage.createStage("Open playlist from Monstercat.com", parent) @@ -143,7 +142,7 @@ class TabPlaylist : VTab() { if (it.button == MouseButton.PRIMARY && it.clickCount == 2) if (selectionModel.selectedItem != null) { GlobalScope.async { - val apiConnection = APIConnection("playlist", selectionModel.selectedItem.id, "tracks") + val apiConnection = APIConnection("api", "playlist", selectionModel.selectedItem.id, "tracks") loadPlaylist(apiConnection) onFx { stage.close() } } @@ -179,7 +178,7 @@ class TabPlaylist : VTab() { val playlistId = urlField.text.substringAfterLast("/") if (playlistId.length == 24){ GlobalScope.async { - val apiConnection = APIConnection("playlist", playlistId, "tracks") + val apiConnection = APIConnection("api", "playlist", playlistId, "tracks") try { loadPlaylist(apiConnection) onFx { stage.close() } @@ -195,7 +194,7 @@ class TabPlaylist : VTab() { }else { if (connectTable.selectionModel.selectedItem != null) { GlobalScope.async { - val apiConnection = APIConnection("playlist", connectTable.selectionModel.selectedItem.id, "tracks") + val apiConnection = APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id, "tracks") loadPlaylist(apiConnection) onFx { stage.close() } } @@ -230,7 +229,7 @@ class TabPlaylist : VTab() { } private fun savePlaylistDialog(playlist: List){ - val connection = APIConnection("playlist").fields(ConnectPlaylist::class) + val connection = APIConnection("api", "playlist").fields(ConnectPlaylist::class) val parent = VBox() val stage = App.stage.createStage("Save as...", parent) @@ -293,10 +292,10 @@ class TabPlaylist : VTab() { } addButton("Save") { if (existing){ - val saveConnection = APIConnection("playlist", connectTable.selectionModel.selectedItem.id) + val saveConnection = APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id) saveConnection.editPlaylist(playlist) }else{ - val saveConnection = APIConnection("playlist") + val saveConnection = APIConnection("api", "playlist") saveConnection.createPlaylist(if (nameField.text.isNullOrEmpty()) "Unnamed playlist" else nameField.text, playlist) } stage.close() @@ -310,10 +309,7 @@ class TabPlaylist : VTab() { parent.add(buttons) stage.show() } - - inline fun useSelectedTrack(action: (Track) -> Unit) { - action(table.selectionModel.selectedItem) - + private val selectedTrack: Track get() = table.selectionModel.selectedItem private val selectedIndex: Int From dddd1a80176ebd1de010fdbc0fc0fd819abb7705 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Mon, 22 Jul 2019 20:10:54 +0200 Subject: [PATCH 49/99] refactor(api-connection): use raw string for JSON --- .../xerus/monstercat/api/APIConnection.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index 97937f6..ea7f39c 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -81,15 +81,15 @@ class APIConnection(vararg path: String) : HTTPQuery() { setHeader("Content-type", "application/json") var content = "" if (name != null) - content += "\"name\" : \"$name\", " + content += """"name" : "$name", """ if (public != null) - content += "\"public\" : $public, " + content += """"public" : $public, """ if (deleted != null) - content += "\"deleted\" : $deleted, " + content += """"deleted" : $deleted, """ if (tracks != null) { - content += "\"tracks\": [" + content += """"tracks": [""" tracks.forEach { track -> - content += "{\"trackId\":\"${track.id}\",\"releaseId\":\"${track.release.id}\"}" + content += """{"trackId":"${track.id}","releaseId":"${track.release.id}"}""" if (track != tracks.last()) content += "," } @@ -102,18 +102,20 @@ class APIConnection(vararg path: String) : HTTPQuery() { execute(request) } - fun createPlaylist(name: String, tracks: List){ + fun createPlaylist(name: String, tracks: List, public: Boolean? = false){ val request = HttpPost(uri).apply { setHeader("Accept", "application/json") setHeader("Content-type", "application/json") var content = "" - content += "\"name\" : \"$name\", " + content += """"name" : "$name", """ + + content += """"public" : "$public", """ - content += "\"tracks\": [" + content += """"tracks": [""" tracks.forEach { track -> - content += "{\"trackId\":\"${track.id}\",\"releaseId\":\"${track.release.id}\"}" + content += """{"trackId":"${track.id}","releaseId":"${track.release.id}"}""" if (track != tracks.last()) content += "," } From 8271ea02bb1b2c2670e9ce2efabac93240851b5d Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 23 Jul 2019 18:52:42 +0200 Subject: [PATCH 50/99] feat(playlist-api): merge all playlist api functions into one * Now allows the user to rename / delete / make public a playlist * Uses a PATCH on /v2/playlists/ on the API --- .../xerus/monstercat/api/APIConnection.kt | 2 +- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 291 +++++++----------- 2 files changed, 119 insertions(+), 174 deletions(-) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index ea7f39c..5e34b3c 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -76,7 +76,7 @@ class APIConnection(vararg path: String) : HTTPQuery() { parseJSON(PlaylistResponse::class.java)?.results?.map { it.init() } fun editPlaylist(tracks: List? = null, name: String? = null, public: Boolean? = null, deleted: Boolean? = null) { - val request = HttpPut(uri).apply { + val request = HttpPatch(uri).apply { setHeader("Accept", "application/json") setHeader("Content-type", "application/json") var content = "" diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index d7d62e5..6c094a1 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -5,8 +5,8 @@ import javafx.collections.FXCollections import javafx.scene.control.* import javafx.scene.input.KeyCode import javafx.scene.input.MouseButton -import javafx.scene.layout.HBox import javafx.scene.layout.VBox +import javafx.stage.Modality import javafx.util.Callback import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async @@ -18,7 +18,6 @@ import xerus.monstercat.api.* import xerus.monstercat.api.response.ConnectPlaylist import xerus.monstercat.api.response.Track import xerus.monstercat.monsterUtilities -import java.lang.Exception class TabPlaylist : VTab() { @@ -84,16 +83,7 @@ class TabPlaylist : VTab() { } init { - val buttons = HBox() - buttons.add(Label("From Monstercat.com :")) - buttons.addButton("Open..."){ - openPlaylistDialog() - } - buttons.addButton("Save..."){ - savePlaylistDialog(Playlist.tracks) - } - - add(buttons) + addButton("Playlists from Monstercat.com..."){ playlistDialog() } fill(table) } @@ -113,135 +103,121 @@ class TabPlaylist : VTab() { Playlist.setTracks(tracks) } } - - private fun openPlaylistDialog(){ + + private fun playlistDialog(){ val connection = APIConnection("api", "playlist").fields(ConnectPlaylist::class) - + val parent = VBox() - val stage = App.stage.createStage("Open playlist from Monstercat.com", parent) - - val connectTable = TableView().apply { - val playlists = FXCollections.observableArrayList() - - columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY - columns.addAll(TableColumn("Name").apply { - cellValueFactory = Callback, ObservableValue> { p -> - ImmutableObservable(p.value.name) - } - }, TableColumn("Size").apply { - cellValueFactory = Callback, ObservableValue> { p -> - ImmutableObservable(p.value.tracks.size.toString()) + val stage = App.stage.createStage("Monstercat.com Playlists", parent) + stage.initModality(Modality.WINDOW_MODAL) + val connectTable = TableView() + + // Common playlist functions + fun load() { + if (connectTable.selectionModel.selectedItem != null) { + GlobalScope.async { + loadPlaylist(APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id, "tracks")) } - }) - - items = playlists - - selectionModel.selectionMode = SelectionMode.SINGLE - - setOnMouseClicked { - if (it.button == MouseButton.PRIMARY && it.clickCount == 2) - if (selectionModel.selectedItem != null) { - GlobalScope.async { - val apiConnection = APIConnection("api", "playlist", selectionModel.selectedItem.id, "tracks") - loadPlaylist(apiConnection) - onFx { stage.close() } + } + } + fun loadUrl() { + val subParent = VBox() + val subStage = App.stage.createStage("Load from URL", subParent) + subStage.initModality(Modality.WINDOW_MODAL) + val textField = TextField().apply { promptText = "URL" } + subParent.add(textField) + subParent.addRow(createButton("Load"){ + val playlistId = textField.text.substringAfterLast("/") + if (playlistId.length == 24){ + GlobalScope.async { + try { + loadPlaylist(APIConnection("api", "playlist", playlistId, "tracks")) + onFx { subStage.close() } + }catch (e: Exception){ + onFx { + monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlist were found at ${textField.text}.") + } } } + }else{ + monsterUtilities.showAlert(Alert.AlertType.WARNING, "Playlist URL invalid", content = "${textField.text} is not a valid URL.") + } + }, createButton("Cancel"){ + subStage.close() + }) + subStage.show() + } + fun replace() { + if (connectTable.selectionModel.selectedItem != null) { + GlobalScope.async { + APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(Playlist.tracks) + } } - + } + fun delete() { + if (connectTable.selectionModel.selectedItem != null) { + GlobalScope.async { + APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(deleted = true) + } + } + } + fun new() { + val subParent = VBox() + val subStage = App.stage.createStage("New Playlist", subParent) + subStage.initModality(Modality.WINDOW_MODAL) + val textField = TextField().apply { promptText = "Name" } + val publicTick = CheckBox("Public") + subParent.children.addAll(textField, publicTick) + subParent.addRow(createButton("Create"){ + GlobalScope.async { + APIConnection("api", "playlist").createPlaylist(textField.text.let { if (it.isBlank()) "New Playlist" else it }, Playlist.tracks, publicTick.isSelected) + onFx { subStage.close() } + } + }, createButton("Cancel"){ + subStage.close() + }) + subStage.show() + } + fun rename() { + val subParent = VBox() + val subStage = App.stage.createStage("New Playlist", subParent) + subStage.initModality(Modality.WINDOW_MODAL) + val textField = TextField().apply { promptText = "Name" } + subParent.add(textField) + subParent.addRow(createButton("Create"){ + if (connectTable.selectionModel.selectedItem != null) { + GlobalScope.async { + APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(name = textField.text.let { if (it.isBlank()) "Unnamed" else it }) + onFx { subStage.close(); } + } + } + }, createButton("Cancel"){ + subStage.close() + }) + subStage.show() + } + + val playlists = FXCollections.observableArrayList() + fun updatePlaylists() { + playlists.clear() if (APIConnection.connectValidity.value != ConnectValidity.NOGOLD && APIConnection.connectValidity.value != ConnectValidity.GOLD){ - placeholder = Label("Please connect using connect.sid in the downloader tab.") - + connectTable.placeholder = Label("Please connect using connect.sid in the downloader tab.") }else{ - placeholder = Label("Loading...") + connectTable.placeholder = Label("Loading...") GlobalScope.async { val results = connection.getPlaylists() if (results != null && results.isNotEmpty()) playlists.addAll(results) else onFx { - placeholder = Label("No playlists were found on your account.") + connectTable.placeholder = Label("No playlists were found on your account.") } } } } - - val urlField = TextField("").apply { - promptText = "Enter the playlist's URL" - } - - var fromUrl = false - - val buttons = HBox().apply { - addButton("Load") { - if (fromUrl){ - val playlistId = urlField.text.substringAfterLast("/") - if (playlistId.length == 24){ - GlobalScope.async { - val apiConnection = APIConnection("api", "playlist", playlistId, "tracks") - try { - loadPlaylist(apiConnection) - onFx { stage.close() } - }catch (e: Exception){ - onFx { - monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlist were found at ${urlField.text}.") - } - } - } - }else{ - monsterUtilities.showAlert(Alert.AlertType.WARNING, "Playlist URL invalid", content = "${urlField.text} is not a valid URL.") - } - }else { - if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.async { - val apiConnection = APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id, "tracks") - loadPlaylist(apiConnection) - onFx { stage.close() } - } - } - } - } - addButton("From URL..."){}.apply { - onClick { - fromUrl = !fromUrl - - parent.children.removeAt(0) - if (fromUrl) { - text = "From account..." - parent.children.add(0, urlField) - parent.fill(pos = 1) - } else { - text = "From URL..." - parent.children.removeAt(0) - parent.fill(connectTable, 0) - } - } - } - addButton("Cancel") { - stage.close() - } - } - - // parent.add(urlField) - parent.add(buttons) - parent.fill(connectTable, 0) - stage.show() - } - - private fun savePlaylistDialog(playlist: List){ - val connection = APIConnection("api", "playlist").fields(ConnectPlaylist::class) - - val parent = VBox() - val stage = App.stage.createStage("Save as...", parent) - - var existing = false - - val nameField = TextField("").apply { promptText = "Enter the playlist's name" } - - - val connectTable = TableView().apply { - val playlists = FXCollections.observableArrayList() - + + connectTable.apply { + columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY columns.addAll(TableColumn("Name").apply { cellValueFactory = Callback, ObservableValue> { p -> @@ -252,61 +228,30 @@ class TabPlaylist : VTab() { ImmutableObservable(p.value.tracks.size.toString()) } }) - + items = playlists - + selectionModel.selectionMode = SelectionMode.SINGLE - - if (APIConnection.connectValidity.value != ConnectValidity.NOGOLD && APIConnection.connectValidity.value != ConnectValidity.GOLD){ - placeholder = Label("Please connect using connect.sid in the downloader tab.") - - }else{ - placeholder = Label("Loading...") - GlobalScope.async { - val results = connection.getPlaylists() - if (results != null && results.isNotEmpty()) - playlists.addAll(results) - else - onFx { - placeholder = Label("No playlists were found on your account.") - } - } - } - } - - val buttons = HBox().apply { - addButton("Already existing playlist..."){}.apply{ - onClick { - existing = !existing - - parent.children.removeAt(1) - if (existing){ - text = "New playlist..." - parent.fill(connectTable, 1) - }else{ - text = "Already existing playlist..." - parent.children.add(1, nameField) + + setOnMouseClicked { if (it.button == MouseButton.PRIMARY && it.clickCount == 2) load() } + + val publicMenuItem = CheckMenuItem("Public", { + if (connectTable.selectionModel.selectedItem != null) { + GlobalScope.async { + APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(public = it) + onFx { updatePlaylists() } } - stage.sizeToScene() } - } - addButton("Save") { - if (existing){ - val saveConnection = APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id) - saveConnection.editPlaylist(playlist) - }else{ - val saveConnection = APIConnection("api", "playlist") - saveConnection.createPlaylist(if (nameField.text.isNullOrEmpty()) "Unnamed playlist" else nameField.text, playlist) - } - stage.close() - } - addButton("Cancel") { - stage.close() - } + }) + contextMenu = ContextMenu(publicMenuItem, MenuItem("Save into") { replace(); updatePlaylists() }, MenuItem("Rename playlist") { rename(); updatePlaylists() }, MenuItem("Delete playlist") { delete(); updatePlaylists() }) + contextMenu.setOnShown { publicMenuItem.isSelected = selectionModel.selectedItem.public } + + updatePlaylists() } - parent.add(Label("Total : ${playlist.size} tracks")) - parent.add(nameField) - parent.add(buttons) + + parent.add(Label("Tip : You can right-click a playlist to edit it without the window closing each time !")) + parent.addRow(createButton("Load"){ load(); stage.close() }, createButton("From URL..."){ loadUrl(); stage.close() }, createButton("Save into selected"){ replace(); stage.close() }, createButton("Save as new..."){ new(); stage.close() }) + parent.fill(connectTable, 0) stage.show() } From c279baa568b838dc9158110693bce20216cc1dce Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 23 Jul 2019 19:14:24 +0200 Subject: [PATCH 51/99] fix(playlist-api): fix null-pointer when opening ContextMenu on no item --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 6c094a1..9855217 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -243,8 +243,8 @@ class TabPlaylist : VTab() { } } }) - contextMenu = ContextMenu(publicMenuItem, MenuItem("Save into") { replace(); updatePlaylists() }, MenuItem("Rename playlist") { rename(); updatePlaylists() }, MenuItem("Delete playlist") { delete(); updatePlaylists() }) - contextMenu.setOnShown { publicMenuItem.isSelected = selectionModel.selectedItem.public } + contextMenu = ContextMenu(publicMenuItem, SeparatorMenuItem(), MenuItem("Save into") { replace(); updatePlaylists() }, MenuItem("Rename playlist") { rename(); updatePlaylists() }, MenuItem("Delete playlist") { delete(); updatePlaylists() }) + contextMenu.setOnShown { publicMenuItem.isSelected = selectionModel.selectedItem?.public ?: false } updatePlaylists() } From 71761be414640b75c6c2b5a428fa314d4cc02274 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 23 Jul 2019 19:26:20 +0200 Subject: [PATCH 52/99] refactor(playlist-api): use lambda to run code after helper functions --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 9855217..c36c055 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -113,14 +113,15 @@ class TabPlaylist : VTab() { val connectTable = TableView() // Common playlist functions - fun load() { + fun load(runAfter: () -> Unit = {}) { if (connectTable.selectionModel.selectedItem != null) { GlobalScope.async { loadPlaylist(APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id, "tracks")) + onFx { runAfter.invoke() } } } } - fun loadUrl() { + fun loadUrl(runAfter: () -> Unit = {}) { val subParent = VBox() val subStage = App.stage.createStage("Load from URL", subParent) subStage.initModality(Modality.WINDOW_MODAL) @@ -132,7 +133,7 @@ class TabPlaylist : VTab() { GlobalScope.async { try { loadPlaylist(APIConnection("api", "playlist", playlistId, "tracks")) - onFx { subStage.close() } + onFx { subStage.close(); runAfter.invoke() } }catch (e: Exception){ onFx { monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlist were found at ${textField.text}.") @@ -147,21 +148,23 @@ class TabPlaylist : VTab() { }) subStage.show() } - fun replace() { + fun replace(runAfter: () -> Unit = {}) { if (connectTable.selectionModel.selectedItem != null) { GlobalScope.async { APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(Playlist.tracks) + onFx { runAfter.invoke() } } } } - fun delete() { + fun delete(runAfter: () -> Unit = {}) { if (connectTable.selectionModel.selectedItem != null) { GlobalScope.async { APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(deleted = true) + onFx { runAfter.invoke() } } } } - fun new() { + fun new(runAfter: () -> Unit = {}) { val subParent = VBox() val subStage = App.stage.createStage("New Playlist", subParent) subStage.initModality(Modality.WINDOW_MODAL) @@ -171,14 +174,14 @@ class TabPlaylist : VTab() { subParent.addRow(createButton("Create"){ GlobalScope.async { APIConnection("api", "playlist").createPlaylist(textField.text.let { if (it.isBlank()) "New Playlist" else it }, Playlist.tracks, publicTick.isSelected) - onFx { subStage.close() } + onFx { subStage.close(); runAfter.invoke() } } }, createButton("Cancel"){ subStage.close() }) subStage.show() } - fun rename() { + fun rename(runAfter: () -> Unit = {}) { val subParent = VBox() val subStage = App.stage.createStage("New Playlist", subParent) subStage.initModality(Modality.WINDOW_MODAL) @@ -188,7 +191,7 @@ class TabPlaylist : VTab() { if (connectTable.selectionModel.selectedItem != null) { GlobalScope.async { APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(name = textField.text.let { if (it.isBlank()) "Unnamed" else it }) - onFx { subStage.close(); } + onFx { subStage.close(); runAfter.invoke() } } } }, createButton("Cancel"){ @@ -243,14 +246,14 @@ class TabPlaylist : VTab() { } } }) - contextMenu = ContextMenu(publicMenuItem, SeparatorMenuItem(), MenuItem("Save into") { replace(); updatePlaylists() }, MenuItem("Rename playlist") { rename(); updatePlaylists() }, MenuItem("Delete playlist") { delete(); updatePlaylists() }) + contextMenu = ContextMenu(publicMenuItem, SeparatorMenuItem(), MenuItem("Save into") { replace { updatePlaylists() }; }, MenuItem("Rename playlist") { rename { updatePlaylists() } }, MenuItem("Delete playlist") { delete { updatePlaylists() } }) contextMenu.setOnShown { publicMenuItem.isSelected = selectionModel.selectedItem?.public ?: false } updatePlaylists() } parent.add(Label("Tip : You can right-click a playlist to edit it without the window closing each time !")) - parent.addRow(createButton("Load"){ load(); stage.close() }, createButton("From URL..."){ loadUrl(); stage.close() }, createButton("Save into selected"){ replace(); stage.close() }, createButton("Save as new..."){ new(); stage.close() }) + parent.addRow(createButton("Load"){ load(); stage.close() }, createButton("From URL..."){ loadUrl { stage.close() } }, createButton("Save into selected"){ replace { stage.close() } }, createButton("Save as new..."){ new { stage.close() } }) parent.fill(connectTable, 0) stage.show() } From 9baad4d518490090eaea2b171180a5c63fed85c4 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 23 Jul 2019 21:30:15 +0200 Subject: [PATCH 53/99] fix(playlist-api): use playlist stage as parent for sub-stage * Fixes the issue where the playlist stage could still be used while using any sub-stage like Rename or New --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index c36c055..1754f34 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -123,7 +123,7 @@ class TabPlaylist : VTab() { } fun loadUrl(runAfter: () -> Unit = {}) { val subParent = VBox() - val subStage = App.stage.createStage("Load from URL", subParent) + val subStage = stage.createStage("Load from URL", subParent) subStage.initModality(Modality.WINDOW_MODAL) val textField = TextField().apply { promptText = "URL" } subParent.add(textField) @@ -166,7 +166,7 @@ class TabPlaylist : VTab() { } fun new(runAfter: () -> Unit = {}) { val subParent = VBox() - val subStage = App.stage.createStage("New Playlist", subParent) + val subStage = stage.createStage("New Playlist", subParent) subStage.initModality(Modality.WINDOW_MODAL) val textField = TextField().apply { promptText = "Name" } val publicTick = CheckBox("Public") @@ -183,7 +183,7 @@ class TabPlaylist : VTab() { } fun rename(runAfter: () -> Unit = {}) { val subParent = VBox() - val subStage = App.stage.createStage("New Playlist", subParent) + val subStage = stage.createStage("Rename Playlist", subParent) subStage.initModality(Modality.WINDOW_MODAL) val textField = TextField().apply { promptText = "Name" } subParent.add(textField) From 50c233cb39a41916fac8d0963b46506dfc0d85e9 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 23 Jul 2019 22:02:50 +0200 Subject: [PATCH 54/99] refactor(playlist-api): moved playlist manager window to Playlist.kt --- src/main/xerus/monstercat/api/Playlist.kt | 188 ++++++++++++++++++ src/main/xerus/monstercat/tabs/TabPlaylist.kt | 173 +--------------- 2 files changed, 189 insertions(+), 172 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 693d65f..de19abc 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -1,12 +1,24 @@ package xerus.monstercat.api +import javafx.beans.value.ObservableValue import javafx.collections.FXCollections import javafx.collections.ObservableList +import javafx.scene.control.* +import javafx.scene.input.MouseButton +import javafx.scene.layout.VBox +import javafx.stage.Modality +import javafx.util.Callback +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async import mu.KotlinLogging +import xerus.ktutil.javafx.* +import xerus.ktutil.javafx.properties.ImmutableObservable import xerus.ktutil.javafx.properties.SimpleObservable import xerus.ktutil.javafx.properties.bindSoft +import xerus.ktutil.javafx.ui.App import xerus.monstercat.api.response.ConnectPlaylist import xerus.monstercat.api.response.Track +import xerus.monstercat.monsterUtilities import java.util.* import kotlin.collections.ArrayList @@ -89,4 +101,180 @@ object Playlist { else add(track) } } +} + +object PlaylistManager { + suspend fun loadPlaylist(apiConnection: APIConnection){ + val tracks = apiConnection.getTracks() + tracks?.forEachIndexed { index, track -> + val found = Cache.getTracks().find { + it.id == track.id + } + if (found != null) + tracks[index] = found + else + tracks.removeAt(index) + } + if (tracks != null && tracks.isNotEmpty()) { + Player.reset() + Playlist.setTracks(tracks) + } + } + + /** Opens the playlist manager dialog + * Allows to load, save, and manage playlists stored on Monstercat.com + */ + fun playlistDialog(){ + val connection = APIConnection("api", "playlist").fields(ConnectPlaylist::class) + + val parent = VBox() + val stage = App.stage.createStage("Monstercat.com Playlists", parent) + stage.initModality(Modality.WINDOW_MODAL) + val connectTable = TableView() + + // Common playlist functions + fun load(runAfter: () -> Unit = {}) { + if (connectTable.selectionModel.selectedItem != null) { + GlobalScope.async { + loadPlaylist(APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id, "tracks")) + onFx { runAfter.invoke() } + } + } + } + fun loadUrl(runAfter: () -> Unit = {}) { + val subParent = VBox() + val subStage = stage.createStage("Load from URL", subParent) + subStage.initModality(Modality.WINDOW_MODAL) + val textField = TextField().apply { promptText = "URL" } + subParent.add(textField) + subParent.addRow(createButton("Load"){ + val playlistId = textField.text.substringAfterLast("/") + if (playlistId.length == 24){ + GlobalScope.async { + try { + loadPlaylist(APIConnection("api", "playlist", playlistId, "tracks")) + onFx { subStage.close(); runAfter.invoke() } + }catch (e: Exception){ + onFx { + monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlist were found at ${textField.text}.") + } + } + } + }else{ + monsterUtilities.showAlert(Alert.AlertType.WARNING, "Playlist URL invalid", content = "${textField.text} is not a valid URL.") + } + }, createButton("Cancel"){ + subStage.close() + }) + subStage.show() + } + fun replace(runAfter: () -> Unit = {}) { + if (connectTable.selectionModel.selectedItem != null) { + GlobalScope.async { + APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(Playlist.tracks) + onFx { runAfter.invoke() } + } + } + } + fun delete(runAfter: () -> Unit = {}) { + if (connectTable.selectionModel.selectedItem != null) { + GlobalScope.async { + APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(deleted = true) + onFx { runAfter.invoke() } + } + } + } + fun new(runAfter: () -> Unit = {}) { + val subParent = VBox() + val subStage = stage.createStage("New Playlist", subParent) + subStage.initModality(Modality.WINDOW_MODAL) + val textField = TextField().apply { promptText = "Name" } + val publicTick = CheckBox("Public") + subParent.children.addAll(textField, publicTick) + subParent.addRow(createButton("Create"){ + GlobalScope.async { + APIConnection("api", "playlist").createPlaylist(textField.text.let { if (it.isBlank()) "New Playlist" else it }, Playlist.tracks, publicTick.isSelected) + onFx { subStage.close(); runAfter.invoke() } + } + }, createButton("Cancel"){ + subStage.close() + }) + subStage.show() + } + fun rename(runAfter: () -> Unit = {}) { + val subParent = VBox() + val subStage = stage.createStage("Rename Playlist", subParent) + subStage.initModality(Modality.WINDOW_MODAL) + val textField = TextField().apply { promptText = "Name" } + subParent.add(textField) + subParent.addRow(createButton("Create"){ + if (connectTable.selectionModel.selectedItem != null) { + GlobalScope.async { + APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(name = textField.text.let { if (it.isBlank()) "Unnamed" else it }) + onFx { subStage.close(); runAfter.invoke() } + } + } + }, createButton("Cancel"){ + subStage.close() + }) + subStage.show() + } + + val playlists = FXCollections.observableArrayList() + fun updatePlaylists() { + playlists.clear() + if (APIConnection.connectValidity.value != ConnectValidity.NOGOLD && APIConnection.connectValidity.value != ConnectValidity.GOLD){ + connectTable.placeholder = Label("Please connect using connect.sid in the downloader tab.") + }else{ + connectTable.placeholder = Label("Loading...") + GlobalScope.async { + val results = connection.getPlaylists() + if (results != null && results.isNotEmpty()) + playlists.addAll(results) + else + onFx { + connectTable.placeholder = Label("No playlists were found on your account.") + } + } + } + } + + connectTable.apply { + + columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY + columns.addAll(TableColumn("Name").apply { + cellValueFactory = Callback, ObservableValue> { p -> + ImmutableObservable(p.value.name) + } + }, TableColumn("Size").apply { + cellValueFactory = Callback, ObservableValue> { p -> + ImmutableObservable(p.value.tracks.size.toString()) + } + }) + + items = playlists + + selectionModel.selectionMode = SelectionMode.SINGLE + + setOnMouseClicked { if (it.button == MouseButton.PRIMARY && it.clickCount == 2) load() } + + val publicMenuItem = CheckMenuItem("Public", { + if (connectTable.selectionModel.selectedItem != null) { + GlobalScope.async { + APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(public = it) + onFx { updatePlaylists() } + } + } + }) + contextMenu = ContextMenu(publicMenuItem, SeparatorMenuItem(), MenuItem("Save into") { replace { updatePlaylists() }; }, MenuItem("Rename playlist") { rename { updatePlaylists() } }, MenuItem("Delete playlist") { delete { updatePlaylists() } }) + contextMenu.setOnShown { publicMenuItem.isSelected = selectionModel.selectedItem?.public ?: false } + + updatePlaylists() + } + + parent.add(Label("Tip : You can right-click a playlist to edit it without the window closing each time !")) + parent.addRow(createButton("Load"){ load(); stage.close() }, createButton("From URL..."){ loadUrl { stage.close() } }, createButton("Save into selected"){ replace { stage.close() } }, createButton("Save as new..."){ new { stage.close() } }) + parent.fill(connectTable, 0) + stage.show() + } } \ No newline at end of file diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 1754f34..0709cb0 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -83,180 +83,9 @@ class TabPlaylist : VTab() { } init { - addButton("Playlists from Monstercat.com..."){ playlistDialog() } + addButton("Playlists from Monstercat.com..."){ PlaylistManager.playlistDialog() } fill(table) } - - private suspend fun loadPlaylist(apiConnection: APIConnection){ - val tracks = apiConnection.getTracks() - tracks?.forEachIndexed { index, track -> - val found = Cache.getTracks().find { - it.id == track.id - } - if (found != null) - tracks[index] = found - else - tracks.removeAt(index) - } - if (tracks != null && tracks.isNotEmpty()) { - Player.reset() - Playlist.setTracks(tracks) - } - } - - private fun playlistDialog(){ - val connection = APIConnection("api", "playlist").fields(ConnectPlaylist::class) - - val parent = VBox() - val stage = App.stage.createStage("Monstercat.com Playlists", parent) - stage.initModality(Modality.WINDOW_MODAL) - val connectTable = TableView() - - // Common playlist functions - fun load(runAfter: () -> Unit = {}) { - if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.async { - loadPlaylist(APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id, "tracks")) - onFx { runAfter.invoke() } - } - } - } - fun loadUrl(runAfter: () -> Unit = {}) { - val subParent = VBox() - val subStage = stage.createStage("Load from URL", subParent) - subStage.initModality(Modality.WINDOW_MODAL) - val textField = TextField().apply { promptText = "URL" } - subParent.add(textField) - subParent.addRow(createButton("Load"){ - val playlistId = textField.text.substringAfterLast("/") - if (playlistId.length == 24){ - GlobalScope.async { - try { - loadPlaylist(APIConnection("api", "playlist", playlistId, "tracks")) - onFx { subStage.close(); runAfter.invoke() } - }catch (e: Exception){ - onFx { - monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlist were found at ${textField.text}.") - } - } - } - }else{ - monsterUtilities.showAlert(Alert.AlertType.WARNING, "Playlist URL invalid", content = "${textField.text} is not a valid URL.") - } - }, createButton("Cancel"){ - subStage.close() - }) - subStage.show() - } - fun replace(runAfter: () -> Unit = {}) { - if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.async { - APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(Playlist.tracks) - onFx { runAfter.invoke() } - } - } - } - fun delete(runAfter: () -> Unit = {}) { - if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.async { - APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(deleted = true) - onFx { runAfter.invoke() } - } - } - } - fun new(runAfter: () -> Unit = {}) { - val subParent = VBox() - val subStage = stage.createStage("New Playlist", subParent) - subStage.initModality(Modality.WINDOW_MODAL) - val textField = TextField().apply { promptText = "Name" } - val publicTick = CheckBox("Public") - subParent.children.addAll(textField, publicTick) - subParent.addRow(createButton("Create"){ - GlobalScope.async { - APIConnection("api", "playlist").createPlaylist(textField.text.let { if (it.isBlank()) "New Playlist" else it }, Playlist.tracks, publicTick.isSelected) - onFx { subStage.close(); runAfter.invoke() } - } - }, createButton("Cancel"){ - subStage.close() - }) - subStage.show() - } - fun rename(runAfter: () -> Unit = {}) { - val subParent = VBox() - val subStage = stage.createStage("Rename Playlist", subParent) - subStage.initModality(Modality.WINDOW_MODAL) - val textField = TextField().apply { promptText = "Name" } - subParent.add(textField) - subParent.addRow(createButton("Create"){ - if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.async { - APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(name = textField.text.let { if (it.isBlank()) "Unnamed" else it }) - onFx { subStage.close(); runAfter.invoke() } - } - } - }, createButton("Cancel"){ - subStage.close() - }) - subStage.show() - } - - val playlists = FXCollections.observableArrayList() - fun updatePlaylists() { - playlists.clear() - if (APIConnection.connectValidity.value != ConnectValidity.NOGOLD && APIConnection.connectValidity.value != ConnectValidity.GOLD){ - connectTable.placeholder = Label("Please connect using connect.sid in the downloader tab.") - }else{ - connectTable.placeholder = Label("Loading...") - GlobalScope.async { - val results = connection.getPlaylists() - if (results != null && results.isNotEmpty()) - playlists.addAll(results) - else - onFx { - connectTable.placeholder = Label("No playlists were found on your account.") - } - } - } - } - - connectTable.apply { - - columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY - columns.addAll(TableColumn("Name").apply { - cellValueFactory = Callback, ObservableValue> { p -> - ImmutableObservable(p.value.name) - } - }, TableColumn("Size").apply { - cellValueFactory = Callback, ObservableValue> { p -> - ImmutableObservable(p.value.tracks.size.toString()) - } - }) - - items = playlists - - selectionModel.selectionMode = SelectionMode.SINGLE - - setOnMouseClicked { if (it.button == MouseButton.PRIMARY && it.clickCount == 2) load() } - - val publicMenuItem = CheckMenuItem("Public", { - if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.async { - APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(public = it) - onFx { updatePlaylists() } - } - } - }) - contextMenu = ContextMenu(publicMenuItem, SeparatorMenuItem(), MenuItem("Save into") { replace { updatePlaylists() }; }, MenuItem("Rename playlist") { rename { updatePlaylists() } }, MenuItem("Delete playlist") { delete { updatePlaylists() } }) - contextMenu.setOnShown { publicMenuItem.isSelected = selectionModel.selectedItem?.public ?: false } - - updatePlaylists() - } - - parent.add(Label("Tip : You can right-click a playlist to edit it without the window closing each time !")) - parent.addRow(createButton("Load"){ load(); stage.close() }, createButton("From URL..."){ loadUrl { stage.close() } }, createButton("Save into selected"){ replace { stage.close() } }, createButton("Save as new..."){ new { stage.close() } }) - parent.fill(connectTable, 0) - stage.show() - } private val selectedTrack: Track get() = table.selectionModel.selectedItem From 5a132439e73975ed6916122a054bc246eb24114c Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Thu, 25 Jul 2019 18:53:39 +0200 Subject: [PATCH 55/99] refactor(playlist-api): move playlist functions to companion object --- .../xerus/monstercat/api/APIConnection.kt | 87 +++++++------------ src/main/xerus/monstercat/api/Playlist.kt | 17 ++-- .../api/response/ConnectPlaylist.kt | 7 +- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 16 ++-- 4 files changed, 51 insertions(+), 76 deletions(-) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index 5e34b3c..3a8fdbb 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -19,6 +19,7 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager import org.apache.http.impl.cookie.BasicClientCookie import xerus.ktutil.collections.isEmpty import xerus.ktutil.helpers.HTTPQuery +import xerus.ktutil.ifNotNull import xerus.ktutil.javafx.properties.SimpleObservable import xerus.ktutil.javafx.properties.listen import xerus.monstercat.Sheets @@ -74,61 +75,6 @@ class APIConnection(vararg path: String) : HTTPQuery() { fun getPlaylists()= parseJSON(PlaylistResponse::class.java)?.results?.map { it.init() } - - fun editPlaylist(tracks: List? = null, name: String? = null, public: Boolean? = null, deleted: Boolean? = null) { - val request = HttpPatch(uri).apply { - setHeader("Accept", "application/json") - setHeader("Content-type", "application/json") - var content = "" - if (name != null) - content += """"name" : "$name", """ - if (public != null) - content += """"public" : $public, """ - if (deleted != null) - content += """"deleted" : $deleted, """ - if (tracks != null) { - content += """"tracks": [""" - tracks.forEach { track -> - content += """{"trackId":"${track.id}","releaseId":"${track.release.id}"}""" - if (track != tracks.last()) - content += "," - } - content += "], " - } - content = content.removeSuffix(", ") - content = "{ $content }" - entity = StringEntity(content) - } - execute(request) - } - - fun createPlaylist(name: String, tracks: List, public: Boolean? = false){ - val request = HttpPost(uri).apply { - setHeader("Accept", "application/json") - setHeader("Content-type", "application/json") - - var content = "" - - content += """"name" : "$name", """ - - content += """"public" : "$public", """ - - content += """"tracks": [""" - tracks.forEach { track -> - content += """{"trackId":"${track.id}","releaseId":"${track.release.id}"}""" - if (track != tracks.last()) - content += "," - } - content += "], " - - content = content.removeSuffix(", ") - content = "{ $content }" - - entity = StringEntity(content) - } - execute(request) - } - private var httpRequest: HttpUriRequest? = null /** Aborts this connection and thus terminates the InputStream if active */ @@ -139,6 +85,8 @@ class APIConnection(vararg path: String) : HTTPQuery() { // Direct Requesting fun execute(request: HttpUriRequest, context: HttpClientContext? = null) { + request.setHeader("Accept", "application/json") + request.setHeader("Content-type", "application/json") httpRequest = request response = executeRequest(request, context) } @@ -275,6 +223,35 @@ class APIConnection(vararg path: String) : HTTPQuery() { fun logout() { CONNECTSID.clear() } + + private fun convertTracklist(tracks: List) = tracks.map { HashMap().apply { this["trackId"] = it.id; this["releaseId"] = it.release.id } } + + fun editPlaylist(id: String, tracks: List? = null, name: String? = null, public: Boolean? = null, deleted: Boolean? = null) { + val json = HashMap() + tracks.ifNotNull { json["tracks"] = convertTracklist(it) } + name.ifNotNull { json["name"] = it } + public.ifNotNull { json["public"] = it } + deleted.ifNotNull { json["deleted"] = it } + val connection = APIConnection("v2", "playlist", id) + val request = HttpPatch(connection.uri).apply { + val content = Sheets.JSON_FACTORY.toString(json) + entity = StringEntity(content) + } + connection.execute(request) + } + + fun createPlaylist(name: String, tracks: List, public: Boolean = false){ + val connection = APIConnection("api", "playlist") + val request = HttpPost(connection.uri).apply { + val json = HashMap() + json["name"] = name + json["public"] = public + json["tracks"] = convertTracklist(tracks) + val content = Sheets.JSON_FACTORY.toString(json) + entity = StringEntity(content) + } + connection.execute(request) + } data class ConnectResult(val connectsid: String, val validity: ConnectValidity, val session: Session?) } diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index de19abc..26f61ac 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -6,6 +6,7 @@ import javafx.collections.ObservableList import javafx.scene.control.* import javafx.scene.input.MouseButton import javafx.scene.layout.VBox +import javafx.scene.media.MediaPlayer import javafx.stage.Modality import javafx.util.Callback import kotlinx.coroutines.GlobalScope @@ -20,7 +21,6 @@ import xerus.monstercat.api.response.ConnectPlaylist import xerus.monstercat.api.response.Track import xerus.monstercat.monsterUtilities import java.util.* -import kotlin.collections.ArrayList object Playlist { val logger = KotlinLogging.logger { } @@ -116,7 +116,8 @@ object PlaylistManager { tracks.removeAt(index) } if (tracks != null && tracks.isNotEmpty()) { - Player.reset() + if (Player.player?.status != MediaPlayer.Status.DISPOSED) + Player.reset() Playlist.setTracks(tracks) } } @@ -171,7 +172,7 @@ object PlaylistManager { fun replace(runAfter: () -> Unit = {}) { if (connectTable.selectionModel.selectedItem != null) { GlobalScope.async { - APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(Playlist.tracks) + APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, tracks = Playlist.tracks) onFx { runAfter.invoke() } } } @@ -179,7 +180,7 @@ object PlaylistManager { fun delete(runAfter: () -> Unit = {}) { if (connectTable.selectionModel.selectedItem != null) { GlobalScope.async { - APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(deleted = true) + APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, deleted = true) onFx { runAfter.invoke() } } } @@ -193,7 +194,7 @@ object PlaylistManager { subParent.children.addAll(textField, publicTick) subParent.addRow(createButton("Create"){ GlobalScope.async { - APIConnection("api", "playlist").createPlaylist(textField.text.let { if (it.isBlank()) "New Playlist" else it }, Playlist.tracks, publicTick.isSelected) + APIConnection.createPlaylist(textField.text.let { if (it.isBlank()) "New Playlist" else it }, Playlist.tracks, publicTick.isSelected) onFx { subStage.close(); runAfter.invoke() } } }, createButton("Cancel"){ @@ -207,10 +208,10 @@ object PlaylistManager { subStage.initModality(Modality.WINDOW_MODAL) val textField = TextField().apply { promptText = "Name" } subParent.add(textField) - subParent.addRow(createButton("Create"){ + subParent.addRow(createButton("Rename"){ if (connectTable.selectionModel.selectedItem != null) { GlobalScope.async { - APIConnection("v2", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(name = textField.text.let { if (it.isBlank()) "Unnamed" else it }) + APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, name = textField.text.let { if (it.isBlank()) "Unnamed" else it }) onFx { subStage.close(); runAfter.invoke() } } } @@ -261,7 +262,7 @@ object PlaylistManager { val publicMenuItem = CheckMenuItem("Public", { if (connectTable.selectionModel.selectedItem != null) { GlobalScope.async { - APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id).editPlaylist(public = it) + APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, public = it) onFx { updatePlaylists() } } } diff --git a/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt b/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt index 773b0d6..01078e9 100644 --- a/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt +++ b/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt @@ -6,12 +6,13 @@ data class ConnectPlaylist( @Key("_id") var id: String = "", @Key var name: String = "", @Key var public: Boolean = false, - - @Key var tracks: ArrayList = arrayListOf() + @Key var deleted: Boolean = false, + + @Key var tracks: List = arrayListOf() ) { fun init(): ConnectPlaylist { name = name.trim() return this } -} +} \ No newline at end of file diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 0709cb0..1ca5bd4 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -1,23 +1,19 @@ package xerus.monstercat.tabs import javafx.beans.value.ObservableValue -import javafx.collections.FXCollections import javafx.scene.control.* import javafx.scene.input.KeyCode import javafx.scene.input.MouseButton -import javafx.scene.layout.VBox -import javafx.stage.Modality import javafx.util.Callback -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import xerus.ktutil.javafx.* +import xerus.ktutil.javafx.MenuItem +import xerus.ktutil.javafx.addButton +import xerus.ktutil.javafx.fill import xerus.ktutil.javafx.properties.ImmutableObservable import xerus.ktutil.javafx.properties.listen -import xerus.ktutil.javafx.ui.App -import xerus.monstercat.api.* -import xerus.monstercat.api.response.ConnectPlaylist +import xerus.monstercat.api.Player +import xerus.monstercat.api.Playlist +import xerus.monstercat.api.PlaylistManager import xerus.monstercat.api.response.Track -import xerus.monstercat.monsterUtilities class TabPlaylist : VTab() { From 138d82881907a2ea0543bebd84f7c2d37e0a0b63 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Thu, 25 Jul 2019 19:02:19 +0200 Subject: [PATCH 56/99] refactor(playlist): use apply for track addition --- src/main/xerus/monstercat/api/Playlist.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index ccbb471..ca92f74 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -40,14 +40,12 @@ object Playlist { else -> nextSong() } - fun addNext(track: Track) = tracks.apply { - remove(track) - add(currentIndex.value?.let { it + 1 } ?: 0, track) + fun addNext(track: Track) { + tracks.apply { remove(track); add(currentIndex.value?.let { it + 1 } ?: 0, track) } } - fun add(track: Track): Boolean = tracks.run { - remove(track) - return add(track) + fun add(track: Track) { + tracks.apply { remove(track); add(track) } } fun removeAt(index: Int?) { From 9a6333ef3045af09a671ff6a8c3fccbfd731169c Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Thu, 25 Jul 2019 19:07:25 +0200 Subject: [PATCH 57/99] style(player): use tooltip helper instead of apply --- src/main/xerus/monstercat/api/Player.kt | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index a888bf7..e00e81f 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -94,9 +94,7 @@ object Player: FadingHBox(true, targetHeight = 25) { if (Playlist.tracks.size < 2){ add(closeButton) }else{ - add(buttonWithId("skip") { playNext() }).apply { - tooltip = Tooltip("Skip") - } + add(buttonWithId("skip") { playNext() }).tooltip("Skip") Timer().schedule(TimeUnit.SECONDS.toMillis(5)) { playNext() } @@ -176,13 +174,13 @@ object Player: FadingHBox(true, targetHeight = 25) { private val pauseButton = ToggleButton().id("play-pause") .onClick { if (isSelected) player?.pause() else player?.play() } - .apply { tooltip = Tooltip("Pause / Play") } + .tooltip("Pause / Play") private val stopButton = Button().id("stop") .onClick { reset() Playlist.clear() } - .apply { tooltip = Tooltip("Stop playing") } + .tooltip("Stop playing") private val volumeSlider = Slider(0.0, 1.0, Settings.PLAYERVOLUME()) .scrollable(0.05) .apply { @@ -192,10 +190,10 @@ object Player: FadingHBox(true, targetHeight = 25) { } private val shuffleButton = ToggleButton().id("shuffle") .onClick { Playlist.shuffle = isSelected } - .apply { tooltip = Tooltip("Shuffle") } + .tooltip("Shuffle") private val repeatButton = ToggleButton().id("repeat") .onClick { Playlist.repeat = isSelected } - .apply { tooltip = Tooltip("Repeat all") } + .tooltip("Repeat all") private var coverUrl: String? = null private fun playing(text: String) { @@ -207,12 +205,8 @@ object Player: FadingHBox(true, targetHeight = 25) { } add(pauseButton.apply { isSelected = false }) add(stopButton) - add(buttonWithId("skipback") { playPrev() }).apply { - tooltip = Tooltip("Previous") - } - add(buttonWithId("skip") { playNext() }).apply { - tooltip = Tooltip("Next") - } + add(buttonWithId("skipback") { playPrev() }).tooltip("Previous") + add(buttonWithId("skip") { playNext() }).tooltip("Next") add(shuffleButton.apply { isSelected = Playlist.shuffle }) add(repeatButton.apply { isSelected = Playlist.repeat }) add(volumeSlider) From a3d188163935d19426959611a8fce4191e87e858 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Thu, 25 Jul 2019 19:31:23 +0200 Subject: [PATCH 58/99] refactor(playlist-tab): use helper function for table columns --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 2901adb..2b2e0c4 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -1,13 +1,11 @@ package xerus.monstercat.tabs -import javafx.beans.value.ObservableValue import javafx.scene.control.* import javafx.scene.input.KeyCode import javafx.scene.input.MouseButton -import javafx.util.Callback import xerus.ktutil.javafx.MenuItem +import xerus.ktutil.javafx.TableColumn import xerus.ktutil.javafx.fill -import xerus.ktutil.javafx.properties.ImmutableObservable import xerus.ktutil.javafx.properties.listen import xerus.monstercat.api.Player import xerus.monstercat.api.Playlist @@ -18,15 +16,8 @@ class TabPlaylist : VTab() { private var table = TableView().apply { columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY items = Playlist.tracks - columns.addAll(TableColumn("Artists").apply { - cellValueFactory = Callback, ObservableValue> { p -> - ImmutableObservable(p.value.artistsTitle) - } - }, TableColumn("Title").apply { - cellValueFactory = Callback, ObservableValue> { p -> - ImmutableObservable(p.value.title) - } - }) + columns.addAll(TableColumn("Artists") { it.value.artistsTitle }, + TableColumn("Title") { it.value.title }) setRowFactory { TableRow().apply { From 165cdf2451cbbfdf098bfbcc5de515f6e8e524b8 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Thu, 25 Jul 2019 19:35:10 +0200 Subject: [PATCH 59/99] refactor(playlist-tab): move some parts of table into init --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 2b2e0c4..0df337e 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -14,24 +14,12 @@ import xerus.monstercat.api.response.Track class TabPlaylist : VTab() { private var table = TableView().apply { + // Inherent properties columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY - items = Playlist.tracks - columns.addAll(TableColumn("Artists") { it.value.artistsTitle }, - TableColumn("Title") { it.value.title }) - - setRowFactory { - TableRow().apply { - Playlist.currentIndex.listen { - style = "-fx-background-color: ${if (index == it) "#1f6601" else "transparent"}" - } - itemProperty().listen { - style = "-fx-background-color: ${if (index == Playlist.currentIndex.value) "#1f6601" else "transparent"}" - } - } - } - selectionModel.selectionMode = SelectionMode.SINGLE + placeholder = Label("Your playlist is empty.") + // Events fun removeFromPlaylist() = Playlist.removeAt(selectedIndex) fun playFromPlaylist() = Player.playFromPlaylist(selectedIndex) fun playNextPlaylist() = Playlist.addNext(selectedTrack) @@ -53,21 +41,34 @@ class TabPlaylist : VTab() { playNextPlaylist() } } + contextMenu = ContextMenu( + MenuItem("Play") { playFromPlaylist() }, + MenuItem("Play Next") { playNextPlaylist() }, + MenuItem("Remove") { removeFromPlaylist() }, + MenuItem("Clear playlist") { + Playlist.clear() + Player.reset() + } + ) - placeholder = Label("Your playlist is empty.") + // Columns and rows + columns.addAll(TableColumn("Artists") { it.value.artistsTitle }, + TableColumn("Title") { it.value.title }) - contextMenu = ContextMenu( - MenuItem("Play") { playFromPlaylist() }, - MenuItem("Play Next") { playNextPlaylist() }, - MenuItem("Remove") { removeFromPlaylist() }, - MenuItem("Clear playlist") { - Playlist.clear() - Player.reset() + setRowFactory { + TableRow().apply { + Playlist.currentIndex.listen { + style = "-fx-background-color: ${if (index == it) "#1f6601" else "transparent"}" + } + itemProperty().listen { + style = "-fx-background-color: ${if (index == Playlist.currentIndex.value) "#1f6601" else "transparent"}" + } } - ) + } } init { + table.items = Playlist.tracks fill(table) } From ed62aa36c31d0a84e079369cdb0bdf17bf6c3caf Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Fri, 26 Jul 2019 22:31:48 +0200 Subject: [PATCH 60/99] style(playlist): remove apply used for two lines --- src/main/xerus/monstercat/api/Playlist.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index ca92f74..3580e89 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -41,11 +41,13 @@ object Playlist { } fun addNext(track: Track) { - tracks.apply { remove(track); add(currentIndex.value?.let { it + 1 } ?: 0, track) } + tracks.remove(track) + tracks.add(currentIndex.value?.let { it + 1 } ?: 0, track) } fun add(track: Track) { - tracks.apply { remove(track); add(track) } + tracks.remove(track) + tracks.add(track) } fun removeAt(index: Int?) { From 3794cffdf0545c816ccdc1f031846bd12239848d Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Fri, 26 Jul 2019 22:45:02 +0200 Subject: [PATCH 61/99] refactor(player): move apply and use addAll for player buttons --- src/main/xerus/monstercat/api/Player.kt | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index e00e81f..6caf911 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -173,14 +173,14 @@ object Player: FadingHBox(true, targetHeight = 25) { } private val pauseButton = ToggleButton().id("play-pause") - .onClick { if (isSelected) player?.pause() else player?.play() } .tooltip("Pause / Play") + .onClick { if (isSelected) player?.pause() else player?.play() } private val stopButton = Button().id("stop") + .tooltip("Stop playing") .onClick { reset() Playlist.clear() } - .tooltip("Stop playing") private val volumeSlider = Slider(0.0, 1.0, Settings.PLAYERVOLUME()) .scrollable(0.05) .apply { @@ -189,12 +189,18 @@ object Player: FadingHBox(true, targetHeight = 25) { tooltip = Tooltip("Volume") } private val shuffleButton = ToggleButton().id("shuffle") - .onClick { Playlist.shuffle = isSelected } .tooltip("Shuffle") + .onClick { Playlist.shuffle = isSelected } + .apply { isSelected = Playlist.shuffle } private val repeatButton = ToggleButton().id("repeat") - .onClick { Playlist.repeat = isSelected } .tooltip("Repeat all") - + .onClick { Playlist.repeat = isSelected } + .apply { isSelected = Playlist.repeat } + private val skipbackButton = buttonWithId("skipback") { playPrev() } + .tooltip("Previous") + private val skipButton = buttonWithId("skip") { playNext() } + .tooltip("Next") + private var coverUrl: String? = null private fun playing(text: String) { onFx { @@ -203,13 +209,7 @@ object Player: FadingHBox(true, targetHeight = 25) { children.add(0, ImageView(Covers.getCoverImage(coverUrl!!, 24))) children.add(1, Region().setSize(4.0)) } - add(pauseButton.apply { isSelected = false }) - add(stopButton) - add(buttonWithId("skipback") { playPrev() }).tooltip("Previous") - add(buttonWithId("skip") { playNext() }).tooltip("Next") - add(shuffleButton.apply { isSelected = Playlist.shuffle }) - add(repeatButton.apply { isSelected = Playlist.repeat }) - add(volumeSlider) + children.addAll(pauseButton.apply { isSelected = false }, stopButton, skipbackButton, skipButton, shuffleButton, repeatButton, volumeSlider) fill(pos = 0) fill() add(closeButton) From f8eea82b4e90ce2272c0ac2e2caca58216262fd6 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Fri, 26 Jul 2019 22:45:26 +0200 Subject: [PATCH 62/99] refactor(playlist): use different null checks and more compact code --- src/main/xerus/monstercat/api/Player.kt | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 6caf911..2abf442 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import mu.KotlinLogging +import xerus.ktutil.ifNotNull import xerus.ktutil.javafx.* import xerus.ktutil.javafx.properties.SimpleObservable import xerus.ktutil.javafx.properties.dependOn @@ -237,10 +238,7 @@ object Player: FadingHBox(true, targetHeight = 25) { } fun playFromPlaylist(index: Int){ - val track = Playlist[index] - if (track != null) { - playTrack(track) - } + Playlist[index].ifNotNull { playTrack(it) } } /** Plays this [release], creating an internal playlist when it has multiple Tracks */ @@ -257,20 +255,11 @@ object Player: FadingHBox(true, targetHeight = 25) { } fun playNext(){ - val next = Playlist.getNext() - if (next == null){ - reset() - }else{ - playTrack(next) - - } + Playlist.getNext()?.let { playTrack(it) } ?: reset() } fun playPrev(){ - val prev = Playlist.getPrev() - if (prev != null){ - playTrack(prev) - } + Playlist.getPrev().ifNotNull { playTrack(it) } } fun updateCover(coverUrl: String?) { From ec3ac2dea5fb36590cee7f78013e38770c7a7222 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Fri, 26 Jul 2019 22:56:57 +0200 Subject: [PATCH 63/99] refactor(playlist): use bidirectional binds for shuffle and random --- src/main/xerus/monstercat/api/Player.kt | 6 ++---- src/main/xerus/monstercat/api/Playlist.kt | 14 +++++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 2abf442..463b089 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -191,12 +191,10 @@ object Player: FadingHBox(true, targetHeight = 25) { } private val shuffleButton = ToggleButton().id("shuffle") .tooltip("Shuffle") - .onClick { Playlist.shuffle = isSelected } - .apply { isSelected = Playlist.shuffle } + .apply { selectedProperty().bindBidirectional(Playlist.shuffle) } private val repeatButton = ToggleButton().id("repeat") .tooltip("Repeat all") - .onClick { Playlist.repeat = isSelected } - .apply { isSelected = Playlist.repeat } + .apply { selectedProperty().bindBidirectional(Playlist.repeat) } private val skipbackButton = buttonWithId("skipback") { playPrev() } .tooltip("Previous") private val skipButton = buttonWithId("skip") { playNext() } diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 3580e89..3544936 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -1,5 +1,6 @@ package xerus.monstercat.api +import javafx.beans.property.SimpleBooleanProperty import javafx.collections.FXCollections import javafx.collections.ObservableList import mu.KotlinLogging @@ -7,7 +8,6 @@ import xerus.ktutil.javafx.properties.SimpleObservable import xerus.ktutil.javafx.properties.bindSoft import xerus.monstercat.api.response.Track import java.util.* -import kotlin.collections.ArrayList object Playlist { val logger = KotlinLogging.logger { } @@ -19,9 +19,9 @@ object Playlist { tracks.indexOf(Player.activeTrack.value).takeUnless { i -> i == -1 } }, Player.activeTrack, tracks) } - - var repeat = false - var shuffle = false + + val repeat = SimpleBooleanProperty(false) + val shuffle = SimpleBooleanProperty(false) operator fun get(index: Int): Track? = tracks.getOrNull(index) @@ -35,8 +35,8 @@ object Playlist { } ?: currentIndex.value?.let { get(it - 1) } fun getNext() = when { - shuffle -> nextSongRandom() - repeat && (nextSong() == null) -> tracks[0] + shuffle.value -> nextSongRandom() + repeat.value && (nextSong() == null) -> tracks[0] else -> nextSong() } @@ -74,7 +74,7 @@ object Playlist { return when { cur == null -> tracks.firstOrNull() cur + 1 < tracks.size -> tracks[cur + 1] - repeat -> tracks.firstOrNull() + repeat.value -> tracks.firstOrNull() else -> return null } } From c2e4baef3142335f7c88f701e51baa7794c9f78e Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Fri, 26 Jul 2019 23:02:11 +0200 Subject: [PATCH 64/99] refactor(playlist): change null check --- src/main/xerus/monstercat/api/Playlist.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 3544936..7e0d6f9 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -51,8 +51,7 @@ object Playlist { } fun removeAt(index: Int?) { - if (index != null) tracks.removeAt(index) - else tracks.removeAt(tracks.size - 1) + tracks.removeAt(index ?: tracks.size - 1) } fun clear(){ From c5a85334538048ec958262df67af3a391238970c Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Fri, 26 Jul 2019 23:08:08 +0200 Subject: [PATCH 65/99] refactor(playlist): make addAll more efficient and less bulky --- src/main/xerus/monstercat/api/Playlist.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 7e0d6f9..c120df7 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -79,10 +79,10 @@ object Playlist { } fun addAll(tracks: ArrayList, asNext: Boolean = false) { - if (asNext) tracks.reverse() - tracks.forEach { track -> - if (asNext) addNext(track) - else add(track) - } + tracks.removeAll(tracks) + if (asNext) + tracks.addAll(currentIndex.value?.let { it + 1 } ?: 0, tracks) + else + tracks.addAll(tracks) } } \ No newline at end of file From 13492d640e1453209e1644de412c43cfcd8e8792 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Mon, 29 Jul 2019 11:55:51 +0200 Subject: [PATCH 66/99] refactor(playlist-api): remove useless (?) trimming for playlist object --- src/main/xerus/monstercat/api/APIConnection.kt | 2 +- src/main/xerus/monstercat/api/response/ConnectPlaylist.kt | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index 3a8fdbb..5246aec 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -74,7 +74,7 @@ class APIConnection(vararg path: String) : HTTPQuery() { parseJSON(TrackResponse::class.java)?.results fun getPlaylists()= - parseJSON(PlaylistResponse::class.java)?.results?.map { it.init() } + parseJSON(PlaylistResponse::class.java)?.results private var httpRequest: HttpUriRequest? = null /** Aborts this connection and thus terminates the InputStream if active */ diff --git a/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt b/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt index 01078e9..0db252d 100644 --- a/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt +++ b/src/main/xerus/monstercat/api/response/ConnectPlaylist.kt @@ -9,10 +9,4 @@ data class ConnectPlaylist( @Key var deleted: Boolean = false, @Key var tracks: List = arrayListOf() -) { - fun init(): ConnectPlaylist { - name = name.trim() - return this - } - -} \ No newline at end of file +) \ No newline at end of file From 820900bb473c8615c2509f1a05e8e5ea2168feeb Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Mon, 29 Jul 2019 12:01:26 +0200 Subject: [PATCH 67/99] refactor(playlist-api): use helper function TableColumn --- src/main/xerus/monstercat/api/Playlist.kt | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 26f61ac..ff26691 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -241,17 +241,9 @@ object PlaylistManager { } connectTable.apply { - columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY - columns.addAll(TableColumn("Name").apply { - cellValueFactory = Callback, ObservableValue> { p -> - ImmutableObservable(p.value.name) - } - }, TableColumn("Size").apply { - cellValueFactory = Callback, ObservableValue> { p -> - ImmutableObservable(p.value.tracks.size.toString()) - } - }) + columns.addAll(TableColumn("Name") { it.value.name }, + TableColumn("Size") { it.value.tracks.size.toString() }) items = playlists From 5936a79a167cac92eb6ab069a0be59a7525d45b7 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Mon, 29 Jul 2019 12:04:23 +0200 Subject: [PATCH 68/99] fix(playlist-api): correct grammar --- src/main/xerus/monstercat/api/Playlist.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index ff26691..b097bc4 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -157,7 +157,7 @@ object PlaylistManager { onFx { subStage.close(); runAfter.invoke() } }catch (e: Exception){ onFx { - monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlist were found at ${textField.text}.") + monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlists were found at ${textField.text}.") } } } From 587ecea842105969f76a66c0ff4a215b4eca12f4 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Mon, 29 Jul 2019 12:06:42 +0200 Subject: [PATCH 69/99] style(playlist-api): group playlist manager init code --- src/main/xerus/monstercat/api/Playlist.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index b097bc4..eaaf517 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -244,13 +244,12 @@ object PlaylistManager { columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY columns.addAll(TableColumn("Name") { it.value.name }, TableColumn("Size") { it.value.tracks.size.toString() }) - items = playlists + updatePlaylists() selectionModel.selectionMode = SelectionMode.SINGLE - setOnMouseClicked { if (it.button == MouseButton.PRIMARY && it.clickCount == 2) load() } - + val publicMenuItem = CheckMenuItem("Public", { if (connectTable.selectionModel.selectedItem != null) { GlobalScope.async { @@ -261,8 +260,6 @@ object PlaylistManager { }) contextMenu = ContextMenu(publicMenuItem, SeparatorMenuItem(), MenuItem("Save into") { replace { updatePlaylists() }; }, MenuItem("Rename playlist") { rename { updatePlaylists() } }, MenuItem("Delete playlist") { delete { updatePlaylists() } }) contextMenu.setOnShown { publicMenuItem.isSelected = selectionModel.selectedItem?.public ?: false } - - updatePlaylists() } parent.add(Label("Tip : You can right-click a playlist to edit it without the window closing each time !")) From 0f6dd4f179479101a8ea5a094d612b9ef62cfec0 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Mon, 29 Jul 2019 12:09:40 +0200 Subject: [PATCH 70/99] refactor(playlist-api): use launch instead of async Coroutines --- src/main/xerus/monstercat/api/Playlist.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index eaaf517..24f5304 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -11,6 +11,7 @@ import javafx.stage.Modality import javafx.util.Callback import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async +import kotlinx.coroutines.launch import mu.KotlinLogging import xerus.ktutil.javafx.* import xerus.ktutil.javafx.properties.ImmutableObservable @@ -136,7 +137,7 @@ object PlaylistManager { // Common playlist functions fun load(runAfter: () -> Unit = {}) { if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.async { + GlobalScope.launch { loadPlaylist(APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id, "tracks")) onFx { runAfter.invoke() } } @@ -151,7 +152,7 @@ object PlaylistManager { subParent.addRow(createButton("Load"){ val playlistId = textField.text.substringAfterLast("/") if (playlistId.length == 24){ - GlobalScope.async { + GlobalScope.launch { try { loadPlaylist(APIConnection("api", "playlist", playlistId, "tracks")) onFx { subStage.close(); runAfter.invoke() } @@ -171,7 +172,7 @@ object PlaylistManager { } fun replace(runAfter: () -> Unit = {}) { if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.async { + GlobalScope.launch { APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, tracks = Playlist.tracks) onFx { runAfter.invoke() } } @@ -179,7 +180,7 @@ object PlaylistManager { } fun delete(runAfter: () -> Unit = {}) { if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.async { + GlobalScope.launch { APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, deleted = true) onFx { runAfter.invoke() } } @@ -193,7 +194,7 @@ object PlaylistManager { val publicTick = CheckBox("Public") subParent.children.addAll(textField, publicTick) subParent.addRow(createButton("Create"){ - GlobalScope.async { + GlobalScope.launch { APIConnection.createPlaylist(textField.text.let { if (it.isBlank()) "New Playlist" else it }, Playlist.tracks, publicTick.isSelected) onFx { subStage.close(); runAfter.invoke() } } @@ -210,7 +211,7 @@ object PlaylistManager { subParent.add(textField) subParent.addRow(createButton("Rename"){ if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.async { + GlobalScope.launch { APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, name = textField.text.let { if (it.isBlank()) "Unnamed" else it }) onFx { subStage.close(); runAfter.invoke() } } @@ -228,7 +229,7 @@ object PlaylistManager { connectTable.placeholder = Label("Please connect using connect.sid in the downloader tab.") }else{ connectTable.placeholder = Label("Loading...") - GlobalScope.async { + GlobalScope.launch { val results = connection.getPlaylists() if (results != null && results.isNotEmpty()) playlists.addAll(results) @@ -252,7 +253,7 @@ object PlaylistManager { val publicMenuItem = CheckMenuItem("Public", { if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.async { + GlobalScope.launch { APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, public = it) onFx { updatePlaylists() } } From 335b23e338886d68ad78d2ec64d6b79b7e999a88 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Mon, 29 Jul 2019 12:21:52 +0200 Subject: [PATCH 71/99] refactor(playlist-api): add logging for cache matching errors --- src/main/xerus/monstercat/api/Playlist.kt | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 24f5304..d9dba0e 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -1,6 +1,5 @@ package xerus.monstercat.api -import javafx.beans.value.ObservableValue import javafx.collections.FXCollections import javafx.collections.ObservableList import javafx.scene.control.* @@ -8,13 +7,10 @@ import javafx.scene.input.MouseButton import javafx.scene.layout.VBox import javafx.scene.media.MediaPlayer import javafx.stage.Modality -import javafx.util.Callback import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async import kotlinx.coroutines.launch import mu.KotlinLogging import xerus.ktutil.javafx.* -import xerus.ktutil.javafx.properties.ImmutableObservable import xerus.ktutil.javafx.properties.SimpleObservable import xerus.ktutil.javafx.properties.bindSoft import xerus.ktutil.javafx.ui.App @@ -23,9 +19,9 @@ import xerus.monstercat.api.response.Track import xerus.monstercat.monsterUtilities import java.util.* +private val logger = KotlinLogging.logger {} + object Playlist { - val logger = KotlinLogging.logger { } - val tracks: ObservableList = FXCollections.observableArrayList() val history = ArrayDeque() val currentIndex = SimpleObservable(null).apply { @@ -108,13 +104,13 @@ object PlaylistManager { suspend fun loadPlaylist(apiConnection: APIConnection){ val tracks = apiConnection.getTracks() tracks?.forEachIndexed { index, track -> - val found = Cache.getTracks().find { - it.id == track.id - } + val found = Cache.getTracks().find { it.id == track.id } if (found != null) tracks[index] = found - else + else { tracks.removeAt(index) + logger.error("Skipped track ${track.artistsTitle} - ${track.title} with id ${track.id}: Not found in the cache.") + } } if (tracks != null && tracks.isNotEmpty()) { if (Player.player?.status != MediaPlayer.Status.DISPOSED) From 508576e9ed96d9e2b820a44751f9b5fdf33dfd49 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 30 Jul 2019 16:21:43 +0200 Subject: [PATCH 72/99] refactor(playlist): change next track getter functions names --- src/main/xerus/monstercat/api/Playlist.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index c120df7..636fd0f 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -35,9 +35,9 @@ object Playlist { } ?: currentIndex.value?.let { get(it - 1) } fun getNext() = when { - shuffle.value -> nextSongRandom() - repeat.value && (nextSong() == null) -> tracks[0] - else -> nextSong() + shuffle.value -> getNextTrackRandom() + repeat.value && (getNextTrack() == null) -> tracks[0] + else -> getNextTrack() } fun addNext(track: Track) { @@ -64,11 +64,11 @@ object Playlist { tracks.setAll(playlist) } - fun nextSongRandom(): Track { + fun getNextTrackRandom(): Track { val index = (Math.random() * tracks.size).toInt() - return if(index == currentIndex.value && tracks.size > 1) nextSongRandom() else tracks[index] + return if(index == currentIndex.value && tracks.size > 1) getNextTrackRandom() else tracks[index] } - fun nextSong(): Track? { + fun getNextTrack(): Track? { val cur = currentIndex.value return when { cur == null -> tracks.firstOrNull() From 7841047d9ee9eddda63760896916bcc947d1f8ea Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 30 Jul 2019 16:56:25 +0200 Subject: [PATCH 73/99] feat(player): disable next and previous buttons when not applicable --- src/main/xerus/monstercat/api/Player.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 463b089..2cf3a2a 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -197,8 +197,23 @@ object Player: FadingHBox(true, targetHeight = 25) { .apply { selectedProperty().bindBidirectional(Playlist.repeat) } private val skipbackButton = buttonWithId("skipback") { playPrev() } .tooltip("Previous") + .apply { Playlist.currentIndex.addListener { _, _, newValue -> + val disable = (newValue == 0) + isDisable = disable + isVisible = !disable + } } private val skipButton = buttonWithId("skip") { playNext() } .tooltip("Next") + .apply { + fun disable(index: Int? = Playlist.currentIndex.value, repeat: Boolean? = Playlist.repeat.value, random: Boolean? = Playlist.shuffle.value) { + val disable = index == Playlist.tracks.lastIndex && repeat == false && random == false + isDisable = disable + isVisible = !disable + } + Playlist.currentIndex.addListener { _, _, newValue -> disable(index = newValue) } + Playlist.repeat.addListener { _, _, newValue -> disable(repeat = newValue)} + Playlist.shuffle.addListener { _, _, newValue -> disable(random = newValue)} + } private var coverUrl: String? = null private fun playing(text: String) { From 589db1aa4798a27dec712f4221c404cfd4a229f0 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Wed, 31 Jul 2019 19:48:16 +0200 Subject: [PATCH 74/99] fix(playlist-api): close dialog only after playlist is fully loaded --- src/main/xerus/monstercat/api/Playlist.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 6f15350..30251d3 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -260,7 +260,7 @@ object PlaylistManager { } parent.add(Label("Tip : You can right-click a playlist to edit it without the window closing each time !")) - parent.addRow(createButton("Load"){ load(); stage.close() }, createButton("From URL..."){ loadUrl { stage.close() } }, createButton("Save into selected"){ replace { stage.close() } }, createButton("Save as new..."){ new { stage.close() } }) + parent.addRow(createButton("Load"){ load { stage.close() } }, createButton("From URL..."){ loadUrl { stage.close() } }, createButton("Save into selected"){ replace { stage.close() } }, createButton("Save as new..."){ new { stage.close() } }) parent.fill(connectTable, 0) stage.show() } From cd56c649088e892ef913e08226067e0767312aa2 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Thu, 1 Aug 2019 18:21:05 +0200 Subject: [PATCH 75/99] refactor(playlist-api): use Job to invoke code on substage completion --- src/main/xerus/monstercat/api/Playlist.kt | 99 ++++++++++++++--------- 1 file changed, 63 insertions(+), 36 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 30251d3..0e9260d 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -8,9 +8,10 @@ import javafx.scene.input.MouseButton import javafx.scene.layout.VBox import javafx.scene.media.MediaPlayer import javafx.stage.Modality -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import mu.KotlinLogging +import xerus.ktutil.ifNotNull +import xerus.ktutil.ifNull import xerus.ktutil.javafx.* import xerus.ktutil.javafx.properties.SimpleObservable import xerus.ktutil.javafx.properties.bindSoft @@ -131,91 +132,108 @@ object PlaylistManager { val connectTable = TableView() // Common playlist functions - fun load(runAfter: () -> Unit = {}) { + fun load(): Job? { if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.launch { + return GlobalScope.launch { loadPlaylist(APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id, "tracks")) - onFx { runAfter.invoke() } } } + return null } - fun loadUrl(runAfter: () -> Unit = {}) { + fun loadUrl(): Job { val subParent = VBox() val subStage = stage.createStage("Load from URL", subParent) subStage.initModality(Modality.WINDOW_MODAL) val textField = TextField().apply { promptText = "URL" } subParent.add(textField) - subParent.addRow(createButton("Load"){ - val playlistId = textField.text.substringAfterLast("/") - if (playlistId.length == 24){ - GlobalScope.launch { - try { - loadPlaylist(APIConnection("api", "playlist", playlistId, "tracks")) - onFx { subStage.close(); runAfter.invoke() } - }catch (e: Exception){ - onFx { - monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlists were found at ${textField.text}.") - } - } + + var playlistId: String? = null + val job = GlobalScope.launch(start = CoroutineStart.LAZY) { + try { + loadPlaylist(APIConnection("api", "playlist", playlistId!!, "tracks")) + onFx { subStage.close() } + }catch (e: Exception){ // FIXME : This breaks everything; if we get in the catch, the error message will show, but job.invokeOnComplete will still work and everything will be closed. + onFx { + monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlists were found at ${textField.text}.") } + this.cancel() + } + } + + subParent.addRow(createButton("Load"){ + playlistId = textField.text.substringAfterLast("/") + if (playlistId!!.length == 24){ + job.start() }else{ monsterUtilities.showAlert(Alert.AlertType.WARNING, "Playlist URL invalid", content = "${textField.text} is not a valid URL.") } }, createButton("Cancel"){ subStage.close() + job.cancel() }) subStage.show() + return job } - fun replace(runAfter: () -> Unit = {}) { + fun replace(): Job? { if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.launch { + return GlobalScope.launch { APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, tracks = Playlist.tracks) - onFx { runAfter.invoke() } } } + return null } - fun delete(runAfter: () -> Unit = {}) { + fun delete(): Job? { if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.launch { + return GlobalScope.launch { APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, deleted = true) - onFx { runAfter.invoke() } } } + return null } - fun new(runAfter: () -> Unit = {}) { + fun new(): Job { val subParent = VBox() val subStage = stage.createStage("New Playlist", subParent) subStage.initModality(Modality.WINDOW_MODAL) val textField = TextField().apply { promptText = "Name" } val publicTick = CheckBox("Public") subParent.children.addAll(textField, publicTick) + + val job = GlobalScope.launch(start = CoroutineStart.LAZY) { + APIConnection.createPlaylist(textField.text.let { if (it.isBlank()) "New Playlist" else it }, Playlist.tracks, publicTick.isSelected) + onFx { subStage.close() } + } + subParent.addRow(createButton("Create"){ - GlobalScope.launch { - APIConnection.createPlaylist(textField.text.let { if (it.isBlank()) "New Playlist" else it }, Playlist.tracks, publicTick.isSelected) - onFx { subStage.close(); runAfter.invoke() } - } + job.start() }, createButton("Cancel"){ subStage.close() + job.cancel() }) subStage.show() + return job } - fun rename(runAfter: () -> Unit = {}) { + fun rename(): Job { val subParent = VBox() val subStage = stage.createStage("Rename Playlist", subParent) subStage.initModality(Modality.WINDOW_MODAL) val textField = TextField().apply { promptText = "Name" } subParent.add(textField) + + val job = GlobalScope.launch(start = CoroutineStart.LAZY) { + APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, name = textField.text.let { if (it.isBlank()) "Unnamed" else it }) + onFx { subStage.close() } + } + subParent.addRow(createButton("Rename"){ if (connectTable.selectionModel.selectedItem != null) { - GlobalScope.launch { - APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, name = textField.text.let { if (it.isBlank()) "Unnamed" else it }) - onFx { subStage.close(); runAfter.invoke() } - } + job.start() } }, createButton("Cancel"){ subStage.close() + job.cancel() }) subStage.show() + return job } val playlists = FXCollections.observableArrayList() @@ -255,12 +273,21 @@ object PlaylistManager { } } }) - contextMenu = ContextMenu(publicMenuItem, SeparatorMenuItem(), MenuItem("Save into") { replace { updatePlaylists() }; }, MenuItem("Rename playlist") { rename { updatePlaylists() } }, MenuItem("Delete playlist") { delete { updatePlaylists() } }) + contextMenu = ContextMenu( + publicMenuItem, + SeparatorMenuItem(), + MenuItem("Save into") { replace()?.invokeOnCompletion { onFx { updatePlaylists() } } }, + MenuItem("Rename playlist") { rename().invokeOnCompletion { it.ifNull { onFx { updatePlaylists() } } } }, + MenuItem("Delete playlist") { delete()?.invokeOnCompletion { onFx { updatePlaylists() } } }) contextMenu.setOnShown { publicMenuItem.isSelected = selectionModel.selectedItem?.public ?: false } } parent.add(Label("Tip : You can right-click a playlist to edit it without the window closing each time !")) - parent.addRow(createButton("Load"){ load { stage.close() } }, createButton("From URL..."){ loadUrl { stage.close() } }, createButton("Save into selected"){ replace { stage.close() } }, createButton("Save as new..."){ new { stage.close() } }) + parent.addRow( + createButton("Load"){ load()?.invokeOnCompletion { onFx { stage.close() } } }, + createButton("From URL..."){ loadUrl().invokeOnCompletion { it.ifNull { onFx { stage.close() } } } }, + createButton("Save into selected"){ replace()?.invokeOnCompletion { onFx { stage.close() } } }, + createButton("Save as new..."){ new().invokeOnCompletion { it.ifNull { onFx { stage.close() } } } }) parent.fill(connectTable, 0) stage.show() } From 031033a9559fba8e1d6e68176f91ea202919bd6c Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Thu, 1 Aug 2019 18:43:06 +0200 Subject: [PATCH 76/99] feat(playlist-api): disable buttons, show "Loading" when waiting for API --- src/main/xerus/monstercat/api/Playlist.kt | 79 ++++++++++++----------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 0e9260d..d8abd12 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -132,14 +132,7 @@ object PlaylistManager { val connectTable = TableView() // Common playlist functions - fun load(): Job? { - if (connectTable.selectionModel.selectedItem != null) { - return GlobalScope.launch { - loadPlaylist(APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id, "tracks")) - } - } - return null - } + fun load() = GlobalScope.launch { loadPlaylist(APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id, "tracks")) } fun loadUrl(): Job { val subParent = VBox() val subStage = stage.createStage("Load from URL", subParent) @@ -164,6 +157,10 @@ object PlaylistManager { playlistId = textField.text.substringAfterLast("/") if (playlistId!!.length == 24){ job.start() + (it.source as Button).let { button -> + button.isDisable = true + button.text = "Loading..." + } }else{ monsterUtilities.showAlert(Alert.AlertType.WARNING, "Playlist URL invalid", content = "${textField.text} is not a valid URL.") } @@ -174,22 +171,8 @@ object PlaylistManager { subStage.show() return job } - fun replace(): Job? { - if (connectTable.selectionModel.selectedItem != null) { - return GlobalScope.launch { - APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, tracks = Playlist.tracks) - } - } - return null - } - fun delete(): Job? { - if (connectTable.selectionModel.selectedItem != null) { - return GlobalScope.launch { - APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, deleted = true) - } - } - return null - } + fun replace() = GlobalScope.launch { APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, tracks = Playlist.tracks) } + fun delete() = GlobalScope.launch { APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, deleted = true) } fun new(): Job { val subParent = VBox() val subStage = stage.createStage("New Playlist", subParent) @@ -205,6 +188,10 @@ object PlaylistManager { subParent.addRow(createButton("Create"){ job.start() + (it.source as Button).let { button -> + button.isDisable = true + button.text = "Loading..." + } }, createButton("Cancel"){ subStage.close() job.cancel() @@ -225,8 +212,10 @@ object PlaylistManager { } subParent.addRow(createButton("Rename"){ - if (connectTable.selectionModel.selectedItem != null) { - job.start() + job.start() + (it.source as Button).let { button -> + button.isDisable = true + button.text = "Loading..." } }, createButton("Cancel"){ subStage.close() @@ -274,20 +263,38 @@ object PlaylistManager { } }) contextMenu = ContextMenu( - publicMenuItem, - SeparatorMenuItem(), - MenuItem("Save into") { replace()?.invokeOnCompletion { onFx { updatePlaylists() } } }, - MenuItem("Rename playlist") { rename().invokeOnCompletion { it.ifNull { onFx { updatePlaylists() } } } }, - MenuItem("Delete playlist") { delete()?.invokeOnCompletion { onFx { updatePlaylists() } } }) - contextMenu.setOnShown { publicMenuItem.isSelected = selectionModel.selectedItem?.public ?: false } + publicMenuItem, + SeparatorMenuItem(), + MenuItem("Save into") { replace().invokeOnCompletion { onFx { updatePlaylists() } } }, + MenuItem("Rename playlist") { rename().invokeOnCompletion { it.ifNull { onFx { updatePlaylists() } } } }, + MenuItem("Delete playlist") { delete().invokeOnCompletion { onFx { updatePlaylists() } } }) + contextMenu.setOnShown { + contextMenu.items.forEach { it.isDisable = connectTable.selectionModel.selectedItem == null } + publicMenuItem.isSelected = selectionModel.selectedItem?.public ?: false + } } parent.add(Label("Tip : You can right-click a playlist to edit it without the window closing each time !")) parent.addRow( - createButton("Load"){ load()?.invokeOnCompletion { onFx { stage.close() } } }, - createButton("From URL..."){ loadUrl().invokeOnCompletion { it.ifNull { onFx { stage.close() } } } }, - createButton("Save into selected"){ replace()?.invokeOnCompletion { onFx { stage.close() } } }, - createButton("Save as new..."){ new().invokeOnCompletion { it.ifNull { onFx { stage.close() } } } }) + createButton("Load"){ + connectTable.selectionModel.selectedItem ?: return@createButton + load().invokeOnCompletion { onFx { stage.close() } } + (it.source as Button).let { button -> + button.parent.isDisable = true + button.text = "Loading..." + } + }, + createButton("From URL..."){ loadUrl().invokeOnCompletion { it.ifNull { onFx { stage.close() } } } }, + createButton("Save into selected"){ + connectTable.selectionModel.selectedItem ?: return@createButton + replace().invokeOnCompletion { onFx { stage.close() } } + (it.source as Button).let { button -> + button.parent.isDisable = true + button.text = "Loading..." + } + }, + createButton("Save as new..."){ new().invokeOnCompletion { it.ifNull { onFx { stage.close() } } } }, + createButton("Cancel"){ stage.close() }) parent.fill(connectTable, 0) stage.show() } From d84109a9c4fdf432f88e85cb1c3e7fe0e6d8edf8 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Fri, 2 Aug 2019 15:47:06 +0200 Subject: [PATCH 77/99] refactor: use when for Mouse and Keyboard events --- src/main/xerus/monstercat/tabs/TabCatalog.kt | 17 ++++++++--------- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 19 ++++++++----------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabCatalog.kt b/src/main/xerus/monstercat/tabs/TabCatalog.kt index 04d5a01..967086f 100644 --- a/src/main/xerus/monstercat/tabs/TabCatalog.kt +++ b/src/main/xerus/monstercat/tabs/TabCatalog.kt @@ -71,17 +71,16 @@ class TabCatalog: TableTab() { } } table.setOnMouseClicked { me -> - if(me.clickCount == 2 && me.button == MouseButton.PRIMARY) { - playTracks(false) - }else if(me.button == MouseButton.MIDDLE){ - playTracks(true) + when { + me.clickCount == 2 && me.button == MouseButton.PRIMARY -> playTracks(false) + me.button == MouseButton.MIDDLE -> playTracks(true) } } - table.setOnKeyPressed { - if (it.code == KeyCode.ENTER){ - playTracks(false) - }else if(it.code == KeyCode.PLUS || it.code == KeyCode.ADD){ - playTracks(true) + table.setOnKeyPressed { ke -> + when (ke.code) { + KeyCode.ENTER -> playTracks(false) + KeyCode.PLUS, KeyCode.ADD -> playTracks(true) + else -> return@setOnKeyPressed } } diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 0df337e..c7fa9d8 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -25,20 +25,17 @@ class TabPlaylist : VTab() { fun playNextPlaylist() = Playlist.addNext(selectedTrack) setOnMouseClicked { me -> - if (me.button == MouseButton.PRIMARY && me.clickCount == 2) { - playFromPlaylist() - } - if (me.button == MouseButton.MIDDLE && me.clickCount == 1) { - removeFromPlaylist() + when { + me.button == MouseButton.PRIMARY && me.clickCount == 2 -> playFromPlaylist() + me.button == MouseButton.MIDDLE && me.clickCount == 1 -> removeFromPlaylist() } } setOnKeyPressed { ke -> - if (ke.code == KeyCode.DELETE){ - removeFromPlaylist() - }else if (ke.code == KeyCode.ENTER){ - playFromPlaylist() - }else if (ke.code == KeyCode.ADD || ke.code == KeyCode.PLUS){ - playNextPlaylist() + when(ke.code) { + KeyCode.DELETE -> removeFromPlaylist() + KeyCode.ENTER -> playFromPlaylist() + KeyCode.ADD, KeyCode.PLUS -> playNextPlaylist() + else -> return@setOnKeyPressed } } contextMenu = ContextMenu( From 64116fc9d6fe00ad288d566aa2bb716ab789b246 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Fri, 2 Aug 2019 15:47:23 +0200 Subject: [PATCH 78/99] style(playlist-tab): indent MenuItem properly --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index c7fa9d8..6b8811b 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -39,13 +39,13 @@ class TabPlaylist : VTab() { } } contextMenu = ContextMenu( - MenuItem("Play") { playFromPlaylist() }, - MenuItem("Play Next") { playNextPlaylist() }, - MenuItem("Remove") { removeFromPlaylist() }, - MenuItem("Clear playlist") { - Playlist.clear() - Player.reset() - } + MenuItem("Play") { playFromPlaylist() }, + MenuItem("Play Next") { playNextPlaylist() }, + MenuItem("Remove") { removeFromPlaylist() }, + MenuItem("Clear playlist") { + Playlist.clear() + Player.reset() + } ) // Columns and rows From 0e108aedc082ddbd57506638ac87f7b301a973b0 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Fri, 2 Aug 2019 16:09:43 +0200 Subject: [PATCH 79/99] refactor(player): make skip button listeners smaller --- src/main/xerus/monstercat/api/Player.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 2cf3a2a..303d5b8 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -197,22 +197,20 @@ object Player: FadingHBox(true, targetHeight = 25) { .apply { selectedProperty().bindBidirectional(Playlist.repeat) } private val skipbackButton = buttonWithId("skipback") { playPrev() } .tooltip("Previous") - .apply { Playlist.currentIndex.addListener { _, _, newValue -> - val disable = (newValue == 0) - isDisable = disable - isVisible = !disable + .apply { Playlist.currentIndex.listen { listener -> + isDisable = listener == 0 + isVisible = listener != 0 } } private val skipButton = buttonWithId("skip") { playNext() } .tooltip("Next") .apply { - fun disable(index: Int? = Playlist.currentIndex.value, repeat: Boolean? = Playlist.repeat.value, random: Boolean? = Playlist.shuffle.value) { - val disable = index == Playlist.tracks.lastIndex && repeat == false && random == false + fun disable(disable: Boolean) { isDisable = disable isVisible = !disable } - Playlist.currentIndex.addListener { _, _, newValue -> disable(index = newValue) } - Playlist.repeat.addListener { _, _, newValue -> disable(repeat = newValue)} - Playlist.shuffle.addListener { _, _, newValue -> disable(random = newValue)} + Playlist.currentIndex.listen { listener -> disable(listener == Playlist.tracks.lastIndex) } + Playlist.repeat.listen { listener -> disable(!listener)} + Playlist.shuffle.listen { listener -> disable(!listener)} } private var coverUrl: String? = null From 3be28d6a2cdc930d7fdd893677c787ac1606973b Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 4 Aug 2019 13:26:15 +0200 Subject: [PATCH 80/99] fix(playlist): add this to fix addAll not working addAll was applying to "tracks", not "this.tracks" --- src/main/xerus/monstercat/api/Playlist.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 636fd0f..a8bccca 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -79,10 +79,10 @@ object Playlist { } fun addAll(tracks: ArrayList, asNext: Boolean = false) { - tracks.removeAll(tracks) + this.tracks.removeAll(tracks) if (asNext) - tracks.addAll(currentIndex.value?.let { it + 1 } ?: 0, tracks) + this.tracks.addAll(currentIndex.value?.let { it + 1 } ?: 0, tracks) else - tracks.addAll(tracks) + this.tracks.addAll(tracks) } } \ No newline at end of file From 42f423760763b96a6ffe316bf63a28b6c6c0e8cc Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 4 Aug 2019 13:56:18 +0200 Subject: [PATCH 81/99] fix(player): remove duplicate updateCover --- src/main/xerus/monstercat/api/Player.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 303d5b8..237bb27 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -255,7 +255,6 @@ object Player: FadingHBox(true, targetHeight = 25) { /** Plays this [release], creating an internal playlist when it has multiple Tracks */ fun play(release: Release) { checkFx { showText("Searching for $release") } - updateCover(release.coverUrl) playTracks(release.tracks, 0) } From 5382a924d4972475d5f8e9add73450f808258390 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 4 Aug 2019 14:07:45 +0200 Subject: [PATCH 82/99] style(playlist): reformat code --- src/main/xerus/monstercat/api/Playlist.kt | 85 ++++++++++++----------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index d8abd12..360b245 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -8,9 +8,12 @@ import javafx.scene.input.MouseButton import javafx.scene.layout.VBox import javafx.scene.media.MediaPlayer import javafx.stage.Modality -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch import mu.KotlinLogging -import xerus.ktutil.ifNotNull import xerus.ktutil.ifNull import xerus.ktutil.javafx.* import xerus.ktutil.javafx.properties.SimpleObservable @@ -31,7 +34,7 @@ object Playlist { tracks.indexOf(Player.activeTrack.value).takeUnless { i -> i == -1 } }, Player.activeTrack, tracks) } - + val repeat = SimpleBooleanProperty(false) val shuffle = SimpleBooleanProperty(false) @@ -68,7 +71,7 @@ object Playlist { tracks.removeAt(index ?: tracks.size - 1) } - fun clear(){ + fun clear() { history.clear() tracks.clear() } @@ -82,6 +85,7 @@ object Playlist { val index = (Math.random() * tracks.size).toInt() return if(index == currentIndex.value && tracks.size > 1) getNextTrackRandom() else tracks[index] } + fun getNextTrack(): Track? { val cur = currentIndex.value return when { @@ -94,7 +98,7 @@ object Playlist { fun addAll(tracks: ArrayList, asNext: Boolean = false) { tracks.removeAll(tracks) - if (asNext) + if(asNext) tracks.addAll(currentIndex.value?.let { it + 1 } ?: 0, tracks) else tracks.addAll(tracks) @@ -102,37 +106,38 @@ object Playlist { } object PlaylistManager { - suspend fun loadPlaylist(apiConnection: APIConnection){ + suspend fun loadPlaylist(apiConnection: APIConnection) { val tracks = apiConnection.getTracks() tracks?.forEachIndexed { index, track -> val found = Cache.getTracks().find { it.id == track.id } - if (found != null) + if(found != null) tracks[index] = found else { tracks.removeAt(index) logger.error("Skipped track ${track.artistsTitle} - ${track.title} with id ${track.id}: Not found in the cache.") } } - if (tracks != null && tracks.isNotEmpty()) { - if (Player.player?.status != MediaPlayer.Status.DISPOSED) + if(tracks != null && tracks.isNotEmpty()) { + if(Player.player?.status != MediaPlayer.Status.DISPOSED) Player.reset() Playlist.setTracks(tracks) } } - + /** Opens the playlist manager dialog * Allows to load, save, and manage playlists stored on Monstercat.com */ - fun playlistDialog(){ + fun playlistDialog() { val connection = APIConnection("api", "playlist").fields(ConnectPlaylist::class) - + val parent = VBox() val stage = App.stage.createStage("Monstercat.com Playlists", parent) stage.initModality(Modality.WINDOW_MODAL) val connectTable = TableView() - + // Common playlist functions fun load() = GlobalScope.launch { loadPlaylist(APIConnection("api", "playlist", connectTable.selectionModel.selectedItem.id, "tracks")) } + fun loadUrl(): Job { val subParent = VBox() val subStage = stage.createStage("Load from URL", subParent) @@ -145,7 +150,7 @@ object PlaylistManager { try { loadPlaylist(APIConnection("api", "playlist", playlistId!!, "tracks")) onFx { subStage.close() } - }catch (e: Exception){ // FIXME : This breaks everything; if we get in the catch, the error message will show, but job.invokeOnComplete will still work and everything will be closed. + } catch(e: Exception) { // FIXME : This breaks everything; if we get in the catch, the error message will show, but job.invokeOnComplete will still work and everything will be closed. onFx { monsterUtilities.showAlert(Alert.AlertType.WARNING, "No playlist found", content = "No playlists were found at ${textField.text}.") } @@ -153,24 +158,25 @@ object PlaylistManager { } } - subParent.addRow(createButton("Load"){ + subParent.addRow(createButton("Load") { playlistId = textField.text.substringAfterLast("/") - if (playlistId!!.length == 24){ + if(playlistId!!.length == 24) { job.start() (it.source as Button).let { button -> button.isDisable = true button.text = "Loading..." } - }else{ + } else { monsterUtilities.showAlert(Alert.AlertType.WARNING, "Playlist URL invalid", content = "${textField.text} is not a valid URL.") } - }, createButton("Cancel"){ + }, createButton("Cancel") { subStage.close() job.cancel() }) subStage.show() return job } + fun replace() = GlobalScope.launch { APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, tracks = Playlist.tracks) } fun delete() = GlobalScope.launch { APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, deleted = true) } fun new(): Job { @@ -182,23 +188,24 @@ object PlaylistManager { subParent.children.addAll(textField, publicTick) val job = GlobalScope.launch(start = CoroutineStart.LAZY) { - APIConnection.createPlaylist(textField.text.let { if (it.isBlank()) "New Playlist" else it }, Playlist.tracks, publicTick.isSelected) + APIConnection.createPlaylist(textField.text.let { if(it.isBlank()) "New Playlist" else it }, Playlist.tracks, publicTick.isSelected) onFx { subStage.close() } } - subParent.addRow(createButton("Create"){ + subParent.addRow(createButton("Create") { job.start() (it.source as Button).let { button -> button.isDisable = true button.text = "Loading..." } - }, createButton("Cancel"){ + }, createButton("Cancel") { subStage.close() job.cancel() }) subStage.show() return job } + fun rename(): Job { val subParent = VBox() val subStage = stage.createStage("Rename Playlist", subParent) @@ -207,34 +214,34 @@ object PlaylistManager { subParent.add(textField) val job = GlobalScope.launch(start = CoroutineStart.LAZY) { - APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, name = textField.text.let { if (it.isBlank()) "Unnamed" else it }) + APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, name = textField.text.let { if(it.isBlank()) "Unnamed" else it }) onFx { subStage.close() } } - subParent.addRow(createButton("Rename"){ + subParent.addRow(createButton("Rename") { job.start() (it.source as Button).let { button -> button.isDisable = true button.text = "Loading..." } - }, createButton("Cancel"){ + }, createButton("Cancel") { subStage.close() job.cancel() }) subStage.show() return job } - + val playlists = FXCollections.observableArrayList() fun updatePlaylists() { playlists.clear() - if (APIConnection.connectValidity.value != ConnectValidity.NOGOLD && APIConnection.connectValidity.value != ConnectValidity.GOLD){ + if(APIConnection.connectValidity.value != ConnectValidity.NOGOLD && APIConnection.connectValidity.value != ConnectValidity.GOLD) { connectTable.placeholder = Label("Please connect using connect.sid in the downloader tab.") - }else{ + } else { connectTable.placeholder = Label("Loading...") GlobalScope.launch { val results = connection.getPlaylists() - if (results != null && results.isNotEmpty()) + if(results != null && results.isNotEmpty()) playlists.addAll(results) else onFx { @@ -243,19 +250,19 @@ object PlaylistManager { } } } - + connectTable.apply { columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY columns.addAll(TableColumn("Name") { it.value.name }, - TableColumn("Size") { it.value.tracks.size.toString() }) + TableColumn("Size") { it.value.tracks.size.toString() }) items = playlists updatePlaylists() - + selectionModel.selectionMode = SelectionMode.SINGLE - setOnMouseClicked { if (it.button == MouseButton.PRIMARY && it.clickCount == 2) load() } + setOnMouseClicked { if(it.button == MouseButton.PRIMARY && it.clickCount == 2) load() } val publicMenuItem = CheckMenuItem("Public", { - if (connectTable.selectionModel.selectedItem != null) { + if(connectTable.selectionModel.selectedItem != null) { GlobalScope.launch { APIConnection.editPlaylist(connectTable.selectionModel.selectedItem.id, public = it) onFx { updatePlaylists() } @@ -273,10 +280,10 @@ object PlaylistManager { publicMenuItem.isSelected = selectionModel.selectedItem?.public ?: false } } - + parent.add(Label("Tip : You can right-click a playlist to edit it without the window closing each time !")) parent.addRow( - createButton("Load"){ + createButton("Load") { connectTable.selectionModel.selectedItem ?: return@createButton load().invokeOnCompletion { onFx { stage.close() } } (it.source as Button).let { button -> @@ -284,8 +291,8 @@ object PlaylistManager { button.text = "Loading..." } }, - createButton("From URL..."){ loadUrl().invokeOnCompletion { it.ifNull { onFx { stage.close() } } } }, - createButton("Save into selected"){ + createButton("From URL...") { loadUrl().invokeOnCompletion { it.ifNull { onFx { stage.close() } } } }, + createButton("Save into selected") { connectTable.selectionModel.selectedItem ?: return@createButton replace().invokeOnCompletion { onFx { stage.close() } } (it.source as Button).let { button -> @@ -293,8 +300,8 @@ object PlaylistManager { button.text = "Loading..." } }, - createButton("Save as new..."){ new().invokeOnCompletion { it.ifNull { onFx { stage.close() } } } }, - createButton("Cancel"){ stage.close() }) + createButton("Save as new...") { new().invokeOnCompletion { it.ifNull { onFx { stage.close() } } } }, + createButton("Cancel") { stage.close() }) parent.fill(connectTable, 0) stage.show() } From 5b6ae0ebbb71ee610e8474cd35891528677cac08 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Mon, 5 Aug 2019 13:21:31 +0200 Subject: [PATCH 83/99] refactor(player): make skip buttons listeners even better --- src/main/xerus/monstercat/api/Player.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index 237bb27..f3f96a2 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -1,5 +1,7 @@ package xerus.monstercat.api +import javafx.beans.Observable +import javafx.beans.value.ObservableValue import javafx.event.EventHandler import javafx.geometry.Pos import javafx.scene.control.* @@ -17,6 +19,7 @@ import mu.KotlinLogging import xerus.ktutil.ifNotNull import xerus.ktutil.javafx.* import xerus.ktutil.javafx.properties.SimpleObservable +import xerus.ktutil.javafx.properties.addListener import xerus.ktutil.javafx.properties.dependOn import xerus.ktutil.javafx.properties.listen import xerus.ktutil.javafx.ui.controls.FadingHBox @@ -197,20 +200,18 @@ object Player: FadingHBox(true, targetHeight = 25) { .apply { selectedProperty().bindBidirectional(Playlist.repeat) } private val skipbackButton = buttonWithId("skipback") { playPrev() } .tooltip("Previous") - .apply { Playlist.currentIndex.listen { listener -> - isDisable = listener == 0 - isVisible = listener != 0 + .apply { Playlist.currentIndex.listen { value -> + isDisable = value == 0 + isVisible = value != 0 } } private val skipButton = buttonWithId("skip") { playNext() } .tooltip("Next") .apply { - fun disable(disable: Boolean) { - isDisable = disable - isVisible = !disable + arrayOf(Playlist.currentIndex, Playlist.repeat, Playlist.shuffle).addListener { + val disabled = Playlist.currentIndex.value == Playlist.tracks.lastIndex && !Playlist.repeat.value && !Playlist.shuffle.value + isDisable = disabled + isVisible = !disabled } - Playlist.currentIndex.listen { listener -> disable(listener == Playlist.tracks.lastIndex) } - Playlist.repeat.listen { listener -> disable(!listener)} - Playlist.shuffle.listen { listener -> disable(!listener)} } private var coverUrl: String? = null From a5ee3b8210c30b70bed043a1e9bd1b7604e4e682 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Tue, 6 Aug 2019 12:38:50 +0200 Subject: [PATCH 84/99] refactor(playlist): use lastIndex instead of (size - 1) Co-Authored-By: Janek <27jf@web.de> --- src/main/xerus/monstercat/api/Playlist.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index a8bccca..7199fd8 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -72,7 +72,7 @@ object Playlist { val cur = currentIndex.value return when { cur == null -> tracks.firstOrNull() - cur + 1 < tracks.size -> tracks[cur + 1] + cur < tracks.lastIndex -> tracks[cur + 1] repeat.value -> tracks.firstOrNull() else -> return null } @@ -85,4 +85,4 @@ object Playlist { else this.tracks.addAll(tracks) } -} \ No newline at end of file +} From b2ac140c6b05a632db5317ce9a9eb3b9f59477cb Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 6 Aug 2019 16:22:08 +0200 Subject: [PATCH 85/99] refactor(playlist): replace recursive next song random function --- src/main/xerus/monstercat/api/Playlist.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 7199fd8..dcecb13 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -65,8 +65,8 @@ object Playlist { } fun getNextTrackRandom(): Track { - val index = (Math.random() * tracks.size).toInt() - return if(index == currentIndex.value && tracks.size > 1) getNextTrackRandom() else tracks[index] + val index = (Math.random() * (tracks.size - 1)).toInt().let { if (it >= currentIndex.value!!) it + 1 else it }.takeUnless { it >= tracks.size } ?: 0 + return tracks[index] } fun getNextTrack(): Track? { val cur = currentIndex.value From a6e0ae4813ac9e88a69c7d641ab6233e8d955e4a Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 6 Aug 2019 16:27:14 +0200 Subject: [PATCH 86/99] refactor(player): remove disabled property on prev / next buttons * isVisible also disables mouse events, isDisable is redundant. --- src/main/xerus/monstercat/api/Player.kt | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index f3f96a2..de78d31 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -1,7 +1,6 @@ package xerus.monstercat.api import javafx.beans.Observable -import javafx.beans.value.ObservableValue import javafx.event.EventHandler import javafx.geometry.Pos import javafx.scene.control.* @@ -200,18 +199,12 @@ object Player: FadingHBox(true, targetHeight = 25) { .apply { selectedProperty().bindBidirectional(Playlist.repeat) } private val skipbackButton = buttonWithId("skipback") { playPrev() } .tooltip("Previous") - .apply { Playlist.currentIndex.listen { value -> - isDisable = value == 0 - isVisible = value != 0 - } } + .apply { Playlist.currentIndex.listen { value -> isVisible = value != 0 } } private val skipButton = buttonWithId("skip") { playNext() } .tooltip("Next") .apply { arrayOf(Playlist.currentIndex, Playlist.repeat, Playlist.shuffle).addListener { - val disabled = Playlist.currentIndex.value == Playlist.tracks.lastIndex && !Playlist.repeat.value && !Playlist.shuffle.value - isDisable = disabled - isVisible = !disabled - } + isVisible = Playlist.currentIndex.value != Playlist.tracks.lastIndex || Playlist.repeat.value || Playlist.shuffle.value } } private var coverUrl: String? = null From b575ca968c79c94f2527ce9e5395b4fee93c225d Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 6 Aug 2019 16:33:32 +0200 Subject: [PATCH 87/99] style: reformat code --- .../xerus/monstercat/api/APIConnection.kt | 15 +++-- src/main/xerus/monstercat/api/Player.kt | 59 +++++++++--------- src/main/xerus/monstercat/api/Playlist.kt | 10 +-- src/main/xerus/monstercat/tabs/TabCatalog.kt | 62 ++++++++++--------- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 27 ++++---- 5 files changed, 93 insertions(+), 80 deletions(-) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index b8d5603..c6fefa8 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -9,7 +9,10 @@ import mu.KotlinLogging import org.apache.http.HttpResponse import org.apache.http.client.config.CookieSpecs import org.apache.http.client.config.RequestConfig -import org.apache.http.client.methods.* +import org.apache.http.client.methods.CloseableHttpResponse +import org.apache.http.client.methods.HttpGet +import org.apache.http.client.methods.HttpPost +import org.apache.http.client.methods.HttpUriRequest import org.apache.http.client.protocol.HttpClientContext import org.apache.http.entity.StringEntity import org.apache.http.impl.client.BasicCookieStore @@ -38,7 +41,7 @@ import kotlin.reflect.KClass private val logger = KotlinLogging.logger { } /** eases query creation to the Monstercat API */ -class APIConnection(vararg path: String) : HTTPQuery() { +class APIConnection(vararg path: String): HTTPQuery() { private val path: String = "/" + path.joinToString("/") val uri: URI @@ -74,7 +77,7 @@ class APIConnection(vararg path: String) : HTTPQuery() { /** @return null when the connection fails, else the parsed result */ fun getTracks() = parseJSON(TrackResponse::class.java)?.results - + private var httpRequest: HttpUriRequest? = null /** Aborts this connection and thus terminates the InputStream if active */ fun abort() { @@ -82,7 +85,7 @@ class APIConnection(vararg path: String) : HTTPQuery() { } // Direct Requesting - + fun execute(request: HttpUriRequest, context: HttpClientContext? = null) { httpRequest = request response = executeRequest(request, context) @@ -209,10 +212,10 @@ class APIConnection(vararg path: String) : HTTPQuery() { setHeader("Content-type", "application/json") entity = StringEntity("""{"email":"$username","password":"$password"}""") }, context) - + val code = connection.response?.statusLine?.statusCode logger.trace("Login API (POST) returned response code $code") - if (code !in 200..206) return false + if(code !in 200..206) return false CONNECTSID.value = (context.cookieStore.cookies.find { it.name == "connect.sid" }?.value ?: return false) return true } diff --git a/src/main/xerus/monstercat/api/Player.kt b/src/main/xerus/monstercat/api/Player.kt index de78d31..b2e51bf 100644 --- a/src/main/xerus/monstercat/api/Player.kt +++ b/src/main/xerus/monstercat/api/Player.kt @@ -94,9 +94,9 @@ object Player: FadingHBox(true, targetHeight = 25) { addButton { reset() }.id("back") fill(pos = 0) fill() - if (Playlist.tracks.size < 2){ + if(Playlist.tracks.size < 2) { add(closeButton) - }else{ + } else { add(buttonWithId("skip") { playNext() }).tooltip("Skip") Timer().schedule(TimeUnit.SECONDS.toMillis(5)) { playNext() @@ -176,37 +176,38 @@ object Player: FadingHBox(true, targetHeight = 25) { } private val pauseButton = ToggleButton().id("play-pause") - .tooltip("Pause / Play") - .onClick { if (isSelected) player?.pause() else player?.play() } + .tooltip("Pause / Play") + .onClick { if(isSelected) player?.pause() else player?.play() } private val stopButton = Button().id("stop") - .tooltip("Stop playing") - .onClick { - reset() - Playlist.clear() - } + .tooltip("Stop playing") + .onClick { + reset() + Playlist.clear() + } private val volumeSlider = Slider(0.0, 1.0, Settings.PLAYERVOLUME()) - .scrollable(0.05) - .apply { - prefWidth = 100.0 - valueProperty().listen { updateVolume() } - tooltip = Tooltip("Volume") - } + .scrollable(0.05) + .apply { + prefWidth = 100.0 + valueProperty().listen { updateVolume() } + tooltip = Tooltip("Volume") + } private val shuffleButton = ToggleButton().id("shuffle") - .tooltip("Shuffle") - .apply { selectedProperty().bindBidirectional(Playlist.shuffle) } + .tooltip("Shuffle") + .apply { selectedProperty().bindBidirectional(Playlist.shuffle) } private val repeatButton = ToggleButton().id("repeat") - .tooltip("Repeat all") - .apply { selectedProperty().bindBidirectional(Playlist.repeat) } + .tooltip("Repeat all") + .apply { selectedProperty().bindBidirectional(Playlist.repeat) } private val skipbackButton = buttonWithId("skipback") { playPrev() } - .tooltip("Previous") - .apply { Playlist.currentIndex.listen { value -> isVisible = value != 0 } } + .tooltip("Previous") + .apply { Playlist.currentIndex.listen { value -> isVisible = value != 0 } } private val skipButton = buttonWithId("skip") { playNext() } - .tooltip("Next") - .apply { - arrayOf(Playlist.currentIndex, Playlist.repeat, Playlist.shuffle).addListener { - isVisible = Playlist.currentIndex.value != Playlist.tracks.lastIndex || Playlist.repeat.value || Playlist.shuffle.value } + .tooltip("Next") + .apply { + arrayOf(Playlist.currentIndex, Playlist.repeat, Playlist.shuffle).addListener { + isVisible = Playlist.currentIndex.value != Playlist.tracks.lastIndex || Playlist.repeat.value || Playlist.shuffle.value } - + } + private var coverUrl: String? = null private fun playing(text: String) { onFx { @@ -242,7 +243,7 @@ object Player: FadingHBox(true, targetHeight = 25) { } } - fun playFromPlaylist(index: Int){ + fun playFromPlaylist(index: Int) { Playlist[index].ifNotNull { playTrack(it) } } @@ -258,11 +259,11 @@ object Player: FadingHBox(true, targetHeight = 25) { playFromPlaylist(index) } - fun playNext(){ + fun playNext() { Playlist.getNext()?.let { playTrack(it) } ?: reset() } - fun playPrev(){ + fun playPrev() { Playlist.getPrev().ifNotNull { playTrack(it) } } diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index dcecb13..ae62942 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -19,7 +19,7 @@ object Playlist { tracks.indexOf(Player.activeTrack.value).takeUnless { i -> i == -1 } }, Player.activeTrack, tracks) } - + val repeat = SimpleBooleanProperty(false) val shuffle = SimpleBooleanProperty(false) @@ -54,7 +54,7 @@ object Playlist { tracks.removeAt(index ?: tracks.size - 1) } - fun clear(){ + fun clear() { history.clear() tracks.clear() } @@ -65,9 +65,11 @@ object Playlist { } fun getNextTrackRandom(): Track { - val index = (Math.random() * (tracks.size - 1)).toInt().let { if (it >= currentIndex.value!!) it + 1 else it }.takeUnless { it >= tracks.size } ?: 0 + val index = (Math.random() * (tracks.size - 1)).toInt().let { if(it >= currentIndex.value!!) it + 1 else it }.takeUnless { it >= tracks.size } + ?: 0 return tracks[index] } + fun getNextTrack(): Track? { val cur = currentIndex.value return when { @@ -80,7 +82,7 @@ object Playlist { fun addAll(tracks: ArrayList, asNext: Boolean = false) { this.tracks.removeAll(tracks) - if (asNext) + if(asNext) this.tracks.addAll(currentIndex.value?.let { it + 1 } ?: 0, tracks) else this.tracks.addAll(tracks) diff --git a/src/main/xerus/monstercat/tabs/TabCatalog.kt b/src/main/xerus/monstercat/tabs/TabCatalog.kt index 967086f..7ec8db4 100644 --- a/src/main/xerus/monstercat/tabs/TabCatalog.kt +++ b/src/main/xerus/monstercat/tabs/TabCatalog.kt @@ -10,8 +10,12 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import xerus.ktutil.collections.ArraySet import xerus.ktutil.containsAny -import xerus.ktutil.javafx.* +import xerus.ktutil.javafx.MenuItem +import xerus.ktutil.javafx.TableColumn +import xerus.ktutil.javafx.fill +import xerus.ktutil.javafx.onFx import xerus.ktutil.javafx.properties.listen +import xerus.ktutil.javafx.textWidth import xerus.ktutil.javafx.ui.controls.MultiSearchable import xerus.ktutil.javafx.ui.controls.SearchView import xerus.ktutil.javafx.ui.controls.SearchableColumn @@ -22,11 +26,9 @@ import xerus.monstercat.Settings import xerus.monstercat.api.APIUtils import xerus.monstercat.api.Player import xerus.monstercat.api.Playlist -import xerus.monstercat.monsterUtilities +import xerus.monstercat.api.response.Track import java.time.LocalTime import kotlin.math.absoluteValue -import xerus.ktutil.javafx.MenuItem -import xerus.monstercat.api.response.Track val defaultColumns = arrayOf("Genre", "Artists", "Track", "Length").joinToString(multiSeparator) val availableColumns = arrayOf("ID", "Date", "B", "CC", "E", "Genre", "Subgenres", "Artists", "Track", "Comp", "Length", "BPM", "Key", "Fan Ratings").joinToString(multiSeparator) @@ -60,11 +62,11 @@ class TabCatalog: TableTab() { table.visibleLeafColumns.addListener(ListChangeListener { it.next(); Settings.VISIBLECATALOGCOLUMNS.putMulti(*it.addedSubList.map { it.text }.toTypedArray()) }) - - fun playTracks(add: Boolean){ + + fun playTracks(add: Boolean) { val selected = table.selectionModel.selectedItems ?: return GlobalScope.launch { - if (!add) + if(!add) Player.playTracks(getSongs(selected)) else Playlist.addAll(getSongs(selected)) @@ -77,7 +79,7 @@ class TabCatalog: TableTab() { } } table.setOnKeyPressed { ke -> - when (ke.code) { + when(ke.code) { KeyCode.ENTER -> playTracks(false) KeyCode.PLUS, KeyCode.ADD -> playTracks(true) else -> return@setOnKeyPressed @@ -88,28 +90,28 @@ class TabCatalog: TableTab() { val rightClickMenu = ContextMenu() rightClickMenu.items.addAll( - MenuItem("Play") { - val selected = table.selectionModel.selectedItems - GlobalScope.launch { - Player.playTracks(getSongs(selected)) - } - }, - MenuItem("Add to playlist") { - val selected = table.selectionModel.selectedItems - GlobalScope.launch { - Playlist.addAll(getSongs(selected)) - } - }, - MenuItem("Play next") { - val selected = table.selectionModel.selectedItems - GlobalScope.launch { - Playlist.addAll(getSongs(selected), true) - } - }, - SeparatorMenuItem(), - MenuItem("Select All") { - table.selectionModel.selectAll() + MenuItem("Play") { + val selected = table.selectionModel.selectedItems + GlobalScope.launch { + Player.playTracks(getSongs(selected)) + } + }, + MenuItem("Add to playlist") { + val selected = table.selectionModel.selectedItems + GlobalScope.launch { + Playlist.addAll(getSongs(selected)) } + }, + MenuItem("Play next") { + val selected = table.selectionModel.selectedItems + GlobalScope.launch { + Playlist.addAll(getSongs(selected), true) + } + }, + SeparatorMenuItem(), + MenuItem("Select All") { + table.selectionModel.selectAll() + } ) table.contextMenu = rightClickMenu } @@ -118,7 +120,7 @@ class TabCatalog: TableTab() { val tracklist = arrayListOf() songList.forEach { item -> val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) - if (track != null) tracklist.add(track) + if(track != null) tracklist.add(track) else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") } return tracklist diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 6b8811b..3b0a25a 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -1,6 +1,10 @@ package xerus.monstercat.tabs -import javafx.scene.control.* +import javafx.scene.control.ContextMenu +import javafx.scene.control.Label +import javafx.scene.control.SelectionMode +import javafx.scene.control.TableRow +import javafx.scene.control.TableView import javafx.scene.input.KeyCode import javafx.scene.input.MouseButton import xerus.ktutil.javafx.MenuItem @@ -12,18 +16,19 @@ import xerus.monstercat.api.Playlist import xerus.monstercat.api.response.Track -class TabPlaylist : VTab() { +class TabPlaylist: VTab() { private var table = TableView().apply { // Inherent properties columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY selectionModel.selectionMode = SelectionMode.SINGLE placeholder = Label("Your playlist is empty.") - + // Events fun removeFromPlaylist() = Playlist.removeAt(selectedIndex) + fun playFromPlaylist() = Player.playFromPlaylist(selectedIndex) fun playNextPlaylist() = Playlist.addNext(selectedTrack) - + setOnMouseClicked { me -> when { me.button == MouseButton.PRIMARY && me.clickCount == 2 -> playFromPlaylist() @@ -47,26 +52,26 @@ class TabPlaylist : VTab() { Player.reset() } ) - + // Columns and rows columns.addAll(TableColumn("Artists") { it.value.artistsTitle }, - TableColumn("Title") { it.value.title }) - + TableColumn("Title") { it.value.title }) + setRowFactory { TableRow().apply { Playlist.currentIndex.listen { - style = "-fx-background-color: ${if (index == it) "#1f6601" else "transparent"}" + style = "-fx-background-color: ${if(index == it) "#1f6601" else "transparent"}" } itemProperty().listen { - style = "-fx-background-color: ${if (index == Playlist.currentIndex.value) "#1f6601" else "transparent"}" + style = "-fx-background-color: ${if(index == Playlist.currentIndex.value) "#1f6601" else "transparent"}" } } } } - + init { table.items = Playlist.tracks - fill(table) + fill(table) } private val selectedTrack: Track From f6cbf078412ed27816ddd599e37b446dc1074884 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 6 Aug 2019 16:39:02 +0200 Subject: [PATCH 88/99] refactor(playlist): move addAll if into function call --- src/main/xerus/monstercat/api/Playlist.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index ae62942..70d6f54 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -82,9 +82,6 @@ object Playlist { fun addAll(tracks: ArrayList, asNext: Boolean = false) { this.tracks.removeAll(tracks) - if(asNext) - this.tracks.addAll(currentIndex.value?.let { it + 1 } ?: 0, tracks) - else - this.tracks.addAll(tracks) + this.tracks.addAll(if(asNext) currentIndex.value?.let { it + 1 } ?: 0 else this.tracks.lastIndex, tracks) } } From b1979f3c2a4708413b45632731639246ab953619 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 6 Aug 2019 17:37:40 +0200 Subject: [PATCH 89/99] style: reformat code --- .../xerus/monstercat/api/APIConnection.kt | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index 5246aec..526fca8 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -9,7 +9,11 @@ import mu.KotlinLogging import org.apache.http.HttpResponse import org.apache.http.client.config.CookieSpecs import org.apache.http.client.config.RequestConfig -import org.apache.http.client.methods.* +import org.apache.http.client.methods.CloseableHttpResponse +import org.apache.http.client.methods.HttpGet +import org.apache.http.client.methods.HttpPatch +import org.apache.http.client.methods.HttpPost +import org.apache.http.client.methods.HttpUriRequest import org.apache.http.client.protocol.HttpClientContext import org.apache.http.entity.StringEntity import org.apache.http.impl.client.BasicCookieStore @@ -36,7 +40,7 @@ import kotlin.reflect.KClass private val logger = KotlinLogging.logger { } /** eases query creation to the Monstercat API */ -class APIConnection(vararg path: String) : HTTPQuery() { +class APIConnection(vararg path: String): HTTPQuery() { private val path: String = "/" + path.joinToString("/") val uri: URI @@ -73,9 +77,9 @@ class APIConnection(vararg path: String) : HTTPQuery() { fun getTracks() = parseJSON(TrackResponse::class.java)?.results - fun getPlaylists()= + fun getPlaylists() = parseJSON(PlaylistResponse::class.java)?.results - + private var httpRequest: HttpUriRequest? = null /** Aborts this connection and thus terminates the InputStream if active */ fun abort() { @@ -83,7 +87,7 @@ class APIConnection(vararg path: String) : HTTPQuery() { } // Direct Requesting - + fun execute(request: HttpUriRequest, context: HttpClientContext? = null) { request.setHeader("Accept", "application/json") request.setHeader("Content-type", "application/json") @@ -212,10 +216,10 @@ class APIConnection(vararg path: String) : HTTPQuery() { setHeader("Content-type", "application/json") entity = StringEntity("""{"email":"$username","password":"$password"}""") }, context) - + val code = connection.response?.statusLine?.statusCode logger.trace("Login API (POST) returned response code $code") - if (code !in 200..206) return false + if(code !in 200..206) return false CONNECTSID.value = (context.cookieStore.cookies.find { it.name == "connect.sid" }?.value ?: return false) return true } @@ -223,9 +227,9 @@ class APIConnection(vararg path: String) : HTTPQuery() { fun logout() { CONNECTSID.clear() } - + private fun convertTracklist(tracks: List) = tracks.map { HashMap().apply { this["trackId"] = it.id; this["releaseId"] = it.release.id } } - + fun editPlaylist(id: String, tracks: List? = null, name: String? = null, public: Boolean? = null, deleted: Boolean? = null) { val json = HashMap() tracks.ifNotNull { json["tracks"] = convertTracklist(it) } @@ -239,8 +243,8 @@ class APIConnection(vararg path: String) : HTTPQuery() { } connection.execute(request) } - - fun createPlaylist(name: String, tracks: List, public: Boolean = false){ + + fun createPlaylist(name: String, tracks: List, public: Boolean = false) { val connection = APIConnection("api", "playlist") val request = HttpPost(connection.uri).apply { val json = HashMap() From fa69e2d6b159a0d0fc911108f70e7170c791c8fe Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 6 Aug 2019 18:47:16 +0200 Subject: [PATCH 90/99] fix(playlist): fix addAll returning wrong index is tracklist is empty --- src/main/xerus/monstercat/api/Playlist.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 70d6f54..224be79 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -82,6 +82,6 @@ object Playlist { fun addAll(tracks: ArrayList, asNext: Boolean = false) { this.tracks.removeAll(tracks) - this.tracks.addAll(if(asNext) currentIndex.value?.let { it + 1 } ?: 0 else this.tracks.lastIndex, tracks) + this.tracks.addAll(if(asNext) currentIndex.value?.let { it + 1 } ?: 0 else this.tracks.lastIndex.coerceAtLeast(0), tracks) } } From d98c1f76c4f3f9ddc73d0377b74f032017fd0b99 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Tue, 6 Aug 2019 18:47:38 +0200 Subject: [PATCH 91/99] feat(playlist-tab): allow drag and drop to manually sort the playlist --- src/main/xerus/monstercat/tabs/TabPlaylist.kt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/main/xerus/monstercat/tabs/TabPlaylist.kt b/src/main/xerus/monstercat/tabs/TabPlaylist.kt index 3b0a25a..82e96cf 100644 --- a/src/main/xerus/monstercat/tabs/TabPlaylist.kt +++ b/src/main/xerus/monstercat/tabs/TabPlaylist.kt @@ -5,8 +5,11 @@ import javafx.scene.control.Label import javafx.scene.control.SelectionMode import javafx.scene.control.TableRow import javafx.scene.control.TableView +import javafx.scene.input.ClipboardContent +import javafx.scene.input.DataFormat import javafx.scene.input.KeyCode import javafx.scene.input.MouseButton +import javafx.scene.input.TransferMode import xerus.ktutil.javafx.MenuItem import xerus.ktutil.javafx.TableColumn import xerus.ktutil.javafx.fill @@ -65,6 +68,36 @@ class TabPlaylist: VTab() { itemProperty().listen { style = "-fx-background-color: ${if(index == Playlist.currentIndex.value) "#1f6601" else "transparent"}" } + + val serializedFormat = DataFormat.lookupMimeType("application/x-java-serialized-object") ?: DataFormat("application/x-java-serialized-object") + + setOnDragDetected { + if(!isEmpty) { + val dragboard = startDragAndDrop(TransferMode.MOVE) + dragboard.dragView = snapshot(null, null) + dragboard.setContent(ClipboardContent().apply { put(serializedFormat, index) }) + it.consume() + } + } + + setOnDragOver { + (it.dragboard.getContent(serializedFormat) as Int?)?.let { oldIndex -> + if(index != oldIndex) { + it.acceptTransferModes(*TransferMode.COPY_OR_MOVE) + it.consume() + } + } + } + + setOnDragDropped { + (it.dragboard.getContent(serializedFormat) as Int?)?.let { oldIndex -> + val newIndex = if(isEmpty) Playlist.tracks.size else index + Playlist.tracks.add(newIndex, Playlist.tracks.removeAt(oldIndex)) + this.tableView.selectionModel.select(newIndex) + it.isDropCompleted = true + it.consume() + } + } } } } From c3ab35b0cd83d4d2f4ca6eda36ab57d2e2661c41 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Thu, 8 Aug 2019 19:47:54 +0200 Subject: [PATCH 92/99] refactor(playlist): remove useless lambda argument Co-Authored-By: Janek <27jf@web.de> --- src/main/xerus/monstercat/api/Playlist.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 224be79..c78e974 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -16,7 +16,7 @@ object Playlist { val history = ArrayDeque() val currentIndex = SimpleObservable(null).apply { bindSoft({ - tracks.indexOf(Player.activeTrack.value).takeUnless { i -> i == -1 } + tracks.indexOf(Player.activeTrack.value).takeUnless { it == -1 } }, Player.activeTrack, tracks) } From 5312488a1e44ecad94bf310e96222180e24a0794 Mon Sep 17 00:00:00 2001 From: Daniel THIRION Date: Thu, 8 Aug 2019 19:52:25 +0200 Subject: [PATCH 93/99] refactor(playlist): replace int addition inside of let with add Co-Authored-By: Janek <27jf@web.de> --- src/main/xerus/monstercat/api/Playlist.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index c78e974..4b9d6dc 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -82,6 +82,6 @@ object Playlist { fun addAll(tracks: ArrayList, asNext: Boolean = false) { this.tracks.removeAll(tracks) - this.tracks.addAll(if(asNext) currentIndex.value?.let { it + 1 } ?: 0 else this.tracks.lastIndex.coerceAtLeast(0), tracks) + this.tracks.addAll(if(asNext) currentIndex.value?.plus(1) ?: 0 else this.tracks.lastIndex.coerceAtLeast(0), tracks) } } From ab8c72faa1d51cdc66c3e70e63ec63b0d52b6890 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Thu, 8 Aug 2019 19:57:00 +0200 Subject: [PATCH 94/99] refactor(playlist): simplify next random function --- src/main/xerus/monstercat/api/Playlist.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 4b9d6dc..141f9e4 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -8,6 +8,8 @@ import xerus.ktutil.javafx.properties.SimpleObservable import xerus.ktutil.javafx.properties.bindSoft import xerus.monstercat.api.response.Track import java.util.* +import kotlin.random.Random +import kotlin.random.nextInt object Playlist { val logger = KotlinLogging.logger { } @@ -65,8 +67,9 @@ object Playlist { } fun getNextTrackRandom(): Track { - val index = (Math.random() * (tracks.size - 1)).toInt().let { if(it >= currentIndex.value!!) it + 1 else it }.takeUnless { it >= tracks.size } - ?: 0 + val index = Random.nextInt(0..tracks.lastIndex) + .let { if(it >= currentIndex.value!!) it + 1 else it } + .takeUnless { it >= tracks.size } ?: 0 return tracks[index] } From 82b92797a9cf5a05a54aab7a97c067fcdb794ccb Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Thu, 8 Aug 2019 20:01:46 +0200 Subject: [PATCH 95/99] refactor(main): move Playlist tab --- src/main/xerus/monstercat/MonsterUtilities.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/xerus/monstercat/MonsterUtilities.kt b/src/main/xerus/monstercat/MonsterUtilities.kt index b9dc3be..56cb657 100644 --- a/src/main/xerus/monstercat/MonsterUtilities.kt +++ b/src/main/xerus/monstercat/MonsterUtilities.kt @@ -94,12 +94,14 @@ class MonsterUtilities(checkForUpdate: Boolean): JFXMessageDisplay { monsterUtilities.showError(e, "Couldn't create ${tabClass.java.simpleName}!") } } - addTab(TabCatalog::class) - addTab(TabPlaylist::class) - addTab(TabGenres::class) - addTab(TabDownloader::class) - addTab(TabSound::class) - addTab(TabSettings::class) + listOf( + TabCatalog::class, + TabGenres::class, + TabDownloader::class, + TabSound::class, + TabPlaylist::class, + TabSettings::class + ).forEach { addTab(it) } if(VERSION != Settings.LASTVERSION.get()) { if(Settings.LASTVERSION().isEmpty()) { logger.info("First launch! Showing tutorial!") From 8a998b9bb21ccb0da11ff51945a17bc24236dead Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Fri, 9 Aug 2019 16:07:46 +0200 Subject: [PATCH 96/99] fix(api-connection): fix downloader due to forced content type headers --- src/main/xerus/monstercat/api/APIConnection.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index 526fca8..e0f49f9 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -89,8 +89,6 @@ class APIConnection(vararg path: String): HTTPQuery() { // Direct Requesting fun execute(request: HttpUriRequest, context: HttpClientContext? = null) { - request.setHeader("Accept", "application/json") - request.setHeader("Content-type", "application/json") httpRequest = request response = executeRequest(request, context) } From 44ed6a439dc1f0bd76d205bdb9aea99b4fe6cc05 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Thu, 15 Aug 2019 00:10:27 +0200 Subject: [PATCH 97/99] refactor(playlist): filter out random early if only 1 track in playlist --- src/main/xerus/monstercat/api/Playlist.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/xerus/monstercat/api/Playlist.kt b/src/main/xerus/monstercat/api/Playlist.kt index 141f9e4..ce9ebad 100644 --- a/src/main/xerus/monstercat/api/Playlist.kt +++ b/src/main/xerus/monstercat/api/Playlist.kt @@ -67,10 +67,9 @@ object Playlist { } fun getNextTrackRandom(): Track { - val index = Random.nextInt(0..tracks.lastIndex) - .let { if(it >= currentIndex.value!!) it + 1 else it } - .takeUnless { it >= tracks.size } ?: 0 - return tracks[index] + return if (tracks.size <= 1) tracks[0] + else tracks[ + Random.nextInt(0..tracks.lastIndex).let { if(it >= currentIndex.value!!) it + 1 else it } ] } fun getNextTrack(): Track? { From eb9ae6a8782e74aa429783214cdcd115f740c186 Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Thu, 15 Aug 2019 00:14:08 +0200 Subject: [PATCH 98/99] refactor(catalog-tab): use let when matching while adding to playlist --- src/main/xerus/monstercat/tabs/TabCatalog.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/xerus/monstercat/tabs/TabCatalog.kt b/src/main/xerus/monstercat/tabs/TabCatalog.kt index 7ec8db4..3e2d2bd 100644 --- a/src/main/xerus/monstercat/tabs/TabCatalog.kt +++ b/src/main/xerus/monstercat/tabs/TabCatalog.kt @@ -119,9 +119,8 @@ class TabCatalog: TableTab() { private suspend fun getSongs(songList: ObservableList>): ArrayList { val tracklist = arrayListOf() songList.forEach { item -> - val track = APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")]) - if(track != null) tracklist.add(track) - else logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") + APIUtils.find(item[cols.findUnsafe("Track")].trim(), item[cols.findUnsafe("Artist")])?.let { tracklist.add(it) } + ?: logger.warn("Failed matching song ${item[cols.findUnsafe("Artist")]} - ${item[cols.findUnsafe("Track")].trim()} while adding it to playlist") } return tracklist } From 04c8db707bb7e3911fb1c73f384174b918c8861f Mon Sep 17 00:00:00 2001 From: Daniel Thirion Date: Sun, 8 Sep 2019 11:42:21 +0200 Subject: [PATCH 99/99] fix(playlist-api): change playlist endpoint and force content type --- src/main/xerus/monstercat/api/APIConnection.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/xerus/monstercat/api/APIConnection.kt b/src/main/xerus/monstercat/api/APIConnection.kt index e0f49f9..277063a 100644 --- a/src/main/xerus/monstercat/api/APIConnection.kt +++ b/src/main/xerus/monstercat/api/APIConnection.kt @@ -236,6 +236,7 @@ class APIConnection(vararg path: String): HTTPQuery() { deleted.ifNotNull { json["deleted"] = it } val connection = APIConnection("v2", "playlist", id) val request = HttpPatch(connection.uri).apply { + setHeader("Content-Type", "application/json") val content = Sheets.JSON_FACTORY.toString(json) entity = StringEntity(content) } @@ -243,8 +244,9 @@ class APIConnection(vararg path: String): HTTPQuery() { } fun createPlaylist(name: String, tracks: List, public: Boolean = false) { - val connection = APIConnection("api", "playlist") + val connection = APIConnection("v2", "self", "playlist") val request = HttpPost(connection.uri).apply { + setHeader("Content-Type", "application/json") val json = HashMap() json["name"] = name json["public"] = public