From d4d3835f8d1e4e7638748ebefeaeafc0e4734a0f Mon Sep 17 00:00:00 2001 From: shabinder Date: Tue, 16 Mar 2021 16:10:11 +0530 Subject: [PATCH] Web App Changes and Fixes --- .../shabinder/common/models/AllPlatforms.kt | 7 ++ .../com/shabinder/common/models/CorsProxy.kt | 31 +++++ .../com/shabinder/common/di/AndroidActual.kt | 2 + .../kotlin/com/shabinder/common/di/Expect.kt | 3 + .../common/di/gaana/GaanaRequests.kt | 11 +- .../common/di/providers/YoutubeMp3.kt | 10 +- .../common/di/providers/YoutubeMusic.kt | 3 +- .../common/di/spotify/SpotifyRequests.kt | 3 +- .../shabinder/common/di/youtubeMp3/Yt1sMp3.kt | 7 +- .../com/shabinder/common/di/DesktopActual.kt | 2 + .../com/shabinder/common/di/WebActual.kt | 4 +- .../kotlin/com/shabinder/common/di/WebDir.kt | 6 +- .../shabinder/common/main/SpotiFlyerMain.kt | 1 + .../root/callbacks/SpotiFlyerRootCallBacks.kt | 1 + .../root/integration/SpotiFlyerRootImpl.kt | 5 + web-app/src/main/kotlin/home/HomeScreen.kt | 3 +- web-app/src/main/kotlin/home/Searchbar.kt | 12 +- .../main/kotlin/list/CircularProgressBar.kt | 6 +- .../src/main/kotlin/list/DownloadAllButton.kt | 83 ++++++++----- web-app/src/main/kotlin/list/ListScreen.kt | 6 +- web-app/src/main/kotlin/list/TrackItem.kt | 24 ++-- web-app/src/main/kotlin/navbar/NavBar.kt | 109 ++++++++++++++---- web-app/src/main/kotlin/root/RootR.kt | 6 +- .../main/resources/css-circular-prog-bar.css | 3 +- web-app/src/main/resources/styles.css | 57 +++++++++ 25 files changed, 328 insertions(+), 77 deletions(-) create mode 100644 common/data-models/src/commonMain/kotlin/com/shabinder/common/models/AllPlatforms.kt create mode 100644 common/data-models/src/commonMain/kotlin/com/shabinder/common/models/CorsProxy.kt diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/AllPlatforms.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/AllPlatforms.kt new file mode 100644 index 00000000..cb9407b1 --- /dev/null +++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/AllPlatforms.kt @@ -0,0 +1,7 @@ +package com.shabinder.common.models + +sealed class AllPlatforms{ + object Js:AllPlatforms() + object Jvm:AllPlatforms() + object Native:AllPlatforms() +} diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/CorsProxy.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/CorsProxy.kt new file mode 100644 index 00000000..70baacfb --- /dev/null +++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/CorsProxy.kt @@ -0,0 +1,31 @@ +package com.shabinder.common.models + +sealed class CorsProxy(open val url: String){ + data class SelfHostedCorsProxy(override val url:String = "https://kind-grasshopper-73.telebit.io/cors/"):CorsProxy(url) + data class PublicProxyWithExtension(override val url:String = "https://cors.bridged.cc/"):CorsProxy(url) + + fun toggle(mode:CorsProxy? = null):CorsProxy{ + mode?.let { + corsProxy = mode + return corsProxy + } + corsProxy = when(corsProxy){ + is SelfHostedCorsProxy -> PublicProxyWithExtension() + is PublicProxyWithExtension -> SelfHostedCorsProxy() + } + return corsProxy + } + + fun extensionMode():Boolean{ + return when(corsProxy){ + is SelfHostedCorsProxy -> false + is PublicProxyWithExtension -> true + } + } +} + +/* +* This Var Keeps Track for Cors Config in JS Platform +* Default Self Hosted, However ask user to use extension if possible. +* */ +var corsProxy:CorsProxy = CorsProxy.SelfHostedCorsProxy() diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidActual.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidActual.kt index e08c0164..0f9d23d8 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidActual.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidActual.kt @@ -11,6 +11,7 @@ import com.github.kiulian.downloader.model.quality.AudioQuality import com.razorpay.Checkout import com.shabinder.common.database.activityContext import com.shabinder.common.di.worker.ForegroundService +import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.TrackDetails import kotlinx.coroutines.Dispatchers import org.json.JSONObject @@ -30,6 +31,7 @@ actual fun openPlatform(packageID:String, platformLink:String){ } } actual val dispatcherIO = Dispatchers.IO +actual val currentPlatform: AllPlatforms = AllPlatforms.Jvm actual val isInternetAvailable:Boolean get() = internetAvailability.value ?: true diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt index 4f3c9218..5a6da405 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt @@ -1,5 +1,6 @@ package com.shabinder.common.di +import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.TrackDetails import kotlinx.coroutines.CoroutineDispatcher @@ -13,6 +14,8 @@ expect val dispatcherIO: CoroutineDispatcher expect val isInternetAvailable:Boolean +expect val currentPlatform: AllPlatforms + expect suspend fun downloadTracks( list: List, fetcher: FetchPlatformQueryResult, diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/gaana/GaanaRequests.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/gaana/GaanaRequests.kt index 57db44ee..8d490484 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/gaana/GaanaRequests.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/gaana/GaanaRequests.kt @@ -1,11 +1,19 @@ package com.shabinder.common.di.gaana +import com.shabinder.common.di.currentPlatform +import com.shabinder.common.models.AllPlatforms +import com.shabinder.common.models.corsProxy import com.shabinder.common.models.gaana.* import io.ktor.client.* import io.ktor.client.request.* +val corsApi get() = if(currentPlatform is AllPlatforms.Js){ + corsProxy.url +} // "https://spotiflyer-cors.azurewebsites.net/" //"https://spotiflyer-cors.herokuapp.com/"//"https://cors.bridged.cc/" +else "" + private const val TOKEN = "b2e6d7fbc136547a940516e9b77e5990" -private const val BASE_URL = "https://api.gaana.com/" +private val BASE_URL get() = "${corsApi}https://api.gaana.com" interface GaanaRequests { @@ -76,6 +84,7 @@ interface GaanaRequests { "$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format" ) } + /* * Api Request: http://api.gaana.com/?type=artist&subtype=artist_track_listing&seokey=neha-kakkar&limit=50&token=b2e6d7fbc136547a940516e9b77e5990&format=JSON * diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt index 4cd87a71..74a0bfeb 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt @@ -2,7 +2,11 @@ package com.shabinder.common.di.providers import co.touchlab.kermit.Kermit import com.shabinder.common.di.Dir +import com.shabinder.common.di.currentPlatform import com.shabinder.common.di.youtubeMp3.Yt1sMp3 +import com.shabinder.common.models.AllPlatforms +import com.shabinder.common.models.CorsProxy +import com.shabinder.common.models.corsProxy import com.shabinder.database.Database import io.ktor.client.* @@ -11,5 +15,9 @@ class YoutubeMp3( private val logger: Kermit, private val dir: Dir, ):Yt1sMp3 { - suspend fun getMp3DownloadLink(videoID:String):String? = getLinkFromYt1sMp3(videoID) + suspend fun getMp3DownloadLink(videoID:String):String? = getLinkFromYt1sMp3(videoID)?.let{ + println("Is Self Hosted"+(corsProxy is CorsProxy.SelfHostedCorsProxy)) + if (currentPlatform is AllPlatforms.Js && corsProxy !is CorsProxy.PublicProxyWithExtension) "https://kind-grasshopper-73.telebit.io/cors/$it" + else it + } } \ No newline at end of file diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt index 10b3531e..a55c9d25 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt @@ -1,6 +1,7 @@ package com.shabinder.common.di.providers import co.touchlab.kermit.Kermit +import com.shabinder.common.di.gaana.corsApi import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.YoutubeTrack import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch @@ -246,7 +247,7 @@ class YoutubeMusic constructor( } private suspend fun getYoutubeMusicResponse(query: String):String{ - return httpClient.post("https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey"){ + return httpClient.post("${corsApi}https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey"){ contentType(ContentType.Application.Json) headers{ append("referer","https://music.youtube.com/search") diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyRequests.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyRequests.kt index c4e94bf1..b98451e4 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyRequests.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyRequests.kt @@ -1,5 +1,6 @@ package com.shabinder.common.di.spotify +import com.shabinder.common.di.gaana.corsApi import com.shabinder.common.models.spotify.Album import com.shabinder.common.models.spotify.PagingObjectPlaylistTrack import com.shabinder.common.models.spotify.Playlist @@ -7,7 +8,7 @@ import com.shabinder.common.models.spotify.Track import io.ktor.client.* import io.ktor.client.request.* -private const val BASE_URL = "https://api.spotify.com/v1" +private val BASE_URL get() = "${corsApi}https://api.spotify.com/v1" interface SpotifyRequests { diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt index a01a48ba..16440679 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt @@ -1,5 +1,6 @@ package com.shabinder.common.di.youtubeMp3 +import com.shabinder.common.di.gaana.corsApi import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.request.forms.* @@ -19,14 +20,14 @@ interface Yt1sMp3 { * Downloadable Mp3 Link for YT videoID. * */ suspend fun getLinkFromYt1sMp3(videoID: String):String? = - getConvertedMp3Link(videoID,getKey(videoID))?.get("dlink")?.jsonPrimitive?.toString()?.replace("\"", ""); + getConvertedMp3Link(videoID,getKey(videoID))?.get("dlink")?.jsonPrimitive?.toString()?.replace("\"", "") /* * POST:https://yt1s.com/api/ajaxSearch/index * Body Form= q:yt video link ,vt:format=mp3 * */ private suspend fun getKey(videoID:String):String{ - val response:JsonObject? = httpClient.post("https://yt1s.com/api/ajaxSearch/index"){ + val response:JsonObject? = httpClient.post("${corsApi}https://yt1s.com/api/ajaxSearch/index"){ body = FormDataContent(Parameters.build { append("q","https://www.youtube.com/watch?v=$videoID") append("vt","mp3") @@ -36,7 +37,7 @@ interface Yt1sMp3 { } private suspend fun getConvertedMp3Link(videoID: String,key:String):JsonObject?{ - return httpClient.post("https://yt1s.com/api/ajaxConvert/convert"){ + return httpClient.post("${corsApi}https://yt1s.com/api/ajaxConvert/convert"){ body = FormDataContent(Parameters.build { append("vid", videoID) append("k",key) diff --git a/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopActual.kt b/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopActual.kt index 42f8cc25..df6ce47f 100644 --- a/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopActual.kt +++ b/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopActual.kt @@ -4,6 +4,7 @@ import com.github.kiulian.downloader.YoutubeDownloader import com.github.kiulian.downloader.model.YoutubeVideo import com.github.kiulian.downloader.model.formats.Format import com.github.kiulian.downloader.model.quality.AudioQuality +import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails @@ -18,6 +19,7 @@ import kotlinx.coroutines.withContext actual fun openPlatform(packageID:String, platformLink:String){ //TODO } +actual val currentPlatform: AllPlatforms = AllPlatforms.Jvm actual val dispatcherIO = Dispatchers.IO diff --git a/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt index b3037611..255e5087 100644 --- a/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt +++ b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt @@ -1,5 +1,6 @@ package com.shabinder.common.di +import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails @@ -8,6 +9,8 @@ import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collect +actual val currentPlatform:AllPlatforms = AllPlatforms.Js + actual fun openPlatform(packageID:String, platformLink:String){ //TODO } @@ -87,7 +90,6 @@ suspend fun downloadTrack(videoID: String, track: TrackDetails, fetcher:FetchPla when(it){ is DownloadResult.Success -> { println("Download Completed") - allTracksStatus[track.title] = DownloadStatus.Downloaded dir.saveFileWithMetadata(it.byteArray, track) } is DownloadResult.Error -> { diff --git a/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebDir.kt b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebDir.kt index b9db776b..90004b11 100644 --- a/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebDir.kt +++ b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebDir.kt @@ -1,7 +1,9 @@ package com.shabinder.common.di import co.touchlab.kermit.Kermit +import com.shabinder.common.di.gaana.corsApi import com.shabinder.common.models.DownloadResult +import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails import com.shabinder.database.Database import kotlinext.js.Object @@ -50,7 +52,7 @@ actual class Dir actual constructor( trackDetails: TrackDetails ) { val writer = ID3Writer(mp3ByteArray.toArrayBuffer()) - val albumArt = downloadFile(trackDetails.albumArtURL) + val albumArt = downloadFile(corsApi+trackDetails.albumArtURL) albumArt.collect { when(it){ is DownloadResult.Success -> { @@ -83,7 +85,9 @@ actual class Dir actual constructor( albumArt?.let { setFrame("APIC", it) } } writer.addTag() + allTracksStatus[trackDetails.title] = DownloadStatus.Downloaded saveAs(writer.getBlob(), "${removeIllegalChars(trackDetails.title)}.mp3") + DownloadProgressFlow.emit(allTracksStatus) } actual fun addToLibrary(path:String){} diff --git a/common/main/src/commonMain/kotlin/com/shabinder/common/main/SpotiFlyerMain.kt b/common/main/src/commonMain/kotlin/com/shabinder/common/main/SpotiFlyerMain.kt index 72f453c8..9168cf49 100644 --- a/common/main/src/commonMain/kotlin/com/shabinder/common/main/SpotiFlyerMain.kt +++ b/common/main/src/commonMain/kotlin/com/shabinder/common/main/SpotiFlyerMain.kt @@ -42,6 +42,7 @@ interface SpotiFlyerMain { val dir: Dir val showPopUpMessage:(String)->Unit } + sealed class Output { data class Search(val link: String) : Output() } diff --git a/common/root/src/commonMain/kotlin/com/shabinder/common/root/callbacks/SpotiFlyerRootCallBacks.kt b/common/root/src/commonMain/kotlin/com/shabinder/common/root/callbacks/SpotiFlyerRootCallBacks.kt index db0250b3..fb3933cb 100644 --- a/common/root/src/commonMain/kotlin/com/shabinder/common/root/callbacks/SpotiFlyerRootCallBacks.kt +++ b/common/root/src/commonMain/kotlin/com/shabinder/common/root/callbacks/SpotiFlyerRootCallBacks.kt @@ -2,4 +2,5 @@ package com.shabinder.common.root.callbacks interface SpotiFlyerRootCallBacks { fun searchLink(link:String) + fun popBackToHomeScreen() } diff --git a/common/root/src/commonMain/kotlin/com/shabinder/common/root/integration/SpotiFlyerRootImpl.kt b/common/root/src/commonMain/kotlin/com/shabinder/common/root/integration/SpotiFlyerRootImpl.kt index 22515ff3..a1577d25 100644 --- a/common/root/src/commonMain/kotlin/com/shabinder/common/root/integration/SpotiFlyerRootImpl.kt +++ b/common/root/src/commonMain/kotlin/com/shabinder/common/root/integration/SpotiFlyerRootImpl.kt @@ -29,6 +29,11 @@ internal class SpotiFlyerRootImpl( override val callBacks = object : SpotiFlyerRootCallBacks{ override fun searchLink(link: String) = onMainOutput(SpotiFlyerMain.Output.Search(link)) + override fun popBackToHomeScreen() { + router.popWhile { + it !is Configuration.Main + } + } } private fun createChild(configuration: Configuration, componentContext: ComponentContext): Child = diff --git a/web-app/src/main/kotlin/home/HomeScreen.kt b/web-app/src/main/kotlin/home/HomeScreen.kt index a51ab5d3..7200d516 100644 --- a/web-app/src/main/kotlin/home/HomeScreen.kt +++ b/web-app/src/main/kotlin/home/HomeScreen.kt @@ -1,7 +1,9 @@ package home +import com.shabinder.common.di.currentPlatform import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain.State +import com.shabinder.common.models.AllPlatforms import extras.RenderableComponent import kotlinx.coroutines.flow.Flow import kotlinx.css.* @@ -19,7 +21,6 @@ class HomeScreen( override val stateFlow: Flow = model.models override fun RBuilder.render() { - println("Rendering New State = \"${state.data}\" ") styledDiv{ css { display = Display.flex diff --git a/web-app/src/main/kotlin/home/Searchbar.kt b/web-app/src/main/kotlin/home/Searchbar.kt index 126581e9..d27328f9 100644 --- a/web-app/src/main/kotlin/home/Searchbar.kt +++ b/web-app/src/main/kotlin/home/Searchbar.kt @@ -1,9 +1,12 @@ package home +import kotlinx.browser.window import kotlinx.html.InputType import kotlinx.html.js.onChangeFunction import kotlinx.html.js.onClickFunction +import kotlinx.html.js.onKeyDownFunction import org.w3c.dom.HTMLInputElement +import org.w3c.dom.Window import react.* import styled.* @@ -34,6 +37,12 @@ val searchbar = functionalComponent("SearchBar"){ props -> val target = it.target as HTMLInputElement props.onLinkChange(target.value) } + this.onKeyDownFunction = { + if(it.asDynamic().key == "Enter") { + if(props.link.isEmpty()) window.alert("Enter a Link from Supported Platforms") + else props.search(props.link) + } + } value = props.link } css { @@ -43,7 +52,8 @@ val searchbar = functionalComponent("SearchBar"){ props -> styledButton { attrs { onClickFunction = { - props.search(props.link) + if(props.link.isEmpty()) window.alert("Enter a Link from Supported Platforms") + else props.search(props.link) } } css { diff --git a/web-app/src/main/kotlin/list/CircularProgressBar.kt b/web-app/src/main/kotlin/list/CircularProgressBar.kt index b36eacf2..26f7520e 100644 --- a/web-app/src/main/kotlin/list/CircularProgressBar.kt +++ b/web-app/src/main/kotlin/list/CircularProgressBar.kt @@ -1,7 +1,6 @@ package list -import kotlinx.css.px -import kotlinx.css.width +import kotlinx.css.* import react.* import styled.css import styled.styledDiv @@ -31,8 +30,11 @@ private val circularProgressBar = functionalComponent( styledDiv{ css { classes = mutableListOf("value-bar") } } } css{ + display = Display.flex + justifyContent = JustifyContent.center classes = mutableListOf("progress-circle","p${props.progress}").apply { if(props.progress>50) add("over50") } width = 50.px + marginBottom = 65.px } } } \ No newline at end of file diff --git a/web-app/src/main/kotlin/list/DownloadAllButton.kt b/web-app/src/main/kotlin/list/DownloadAllButton.kt index 489760aa..7ef116ec 100644 --- a/web-app/src/main/kotlin/list/DownloadAllButton.kt +++ b/web-app/src/main/kotlin/list/DownloadAllButton.kt @@ -2,6 +2,7 @@ package list import kotlinx.css.* import kotlinx.html.id +import kotlinx.html.js.onClickFunction import react.* import styled.css import styled.styledDiv @@ -10,6 +11,8 @@ import styled.styledImg external interface DownloadAllButtonProps : RProps { var isActive:Boolean + var link : String + var downloadAll:()->Unit } @Suppress("FunctionName") @@ -22,37 +25,65 @@ fun RBuilder.DownloadAllButton(handler: DownloadAllButtonProps.() -> Unit): Reac } private val downloadAllButton = functionalComponent("DownloadAllButton") { props-> - styledDiv { - styledDiv { - styledImg(src = "download.svg",alt = "Download All Button") { + val (isClicked,setClicked) = useState(false) + + useEffect(mutableListOf(props.link)){ + setClicked(false) + } + + if(props.isActive){ + if(isClicked) { + styledDiv{ css { - classes = mutableListOf("download-all-icon") - height = 32.px + display = Display.flex + alignItems = Align.center + justifyContent = JustifyContent.center + height = 52.px } - } - - styledH5 { - attrs { - id = "download-all-text" - } - + "Download All" - css { - whiteSpace = WhiteSpace.nowrap - fontSize = 15.px - } - } - - css { - classes = mutableListOf("download-icon") - display = Display.flex - alignItems = Align.center + LoadingSpinner { } } } - css { - classes = mutableListOf("download-button") - display = if(props.isActive) Display.flex else Display.none - alignItems = Align.center + else{ + styledDiv { + attrs { + onClickFunction = { + //props.downloadAll() + setClicked(true) + } + } + styledDiv { + + styledImg(src = "download.svg",alt = "Download All Button") { + css { + classes = mutableListOf("download-all-icon") + height = 32.px + } + } + + styledH5 { + attrs { + id = "download-all-text" + } + + "Download All" + css { + whiteSpace = WhiteSpace.nowrap + fontSize = 15.px + } + } + + css { + classes = mutableListOf("download-icon") + display = Display.flex + alignItems = Align.center + } + } + css { + classes = mutableListOf("download-button") + display = Display.flex + alignItems = Align.center + } + } } } } diff --git a/web-app/src/main/kotlin/list/ListScreen.kt b/web-app/src/main/kotlin/list/ListScreen.kt index a45b0bd5..d43ded27 100644 --- a/web-app/src/main/kotlin/list/ListScreen.kt +++ b/web-app/src/main/kotlin/list/ListScreen.kt @@ -35,7 +35,11 @@ class ListScreen( } DownloadAllButton { - isActive = state.data.trackList.isNotEmpty() + isActive = state.data.trackList.size > 1 + downloadAll = { + model.onDownloadAllClicked(state.data.trackList) + } + link = state.data.link } styledDiv{ diff --git a/web-app/src/main/kotlin/list/TrackItem.kt b/web-app/src/main/kotlin/list/TrackItem.kt index b28feda0..587b9789 100644 --- a/web-app/src/main/kotlin/list/TrackItem.kt +++ b/web-app/src/main/kotlin/list/TrackItem.kt @@ -22,9 +22,12 @@ fun RBuilder.TrackItem(handler: TrackItemProps.() -> Unit): ReactElement { } private val trackItem = functionalComponent("Track-Item"){ props -> + val (downloadStatus,setDownloadStatus) = useState(props.details.downloaded) val details = props.details + useEffect(listOf(props.details)){ + setDownloadStatus(props.details.downloaded) + } styledDiv { - styledImg(src = details.albumArtURL) { css { height = 90.px @@ -89,20 +92,23 @@ private val trackItem = functionalComponent("Track-Item"){ props whiteSpace = WhiteSpace.nowrap overflow = Overflow.hidden } - + details.durationSec.toString() + + "${details.durationSec/60} min, ${details.durationSec%60} sec" } } } - when(details.downloaded){ + when(downloadStatus){ is DownloadStatus.NotDownloaded ->{ DownloadButton { - onClick = { props.downloadTrack(details) } - status = details.downloaded + onClick = { + setDownloadStatus(DownloadStatus.Queued) + props.downloadTrack(details) + } + status = downloadStatus } } is DownloadStatus.Downloading -> { CircularProgressBar { - progress = (details.downloaded as DownloadStatus.Downloading).progress + progress = downloadStatus.progress } } DownloadStatus.Queued -> { @@ -111,7 +117,7 @@ private val trackItem = functionalComponent("Track-Item"){ props DownloadStatus.Downloaded -> { DownloadButton { onClick = {} - status = details.downloaded + status = downloadStatus } } DownloadStatus.Converting -> { @@ -120,7 +126,7 @@ private val trackItem = functionalComponent("Track-Item"){ props DownloadStatus.Failed -> { DownloadButton { onClick = {} - status = details.downloaded + status = downloadStatus } } } @@ -128,7 +134,7 @@ private val trackItem = functionalComponent("Track-Item"){ props css { alignItems = Align.center display =Display.flex - flexGrow = 1.0 + paddingRight = 16.px } } } diff --git a/web-app/src/main/kotlin/navbar/NavBar.kt b/web-app/src/main/kotlin/navbar/NavBar.kt index 4d0c0d75..8f791c68 100644 --- a/web-app/src/main/kotlin/navbar/NavBar.kt +++ b/web-app/src/main/kotlin/navbar/NavBar.kt @@ -2,10 +2,11 @@ package navbar import kotlinx.css.* import kotlinx.html.id +import kotlinx.html.js.onBlurFunction +import kotlinx.html.js.onClickFunction import react.* import styled.* - @Suppress("FunctionName") fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{ return child(navBar){ @@ -17,47 +18,107 @@ fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{ external interface NavBarProps:RProps{ var isBackVisible: Boolean + var popBackToHomeScreen: () -> Unit } private val navBar = functionalComponent("NavBar") { props -> + styledNav { css { +NavBarStyles.nav } - styledImg(src = "left-arrow.svg",alt = "Back Arrow"){ - css { - height = 42.px - width = 42.px - display = if(props.isBackVisible) Display.inline else Display.none - filter = "invert(100)" - marginRight = 12.px - } - } - styledImg(src = "spotiflyer.svg",alt = "Logo") { - css { - height = 42.px - width = 42.px - } - } - styledH1 { - +"SpotiFlyer" + styledDiv{ attrs { - id = "appName" + onClickFunction = { + props.popBackToHomeScreen() + } + onBlurFunction = { + props.popBackToHomeScreen() + } } - css{ - fontSize = 46.px - margin(horizontal = 14.px) + styledImg(src = "left-arrow.svg",alt = "Back Arrow"){ + css { + height = 42.px + width = 42.px + display = if(props.isBackVisible) Display.inline else Display.none + filter = "invert(100)" + marginRight = 12.px + } } } - styledA(href = "https://github.com/Shabinder/SpotiFlyer/"){ - styledImg(src = "github.svg"){ + styledA(href = "TODO Website Link") { + css { + display = Display.flex + alignItems = Align.center + } + styledImg(src = "spotiflyer.svg",alt = "Logo") { css { height = 42.px width = 42.px } } + styledH1 { + +"SpotiFlyer" + attrs { + id = "appName" + } + css{ + fontSize = 46.px + margin(horizontal = 14.px) + } + } + } + + /*val (corsMode,setCorsMode) = useState(CorsProxy.SelfHostedCorsProxy() as CorsProxy) + + useEffect { + setCorsMode(corsProxy) + }*/ + + styledDiv{ + + /*styledH4 { + "Extension" } + + styledDiv { + styledInput(type = InputType.checkBox) { + attrs{ + id = "cmn-toggle-4" + value = "Extension" + checked = corsMode.extensionMode() + onChangeFunction = { + val state = it.target as HTMLInputElement + if(state.checked){ + setCorsMode(corsProxy.toggle(CorsProxy.PublicProxyWithExtension())) + } else{ + setCorsMode(corsProxy.toggle(CorsProxy.SelfHostedCorsProxy())) + } + println("Active Proxy: ${corsProxy.url}") + } + } + css{ + classes = mutableListOf("cmn-toggle","cmn-toggle-round-flat") + } + } + styledLabel { attrs { htmlFor = "cmn-toggle-4" } } + css{ + classes = mutableListOf("switch") + marginLeft = 8.px + marginRight = 16.px + } + }*/ + + styledA(href = "https://github.com/Shabinder/SpotiFlyer/"){ + styledImg(src = "github.svg"){ + css { + height = 42.px + width = 42.px + } + } + } css { + display = Display.flex + alignItems = Align.center marginLeft = LinearDimension.auto } } diff --git a/web-app/src/main/kotlin/root/RootR.kt b/web-app/src/main/kotlin/root/RootR.kt index 279e4220..d0882e15 100644 --- a/web-app/src/main/kotlin/root/RootR.kt +++ b/web-app/src/main/kotlin/root/RootR.kt @@ -6,7 +6,6 @@ import com.shabinder.common.root.SpotiFlyerRoot.* import extras.RenderableRootComponent import extras.renderableChild import home.HomeScreen -import kotlinx.coroutines.launch import list.ListScreen import navbar.NavBar import react.RBuilder @@ -19,9 +18,12 @@ class RootR(props: Props) : RenderableRootComponent renderableChild(HomeScreen::class, (component as Child.Main).component) @@ -33,7 +35,7 @@ class RootR(props: Props) : RenderableRootComponent, + var routerState: RouterState<*, Child> ) : RState } diff --git a/web-app/src/main/resources/css-circular-prog-bar.css b/web-app/src/main/resources/css-circular-prog-bar.css index f3a9bfa9..fc7fc4f9 100644 --- a/web-app/src/main/resources/css-circular-prog-bar.css +++ b/web-app/src/main/resources/css-circular-prog-bar.css @@ -1,7 +1,5 @@ .progress-circle { margin: 20px; - position: relative; /* so that children can be absolutely positioned */ - line-height: 5em; } .progress-circle:after{ @@ -14,6 +12,7 @@ height: 4.3em; background-color: white; content: " "; + margin-top: 0.35em; } /* Text inside the control */ .progress-circle span { diff --git a/web-app/src/main/resources/styles.css b/web-app/src/main/resources/styles.css index 5f49257e..afa98b23 100644 --- a/web-app/src/main/resources/styles.css +++ b/web-app/src/main/resources/styles.css @@ -16,6 +16,10 @@ html { background-image: url("header-dark.jpg"); font-family: Lora,Helvetica Neue,Helvetica,Arial,sans-serif; } +a:link, a:visited { + color: white; + text-decoration: none; +} body, html { width: 100%; height: 100%; @@ -238,4 +242,57 @@ body, html { -webkit-transform: scale3D(0, 0, 1); transform: scale3D(0, 0, 1); } +} + +/*Extension Switch*/ +.cmn-toggle { + position: absolute; + margin-left: -9999px; + visibility: hidden; +} + +.cmn-toggle + label { + display: block; + position: relative; + cursor: pointer; + outline: none; + user-select: none; +} + +input.cmn-toggle-round-flat + label { + /* width = 2*height or 2*border-radius */ + padding: 2px; + width: 40px; + height: 20px; + border: 3px solid #dddddd; + border-radius: 60px; + transition: border-color 0.3s; +} + +input.cmn-toggle-round-flat + label:before, +input.cmn-toggle-round-flat + label:after { + display: block; + position: absolute; + content: ""; +} + +input.cmn-toggle-round-flat + label:after { + /* width = 2*border-radius */ + top: 4px; + left: 4px; + bottom: 4px; + width: 16px; + background-color: #dddddd; + border-radius: 52px; + transition: margin 0.3s, background 0.3s; +} + +input.cmn-toggle-round-flat:checked + label { + border-color: #8ce196; +} + +input.cmn-toggle-round-flat:checked + label:after { + /* margin-left = border-radius from 'input.cmn-toggle-round-flat + label' */ + margin-left: 20px; + background-color: #8ce196; } \ No newline at end of file