mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 09:04:32 +01:00
Merge branch 'KMP' into imgbot
This commit is contained in:
commit
9c1e3ecd84
33
.github/workflows/build-and-publish-kjs.yml
vendored
Normal file
33
.github/workflows/build-and-publish-kjs.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
name: Build and Publish
|
||||
on: [ push, pull_request ]
|
||||
jobs:
|
||||
build:
|
||||
name: Test and Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
# Setup Java 1.8 environment for the next steps
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
|
||||
# Check out current repository
|
||||
- name: Fetch Sources
|
||||
uses: actions/checkout@v2.3.1
|
||||
|
||||
# Build application
|
||||
- name: Test and Build
|
||||
run: ./gradlew :web-app:build
|
||||
|
||||
# If main branch update, deploy to gh-pages
|
||||
- name: Deploy
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/KMP'
|
||||
uses: JamesIves/github-pages-deploy-action@4.1.0
|
||||
with:
|
||||
repository-name: Shabinder/SpotiFlyer
|
||||
token: adea5c27d4c7ee42dc4010cb8adef92538f6e52d
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BRANCH: gh-pages # The branch the action should deploy to.
|
||||
FOLDER: web-app/build/distributions # The folder the action should deploy.
|
||||
CLEAN: true # Automatically remove deleted files from the deploy branch
|
@ -0,0 +1,7 @@
|
||||
package com.shabinder.common.models
|
||||
|
||||
sealed class AllPlatforms{
|
||||
object Js:AllPlatforms()
|
||||
object Jvm:AllPlatforms()
|
||||
object Native:AllPlatforms()
|
||||
}
|
@ -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()
|
@ -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
|
||||
|
@ -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<TrackDetails>,
|
||||
fetcher: FetchPlatformQueryResult,
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -20,6 +20,7 @@ import co.touchlab.kermit.Kermit
|
||||
import com.shabinder.common.di.*
|
||||
import com.shabinder.common.di.spotify.SpotifyRequests
|
||||
import com.shabinder.common.di.spotify.authenticateSpotify
|
||||
import com.shabinder.common.models.AllPlatforms
|
||||
import com.shabinder.common.models.PlatformQueryResult
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.models.spotify.Album
|
||||
@ -43,7 +44,9 @@ class SpotifyProvider(
|
||||
init {
|
||||
logger.d { "Creating Spotify Provider" }
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
authenticateSpotifyClient()
|
||||
if(currentPlatform is AllPlatforms.Js){
|
||||
authenticateSpotifyClient(override = true)
|
||||
}else authenticateSpotifyClient()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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")
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
@ -7,7 +8,8 @@ import io.ktor.client.request.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import org.khronos.webgl.ArrayBuffer
|
||||
|
||||
actual val currentPlatform:AllPlatforms = AllPlatforms.Js
|
||||
|
||||
actual fun openPlatform(packageID:String, platformLink:String){
|
||||
//TODO
|
||||
@ -43,13 +45,14 @@ private suspend fun isInternetAvailable(): Boolean {
|
||||
actual val isInternetAvailable:Boolean
|
||||
get(){
|
||||
return true
|
||||
var result = false
|
||||
/*var result = false
|
||||
val job = GlobalScope.launch { result = isInternetAvailable() }
|
||||
while(job.isActive){}
|
||||
return result
|
||||
return result*/
|
||||
}
|
||||
|
||||
val DownloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = MutableSharedFlow(1)
|
||||
val allTracksStatus: HashMap<String, DownloadStatus> = hashMapOf()
|
||||
|
||||
actual suspend fun downloadTracks(
|
||||
list: List<TrackDetails>,
|
||||
@ -58,24 +61,29 @@ actual suspend fun downloadTracks(
|
||||
){
|
||||
withContext(Dispatchers.Default){
|
||||
list.forEach {
|
||||
allTracksStatus[it.title] = DownloadStatus.Queued
|
||||
if (!it.videoID.isNullOrBlank()) {//Video ID already known!
|
||||
downloadTrack(it.videoID!!, it, fetcher, dir)
|
||||
} else {
|
||||
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
|
||||
val videoID = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery,it)
|
||||
if (videoID.isNullOrBlank()) {
|
||||
allTracksStatus[it.title] = DownloadStatus.Failed
|
||||
DownloadProgressFlow.emit(allTracksStatus)
|
||||
} else {//Found Youtube Video ID
|
||||
downloadTrack(videoID, it, fetcher, dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
DownloadProgressFlow.emit(allTracksStatus)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun downloadTrack(videoID: String, track: TrackDetails, fetcher:FetchPlatformQueryResult,dir:Dir) {
|
||||
val url = fetcher.youtubeMp3.getMp3DownloadLink(videoID)
|
||||
if(url == null){
|
||||
// TODO Handle
|
||||
allTracksStatus[track.title] = DownloadStatus.Failed
|
||||
DownloadProgressFlow.emit(allTracksStatus)
|
||||
println("No URL to Download")
|
||||
}else {
|
||||
downloadFile(url).collect {
|
||||
@ -84,9 +92,16 @@ suspend fun downloadTrack(videoID: String, track: TrackDetails, fetcher:FetchPla
|
||||
println("Download Completed")
|
||||
dir.saveFileWithMetadata(it.byteArray, track)
|
||||
}
|
||||
is DownloadResult.Error -> println("Download Error: ${track.title}")
|
||||
is DownloadResult.Progress -> println("Download Progress: ${it.progress} : ${track.title}")
|
||||
is DownloadResult.Error -> {
|
||||
allTracksStatus[track.title] = DownloadStatus.Failed
|
||||
println("Download Error: ${track.title}")
|
||||
}
|
||||
is DownloadResult.Progress -> {
|
||||
allTracksStatus[track.title] = DownloadStatus.Downloading(it.progress)
|
||||
println("Download Progress: ${it.progress} : ${track.title}")
|
||||
}
|
||||
}
|
||||
DownloadProgressFlow.emit(allTracksStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 -> {
|
||||
@ -74,7 +76,7 @@ actual class Dir actual constructor(
|
||||
private suspend fun writeTagsAndSave(writer:ID3Writer, albumArt:Object?, trackDetails: TrackDetails){
|
||||
writer.apply {
|
||||
setFrame("TIT2", trackDetails.title)
|
||||
setFrame("TPE1", trackDetails.artists)
|
||||
setFrame("TPE1", trackDetails.artists.toTypedArray())
|
||||
setFrame("TALB", trackDetails.albumName?:"")
|
||||
try{trackDetails.year?.substring(0,4)?.toInt()?.let { setFrame("TYER", it) }} catch(e:Exception){}
|
||||
setFrame("TPE2", trackDetails.artists.joinToString(","))
|
||||
@ -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){}
|
||||
|
@ -42,6 +42,7 @@ interface SpotiFlyerMain {
|
||||
val dir: Dir
|
||||
val showPopUpMessage:(String)->Unit
|
||||
}
|
||||
|
||||
sealed class Output {
|
||||
data class Search(val link: String) : Output()
|
||||
}
|
||||
|
@ -2,4 +2,5 @@ package com.shabinder.common.root.callbacks
|
||||
|
||||
interface SpotiFlyerRootCallBacks {
|
||||
fun searchLink(link:String)
|
||||
fun popBackToHomeScreen()
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.willowtreeapps.fuzzywuzzy
|
||||
package kotlin.com.willowtree.fuzzywuzzy
|
||||
|
||||
import com.willowtreeapps.fuzzywuzzy.diffutils.Extractor
|
||||
import com.willowtreeapps.fuzzywuzzy.diffutils.algorithms.WeightedRatio
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.willowtreeapps.fuzzywuzzy
|
||||
package kotlin.com.willowtree.fuzzywuzzy
|
||||
|
||||
|
||||
import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch
|
||||
|
1
web-app/build/distributions/temp.txt
Normal file
1
web-app/build/distributions/temp.txt
Normal file
@ -0,0 +1 @@
|
||||
temp
|
@ -6,11 +6,10 @@ import com.arkivanov.decompose.lifecycle.resume
|
||||
import com.arkivanov.mvikotlin.core.store.StoreFactory
|
||||
import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory
|
||||
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
|
||||
import com.shabinder.common.models.DownloadStatus
|
||||
import com.shabinder.common.di.DownloadProgressFlow
|
||||
import com.shabinder.common.root.SpotiFlyerRoot
|
||||
import com.shabinder.database.Database
|
||||
import extras.renderableChild
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import react.*
|
||||
import root.RootR
|
||||
|
||||
@ -40,8 +39,7 @@ class App(props: AppProps): RComponent<AppProps, RState>(props) {
|
||||
override val directories = dependencies.directories
|
||||
override val database: Database? = directories.db
|
||||
override val showPopUpMessage: (String) -> Unit = {}//TODO
|
||||
override val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>>
|
||||
= MutableSharedFlow(1)
|
||||
override val downloadProgressReport = DownloadProgressFlow
|
||||
|
||||
}
|
||||
)
|
||||
|
@ -10,7 +10,6 @@ val colorOffWhite = Color("#E7E7E7")
|
||||
object Styles: StyleSheet("Searchbar", isStatic = true) {
|
||||
val makeRow by css {
|
||||
display = Display.flex
|
||||
flexDirection = FlexDirection.row
|
||||
alignItems = Align.center
|
||||
alignContent = Align.center
|
||||
justifyContent = JustifyContent.center
|
||||
|
@ -1,10 +1,14 @@
|
||||
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.browser.document
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.css.*
|
||||
import kotlinx.dom.appendElement
|
||||
import react.*
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
@ -15,11 +19,23 @@ class HomeScreen(
|
||||
props,
|
||||
initialState = State()
|
||||
) {
|
||||
override fun componentDidMount() {
|
||||
super.componentDidMount()
|
||||
val form = document.getElementById("razorpay-form")!!
|
||||
repeat(form.childNodes.length){
|
||||
form.childNodes.item(0)?.let { it1 -> form.removeChild(it1) }
|
||||
form.childNodes.item(it)?.let { it1 -> form.removeChild(it1) }
|
||||
}
|
||||
form.appendElement("script"){
|
||||
this.setAttribute("src","https://checkout.razorpay.com/v1/payment-button.js")
|
||||
this.setAttribute("async", true.toString())
|
||||
this.setAttribute("data-payment_button_id", "pl_GnKuuDBdBu0ank")
|
||||
}
|
||||
}
|
||||
|
||||
override val stateFlow: Flow<SpotiFlyerMain.State> = model.models
|
||||
|
||||
override fun RBuilder.render() {
|
||||
println("Rendering New State = \"${state.data}\" ")
|
||||
styledDiv{
|
||||
css {
|
||||
display = Display.flex
|
||||
@ -55,8 +71,8 @@ class HomeScreen(
|
||||
private val platformIconList = mapOf(
|
||||
"spotify.svg" to "https://open.spotify.com/",
|
||||
"gaana.svg" to "https://www.gaana.com/",
|
||||
"youtube.svg" to "https://www.youtube.com/",
|
||||
"youtube_music.svg" to "https://music.youtube.com/"
|
||||
//"youtube.svg" to "https://www.youtube.com/",
|
||||
//"youtube_music.svg" to "https://music.youtube.com/"
|
||||
)
|
||||
private val badges = mapOf(
|
||||
"https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=7885FF&label=SpotiFlyer&logo=android&style=for-the-badge"
|
||||
|
@ -1,11 +1,13 @@
|
||||
package home
|
||||
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.css.*
|
||||
import kotlinx.dom.appendElement
|
||||
import kotlinx.dom.createElement
|
||||
import kotlinx.html.SCRIPT
|
||||
import kotlinx.html.id
|
||||
import react.*
|
||||
import styled.css
|
||||
import styled.styledA
|
||||
import styled.styledDiv
|
||||
import styled.styledImg
|
||||
import styled.*
|
||||
|
||||
external interface IconListProps : RProps {
|
||||
var iconsAndPlatforms: Map<String,String>
|
||||
@ -22,16 +24,26 @@ fun RBuilder.IconList(handler:IconListProps.() -> Unit): ReactElement {
|
||||
}
|
||||
|
||||
private val iconList = functionalComponent<IconListProps>("IconList") { props ->
|
||||
|
||||
styledDiv {
|
||||
css {
|
||||
+Styles.makeRow
|
||||
margin(18.px)
|
||||
if(props.isBadge) {
|
||||
alignItems = Align.end
|
||||
classes = mutableListOf("info-banners")
|
||||
}
|
||||
+ Styles.makeRow
|
||||
}
|
||||
val firstElem = props.iconsAndPlatforms.keys.elementAt(1)
|
||||
for((icon,platformLink) in props.iconsAndPlatforms){
|
||||
styledA(href = platformLink){
|
||||
if(icon == firstElem && props.isBadge){
|
||||
//<form><script src="https://checkout.razorpay.com/v1/payment-button.js" data-payment_button_id="pl_GnKuuDBdBu0ank" async> </script> </form>
|
||||
styledForm {
|
||||
attrs{
|
||||
id = "razorpay-form"
|
||||
}
|
||||
}
|
||||
}
|
||||
styledA(href = platformLink,target="_blank"){
|
||||
styledImg {
|
||||
attrs {
|
||||
src = icon
|
||||
|
@ -25,7 +25,7 @@ private val message = functionalComponent<MessageProps>("Message") { props->
|
||||
+ props.text
|
||||
css {
|
||||
classes = mutableListOf("headingTitle")
|
||||
fontSize = 3.2.rem
|
||||
fontSize = 2.6.em
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<SearchbarProps>("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<SearchbarProps>("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 {
|
||||
|
40
web-app/src/main/kotlin/list/CircularProgressBar.kt
Normal file
40
web-app/src/main/kotlin/list/CircularProgressBar.kt
Normal file
@ -0,0 +1,40 @@
|
||||
package list
|
||||
|
||||
import kotlinx.css.*
|
||||
import react.*
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
import styled.styledSpan
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun RBuilder.CircularProgressBar(handler: CircularProgressBarProps.() -> Unit): ReactElement {
|
||||
return child(circularProgressBar){
|
||||
attrs {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
external interface CircularProgressBarProps : RProps {
|
||||
var progress:Int
|
||||
}
|
||||
|
||||
private val circularProgressBar = functionalComponent<CircularProgressBarProps>("Circular-Progress-Bar") { props->
|
||||
styledDiv {
|
||||
styledSpan { +"${props.progress}%" }
|
||||
styledDiv{
|
||||
css {
|
||||
classes = mutableListOf("left-half-clipper")
|
||||
}
|
||||
styledDiv{ css { classes = mutableListOf("first50-bar") } }
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@ -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<DownloadAllButtonProps>("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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
49
web-app/src/main/kotlin/list/DownloadButton.kt
Normal file
49
web-app/src/main/kotlin/list/DownloadButton.kt
Normal file
@ -0,0 +1,49 @@
|
||||
package list
|
||||
|
||||
import com.shabinder.common.models.DownloadStatus
|
||||
import kotlinx.css.*
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import react.*
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
import styled.styledImg
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun RBuilder.DownloadButton(handler: DownloadButtonProps.() -> Unit): ReactElement {
|
||||
return child(downloadButton){
|
||||
attrs {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
external interface DownloadButtonProps : RProps {
|
||||
var onClick:()->Unit
|
||||
var status :DownloadStatus
|
||||
}
|
||||
|
||||
private val downloadButton = functionalComponent<DownloadButtonProps>("Circular-Progress-Bar") { props->
|
||||
styledDiv {
|
||||
val src = when(props.status){
|
||||
is DownloadStatus.NotDownloaded -> "download-gradient.svg"
|
||||
is DownloadStatus.Downloaded -> "check.svg"
|
||||
is DownloadStatus.Failed -> "error.svg"
|
||||
else -> ""
|
||||
}
|
||||
styledImg(src = src) {
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
props.onClick()
|
||||
}
|
||||
}
|
||||
css {
|
||||
width = (2.5).em
|
||||
margin(8.px)
|
||||
}
|
||||
}
|
||||
css {
|
||||
classes = mutableListOf("glow-button")
|
||||
borderRadius = 100.px
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import kotlinx.html.id
|
||||
import react.RBuilder
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
import styled.styledSection
|
||||
|
||||
class ListScreen(
|
||||
props: Props<SpotiFlyerList>,
|
||||
@ -20,9 +21,9 @@ class ListScreen(
|
||||
|
||||
val result = state.data.queryResult
|
||||
|
||||
styledDiv {
|
||||
styledSection {
|
||||
attrs {
|
||||
id = "list-screen-div"
|
||||
id = "list-screen"
|
||||
}
|
||||
|
||||
if(result == null){
|
||||
@ -34,13 +35,25 @@ class ListScreen(
|
||||
}
|
||||
|
||||
DownloadAllButton {
|
||||
isActive = state.data.trackList.isNotEmpty()
|
||||
isActive = state.data.trackList.size > 1
|
||||
downloadAll = {
|
||||
model.onDownloadAllClicked(state.data.trackList)
|
||||
}
|
||||
link = state.data.link
|
||||
}
|
||||
|
||||
state.data.trackList.forEachIndexed{ index, trackDetails ->
|
||||
TrackItem {
|
||||
details = trackDetails
|
||||
downloadTrack = model::onDownloadClicked
|
||||
styledDiv{
|
||||
css {
|
||||
display =Display.flex
|
||||
flexGrow = 1.0
|
||||
flexDirection = FlexDirection.column
|
||||
color = Color.white
|
||||
}
|
||||
state.data.trackList.forEachIndexed{ index, trackDetails ->
|
||||
TrackItem {
|
||||
details = trackDetails
|
||||
downloadTrack = model::onDownloadClicked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -48,10 +61,9 @@ class ListScreen(
|
||||
css {
|
||||
classes = mutableListOf("list-screen")
|
||||
display = Display.flex
|
||||
padding(8.px)
|
||||
flexDirection = FlexDirection.column
|
||||
flexGrow = 1.0
|
||||
justifyContent = JustifyContent.center
|
||||
alignItems = Align.stretch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,22 +16,28 @@ fun RBuilder.LoadingAnim(handler: RProps.() -> Unit): ReactElement {
|
||||
}
|
||||
|
||||
private val loadingAnim = functionalComponent<RProps>("Loading Animation") {
|
||||
styledDiv {
|
||||
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube1") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube2") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube3") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube4") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube5") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube6") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube7") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube8") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube9") } }
|
||||
|
||||
styledDiv{
|
||||
css {
|
||||
classes = mutableListOf("sk-cube-grid")
|
||||
height = 60.px
|
||||
width = 60.px
|
||||
flexGrow = 1.0
|
||||
display = Display.flex
|
||||
alignItems = Align.center
|
||||
}
|
||||
styledDiv {
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube1") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube2") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube3") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube4") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube5") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube6") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube7") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube8") } }
|
||||
styledDiv { css { classes = mutableListOf("sk-cube sk-cube9") } }
|
||||
|
||||
css {
|
||||
classes = mutableListOf("sk-cube-grid")
|
||||
height = 60.px
|
||||
width = 60.px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
web-app/src/main/kotlin/list/LoadingSpinner.kt
Normal file
31
web-app/src/main/kotlin/list/LoadingSpinner.kt
Normal file
@ -0,0 +1,31 @@
|
||||
package list
|
||||
|
||||
import kotlinx.css.marginRight
|
||||
import kotlinx.css.px
|
||||
import kotlinx.css.width
|
||||
import react.*
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun RBuilder.LoadingSpinner(handler: RProps.() -> Unit): ReactElement {
|
||||
return child(loadingSpinner){
|
||||
attrs {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val loadingSpinner = functionalComponent<RProps>("Loading-Spinner") {
|
||||
styledDiv {
|
||||
styledDiv{}
|
||||
styledDiv{}
|
||||
styledDiv{}
|
||||
styledDiv{}
|
||||
css{
|
||||
classes = mutableListOf("lds-ring")
|
||||
width = 50.px
|
||||
marginRight = 8.px
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package list
|
||||
|
||||
import com.shabinder.common.models.DownloadStatus
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import kotlinx.css.*
|
||||
import kotlinx.html.id
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import react.*
|
||||
import styled.*
|
||||
|
||||
@ -22,9 +22,12 @@ fun RBuilder.TrackItem(handler: TrackItemProps.() -> Unit): ReactElement {
|
||||
}
|
||||
|
||||
private val trackItem = functionalComponent<TrackItemProps>("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
|
||||
@ -36,72 +39,102 @@ private val trackItem = functionalComponent<TrackItemProps>("Track-Item"){ props
|
||||
attrs {
|
||||
id = "text-details"
|
||||
}
|
||||
styledDiv {
|
||||
css {
|
||||
flexGrow = 1.0
|
||||
minWidth = 0.px
|
||||
display = Display.flex
|
||||
flexDirection = FlexDirection.column
|
||||
margin(8.px)
|
||||
}
|
||||
styledDiv{
|
||||
css {
|
||||
height = 40.px
|
||||
alignItems = Align.center
|
||||
display = Display.flex
|
||||
}
|
||||
styledH3 {
|
||||
+ details.title
|
||||
css {
|
||||
padding(8.px)
|
||||
fontSize = 1.3.em
|
||||
textOverflow = TextOverflow.ellipsis
|
||||
whiteSpace = WhiteSpace.nowrap
|
||||
overflow = Overflow.hidden
|
||||
}
|
||||
}
|
||||
css {
|
||||
height = 40.px
|
||||
display =Display.flex
|
||||
alignItems = Align.center
|
||||
}
|
||||
}
|
||||
styledDiv {
|
||||
css {
|
||||
height = 40.px
|
||||
alignItems = Align.center
|
||||
display = Display.flex
|
||||
}
|
||||
styledH4 {
|
||||
+ details.artists.joinToString(",")
|
||||
css {
|
||||
flexGrow = 1.0
|
||||
padding(8.px)
|
||||
minWidth = 4.em
|
||||
fontSize = 1.1.em
|
||||
textOverflow = TextOverflow.ellipsis
|
||||
whiteSpace = WhiteSpace.nowrap
|
||||
overflow = Overflow.hidden
|
||||
}
|
||||
}
|
||||
styledH4 {
|
||||
+ "${details.durationSec} sec"
|
||||
css {
|
||||
textAlign = TextAlign.end
|
||||
flexGrow = 1.0
|
||||
padding(8.px)
|
||||
textAlign = TextAlign.right
|
||||
minWidth = 4.em
|
||||
fontSize = 1.1.em
|
||||
textOverflow = TextOverflow.ellipsis
|
||||
whiteSpace = WhiteSpace.nowrap
|
||||
overflow = Overflow.hidden
|
||||
}
|
||||
+ "${details.durationSec/60} min, ${details.durationSec%60} sec"
|
||||
}
|
||||
css {
|
||||
height = 40.px
|
||||
display =Display.flex
|
||||
alignItems = Align.center
|
||||
}
|
||||
}
|
||||
css {
|
||||
display = Display.flex
|
||||
flexGrow = 1.0
|
||||
flexDirection = FlexDirection.column
|
||||
margin(8.px)
|
||||
}
|
||||
}
|
||||
styledDiv {
|
||||
styledImg(src = "download-gradient.svg") {
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
when(downloadStatus){
|
||||
is DownloadStatus.NotDownloaded ->{
|
||||
DownloadButton {
|
||||
onClick = {
|
||||
setDownloadStatus(DownloadStatus.Queued)
|
||||
props.downloadTrack(details)
|
||||
}
|
||||
}
|
||||
css {
|
||||
margin(8.px)
|
||||
status = downloadStatus
|
||||
}
|
||||
}
|
||||
css {
|
||||
classes = mutableListOf("glow-button")
|
||||
borderRadius = 100.px
|
||||
width = 65.px
|
||||
is DownloadStatus.Downloading -> {
|
||||
CircularProgressBar {
|
||||
progress = downloadStatus.progress
|
||||
}
|
||||
}
|
||||
DownloadStatus.Queued -> {
|
||||
LoadingSpinner {}
|
||||
}
|
||||
DownloadStatus.Downloaded -> {
|
||||
DownloadButton {
|
||||
onClick = {}
|
||||
status = downloadStatus
|
||||
}
|
||||
}
|
||||
DownloadStatus.Converting -> {
|
||||
LoadingSpinner {}
|
||||
}
|
||||
DownloadStatus.Failed -> {
|
||||
DownloadButton {
|
||||
onClick = {}
|
||||
status = downloadStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
css {
|
||||
alignItems = Align.center
|
||||
display =Display.flex
|
||||
flexDirection = FlexDirection.row
|
||||
flexGrow = 1.0
|
||||
color = Color.white
|
||||
paddingRight = 16.px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<NavBarProps>("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 = "https://shabinder.github.io/SpotiFlyer/",target="_blank") {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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<SpotiFlyerRoot>) : RenderableRootComponent<SpotiFlyerRo
|
||||
private val component: Child
|
||||
get() = model.routerState.value.activeChild.component
|
||||
|
||||
private val callBacks get() = model.callBacks
|
||||
|
||||
override fun RBuilder.render() {
|
||||
NavBar {
|
||||
isBackVisible = (component is Child.List)
|
||||
popBackToHomeScreen = callBacks::popBackToHomeScreen
|
||||
}
|
||||
when(component){
|
||||
is Child.Main -> renderableChild(HomeScreen::class, (component as Child.Main).component)
|
||||
@ -33,7 +35,7 @@ class RootR(props: Props<SpotiFlyerRoot>) : RenderableRootComponent<SpotiFlyerRo
|
||||
model.routerState.bindToState { routerState = it }
|
||||
}
|
||||
class State(
|
||||
var routerState: RouterState<*, Child>,
|
||||
var routerState: RouterState<*, Child>
|
||||
) : RState
|
||||
|
||||
}
|
||||
|
1
web-app/src/main/resources/check.svg
Normal file
1
web-app/src/main/resources/check.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientTransform="matrix(1 0 0 -1 0 -6826)" gradientUnits="userSpaceOnUse" x1="0" x2="512" y1="-7082" y2="-7082"><stop offset="0" stop-color="#31d8ff"/><stop offset="1" stop-color="#FC5C7D"/></linearGradient><path d="m512 256c0 141.386719-114.613281 256-256 256s-256-114.613281-256-256 114.613281-256 256-256 256 114.613281 256 256zm0 0" fill="url(#a)"/><path d="m175 395.246094c-4.035156 0-7.902344-1.628906-10.726562-4.511719l-81-82.832031c-5.789063-5.921875-5.683594-15.417969.238281-21.210938 5.921875-5.792968 15.417969-5.6875 21.210937.238282l70.273438 71.859374 232.277344-237.523437c5.792968-5.921875 15.289062-6.027344 21.210937-.234375 5.925781 5.789062 6.03125 15.289062.238281 21.210938l-243 248.492187c-2.820312 2.882813-6.6875 4.511719-10.722656 4.511719zm0 0" fill="#fff"/></svg>
|
After Width: | Height: | Size: 923 B |
167
web-app/src/main/resources/css-circular-prog-bar.css
Normal file
167
web-app/src/main/resources/css-circular-prog-bar.css
Normal file
@ -0,0 +1,167 @@
|
||||
.progress-circle {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.progress-circle:after{
|
||||
border: none;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
width: 4.3em;
|
||||
height: 4.3em;
|
||||
background-color: white;
|
||||
content: " ";
|
||||
margin-top: 0.35em;
|
||||
}
|
||||
/* Text inside the control */
|
||||
.progress-circle span {
|
||||
position: absolute;
|
||||
line-height: 5em;
|
||||
width: 5em;
|
||||
text-align: center;
|
||||
display: block;
|
||||
color: #53777A;
|
||||
z-index: 2;
|
||||
}
|
||||
.left-half-clipper {
|
||||
/* a round circle */
|
||||
border-radius: 50%;
|
||||
width: 5em;
|
||||
height: 5em;
|
||||
position: absolute; /* needed for clipping */
|
||||
clip: rect(0, 5em, 5em, 2.5em); /* clips the whole left half*/
|
||||
}
|
||||
/* when p>50, don't clip left half*/
|
||||
.progress-circle.over50 .left-half-clipper {
|
||||
clip: rect(auto,auto,auto,auto);
|
||||
}
|
||||
.value-bar {
|
||||
/*This is an overlayed square, that is made round with the border radius,
|
||||
then it is cut to display only the left half, then rotated clockwise
|
||||
to escape the outer clipping path.*/
|
||||
position: absolute; /*needed for clipping*/
|
||||
clip: rect(0, 2.5em, 5em, 0);
|
||||
width: 5em;
|
||||
height: 5em;
|
||||
border-radius: 50%;
|
||||
border: 0.45em solid #53777A; /*The border is 0.35 but making it larger removes visual artifacts */
|
||||
/*background-color: #4D642D;*/ /* for debug */
|
||||
box-sizing: border-box;
|
||||
|
||||
}
|
||||
/* Progress bar filling the whole right half for values above 50% */
|
||||
.progress-circle.over50 .first50-bar {
|
||||
/*Progress bar for the first 50%, filling the whole right half*/
|
||||
position: absolute; /*needed for clipping*/
|
||||
clip: rect(0, 5em, 5em, 2.5em);
|
||||
background-color: #53777A;
|
||||
border-radius: 50%;
|
||||
width: 5em;
|
||||
height: 5em;
|
||||
}
|
||||
.progress-circle:not(.over50) .first50-bar{ display: none; }
|
||||
|
||||
|
||||
/* Progress bar rotation position */
|
||||
.progress-circle.p0 .value-bar { display: none; }
|
||||
.progress-circle.p1 .value-bar { transform: rotate(4deg); }
|
||||
.progress-circle.p2 .value-bar { transform: rotate(7deg); }
|
||||
.progress-circle.p3 .value-bar { transform: rotate(11deg); }
|
||||
.progress-circle.p4 .value-bar { transform: rotate(14deg); }
|
||||
.progress-circle.p5 .value-bar { transform: rotate(18deg); }
|
||||
.progress-circle.p6 .value-bar { transform: rotate(22deg); }
|
||||
.progress-circle.p7 .value-bar { transform: rotate(25deg); }
|
||||
.progress-circle.p8 .value-bar { transform: rotate(29deg); }
|
||||
.progress-circle.p9 .value-bar { transform: rotate(32deg); }
|
||||
.progress-circle.p10 .value-bar { transform: rotate(36deg); }
|
||||
.progress-circle.p11 .value-bar { transform: rotate(40deg); }
|
||||
.progress-circle.p12 .value-bar { transform: rotate(43deg); }
|
||||
.progress-circle.p13 .value-bar { transform: rotate(47deg); }
|
||||
.progress-circle.p14 .value-bar { transform: rotate(50deg); }
|
||||
.progress-circle.p15 .value-bar { transform: rotate(54deg); }
|
||||
.progress-circle.p16 .value-bar { transform: rotate(58deg); }
|
||||
.progress-circle.p17 .value-bar { transform: rotate(61deg); }
|
||||
.progress-circle.p18 .value-bar { transform: rotate(65deg); }
|
||||
.progress-circle.p19 .value-bar { transform: rotate(68deg); }
|
||||
.progress-circle.p20 .value-bar { transform: rotate(72deg); }
|
||||
.progress-circle.p21 .value-bar { transform: rotate(76deg); }
|
||||
.progress-circle.p22 .value-bar { transform: rotate(79deg); }
|
||||
.progress-circle.p23 .value-bar { transform: rotate(83deg); }
|
||||
.progress-circle.p24 .value-bar { transform: rotate(86deg); }
|
||||
.progress-circle.p25 .value-bar { transform: rotate(90deg); }
|
||||
.progress-circle.p26 .value-bar { transform: rotate(94deg); }
|
||||
.progress-circle.p27 .value-bar { transform: rotate(97deg); }
|
||||
.progress-circle.p28 .value-bar { transform: rotate(101deg); }
|
||||
.progress-circle.p29 .value-bar { transform: rotate(104deg); }
|
||||
.progress-circle.p30 .value-bar { transform: rotate(108deg); }
|
||||
.progress-circle.p31 .value-bar { transform: rotate(112deg); }
|
||||
.progress-circle.p32 .value-bar { transform: rotate(115deg); }
|
||||
.progress-circle.p33 .value-bar { transform: rotate(119deg); }
|
||||
.progress-circle.p34 .value-bar { transform: rotate(122deg); }
|
||||
.progress-circle.p35 .value-bar { transform: rotate(126deg); }
|
||||
.progress-circle.p36 .value-bar { transform: rotate(130deg); }
|
||||
.progress-circle.p37 .value-bar { transform: rotate(133deg); }
|
||||
.progress-circle.p38 .value-bar { transform: rotate(137deg); }
|
||||
.progress-circle.p39 .value-bar { transform: rotate(140deg); }
|
||||
.progress-circle.p40 .value-bar { transform: rotate(144deg); }
|
||||
.progress-circle.p41 .value-bar { transform: rotate(148deg); }
|
||||
.progress-circle.p42 .value-bar { transform: rotate(151deg); }
|
||||
.progress-circle.p43 .value-bar { transform: rotate(155deg); }
|
||||
.progress-circle.p44 .value-bar { transform: rotate(158deg); }
|
||||
.progress-circle.p45 .value-bar { transform: rotate(162deg); }
|
||||
.progress-circle.p46 .value-bar { transform: rotate(166deg); }
|
||||
.progress-circle.p47 .value-bar { transform: rotate(169deg); }
|
||||
.progress-circle.p48 .value-bar { transform: rotate(173deg); }
|
||||
.progress-circle.p49 .value-bar { transform: rotate(176deg); }
|
||||
.progress-circle.p50 .value-bar { transform: rotate(180deg); }
|
||||
.progress-circle.p51 .value-bar { transform: rotate(184deg); }
|
||||
.progress-circle.p52 .value-bar { transform: rotate(187deg); }
|
||||
.progress-circle.p53 .value-bar { transform: rotate(191deg); }
|
||||
.progress-circle.p54 .value-bar { transform: rotate(194deg); }
|
||||
.progress-circle.p55 .value-bar { transform: rotate(198deg); }
|
||||
.progress-circle.p56 .value-bar { transform: rotate(202deg); }
|
||||
.progress-circle.p57 .value-bar { transform: rotate(205deg); }
|
||||
.progress-circle.p58 .value-bar { transform: rotate(209deg); }
|
||||
.progress-circle.p59 .value-bar { transform: rotate(212deg); }
|
||||
.progress-circle.p60 .value-bar { transform: rotate(216deg); }
|
||||
.progress-circle.p61 .value-bar { transform: rotate(220deg); }
|
||||
.progress-circle.p62 .value-bar { transform: rotate(223deg); }
|
||||
.progress-circle.p63 .value-bar { transform: rotate(227deg); }
|
||||
.progress-circle.p64 .value-bar { transform: rotate(230deg); }
|
||||
.progress-circle.p65 .value-bar { transform: rotate(234deg); }
|
||||
.progress-circle.p66 .value-bar { transform: rotate(238deg); }
|
||||
.progress-circle.p67 .value-bar { transform: rotate(241deg); }
|
||||
.progress-circle.p68 .value-bar { transform: rotate(245deg); }
|
||||
.progress-circle.p69 .value-bar { transform: rotate(248deg); }
|
||||
.progress-circle.p70 .value-bar { transform: rotate(252deg); }
|
||||
.progress-circle.p71 .value-bar { transform: rotate(256deg); }
|
||||
.progress-circle.p72 .value-bar { transform: rotate(259deg); }
|
||||
.progress-circle.p73 .value-bar { transform: rotate(263deg); }
|
||||
.progress-circle.p74 .value-bar { transform: rotate(266deg); }
|
||||
.progress-circle.p75 .value-bar { transform: rotate(270deg); }
|
||||
.progress-circle.p76 .value-bar { transform: rotate(274deg); }
|
||||
.progress-circle.p77 .value-bar { transform: rotate(277deg); }
|
||||
.progress-circle.p78 .value-bar { transform: rotate(281deg); }
|
||||
.progress-circle.p79 .value-bar { transform: rotate(284deg); }
|
||||
.progress-circle.p80 .value-bar { transform: rotate(288deg); }
|
||||
.progress-circle.p81 .value-bar { transform: rotate(292deg); }
|
||||
.progress-circle.p82 .value-bar { transform: rotate(295deg); }
|
||||
.progress-circle.p83 .value-bar { transform: rotate(299deg); }
|
||||
.progress-circle.p84 .value-bar { transform: rotate(302deg); }
|
||||
.progress-circle.p85 .value-bar { transform: rotate(306deg); }
|
||||
.progress-circle.p86 .value-bar { transform: rotate(310deg); }
|
||||
.progress-circle.p87 .value-bar { transform: rotate(313deg); }
|
||||
.progress-circle.p88 .value-bar { transform: rotate(317deg); }
|
||||
.progress-circle.p89 .value-bar { transform: rotate(320deg); }
|
||||
.progress-circle.p90 .value-bar { transform: rotate(324deg); }
|
||||
.progress-circle.p91 .value-bar { transform: rotate(328deg); }
|
||||
.progress-circle.p92 .value-bar { transform: rotate(331deg); }
|
||||
.progress-circle.p93 .value-bar { transform: rotate(335deg); }
|
||||
.progress-circle.p94 .value-bar { transform: rotate(338deg); }
|
||||
.progress-circle.p95 .value-bar { transform: rotate(342deg); }
|
||||
.progress-circle.p96 .value-bar { transform: rotate(346deg); }
|
||||
.progress-circle.p97 .value-bar { transform: rotate(349deg); }
|
||||
.progress-circle.p98 .value-bar { transform: rotate(353deg); }
|
||||
.progress-circle.p99 .value-bar { transform: rotate(356deg); }
|
||||
.progress-circle.p100 .value-bar { transform: rotate(360deg); }
|
1
web-app/src/main/resources/error.svg
Normal file
1
web-app/src/main/resources/error.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientTransform="matrix(1 0 0 -1 0 -12146)" gradientUnits="userSpaceOnUse" x1="0" x2="512" y1="-12402" y2="-12402"><stop offset="0" stop-color="#9AB3FF"/><stop offset="1" stop-color="#FC5C7D"/></linearGradient><path d="m512 256c0 141.386719-114.613281 256-256 256s-256-114.613281-256-256 114.613281-256 256-256 256 114.613281 256 256zm0 0" fill="url(#a)"/><g fill="#fff"><path d="m256 56c-110.28125 0-200 89.71875-200 200s89.71875 200 200 200 200-89.71875 200-200-89.71875-200-200-200zm0 370c-93.738281 0-170-76.261719-170-170s76.261719-170 170-170 170 76.261719 170 170-76.261719 170-170 170zm0 0"/><path d="m324.179688 187.820312c-5.859376-5.855468-15.355469-5.855468-21.214844 0l-46.964844 46.964844-46.964844-46.964844c-5.859375-5.855468-15.355468-5.855468-21.214844 0-5.855468 5.859376-5.855468 15.355469 0 21.214844l46.964844 46.964844-46.964844 46.964844c-5.855468 5.859375-5.855468 15.355468 0 21.214844 2.929688 2.929687 6.769532 4.394531 10.605469 4.394531 3.839844 0 7.679688-1.464844 10.605469-4.394531l46.96875-46.964844 46.964844 46.964844c2.929687 2.929687 6.769531 4.394531 10.605468 4.394531 3.839844 0 7.679688-1.464844 10.609376-4.394531 5.855468-5.859376 5.855468-15.355469 0-21.214844l-46.964844-46.964844 46.964844-46.964844c5.855468-5.859375 5.855468-15.355468 0-21.214844zm0 0"/></g></svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -7,10 +7,20 @@
|
||||
<script src="web-app.js"></script>
|
||||
<link rel="icon" href="spotiflyer.svg" type="image/icon type">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link href="css-circular-prog-bar.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=RocknRoll+One&display=swap" rel="stylesheet">
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-PGSYNZHSS7"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-PGSYNZHSS7');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="height: 100%;display: flex; align-items: center;flex-direction: column" id="root"></div>
|
||||
<div style="display: flex;flex-direction: column; height: inherit; color: white;" id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -2,10 +2,15 @@
|
||||
font-family: pristine;
|
||||
src: url("pristine_script.ttf");
|
||||
}
|
||||
|
||||
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%;
|
||||
@ -20,16 +25,59 @@ body, html {
|
||||
}
|
||||
#appName{
|
||||
font-family: pristine, cursive;
|
||||
font-weight: 100;
|
||||
text-shadow: 0.3px 0.5px #ffffff;
|
||||
}
|
||||
|
||||
.headingTitle{
|
||||
text-align: center;
|
||||
margin: 10px;
|
||||
font-family: 'RocknRoll One', sans-serif;
|
||||
}
|
||||
.glow-button:hover {
|
||||
.glow-button, .PaymentButton {
|
||||
transition: all .2s ease-in-out;
|
||||
}
|
||||
.glow-button:hover, .PaymentButton:hover {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
box-shadow: 0 5px 15px rgb(105, 44, 143);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/*Loading Spinner*/
|
||||
.lds-ring {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
.lds-ring div {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 3.5em;
|
||||
height: 3.5em;
|
||||
border: 8px solid #fff;
|
||||
border-radius: 50%;
|
||||
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
border-color: #fff transparent transparent transparent;
|
||||
}
|
||||
.lds-ring div:nth-child(1) {
|
||||
animation-delay: -0.45s;
|
||||
}
|
||||
.lds-ring div:nth-child(2) {
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
.lds-ring div:nth-child(3) {
|
||||
animation-delay: -0.15s;
|
||||
}
|
||||
@keyframes lds-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
text-decoration: none;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
@ -50,7 +98,7 @@ body, html {
|
||||
}
|
||||
|
||||
.searchBox:hover > .searchInput {
|
||||
width: 30vw;
|
||||
width: 35vw;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
@ -92,6 +140,8 @@ body, html {
|
||||
line-height: 40px;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
/*Download All Button*/
|
||||
#download-all-text {
|
||||
color: black;
|
||||
display: none;
|
||||
@ -191,9 +241,73 @@ body, html {
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 620px) {
|
||||
.searchBox:hover > .searchInput {
|
||||
width: 150px;
|
||||
|
||||
/*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;
|
||||
}
|
||||
@media screen and (max-width: 600px) {
|
||||
/* CSS HERE ONLY ON PHONE */
|
||||
.info-banners {
|
||||
flex-direction: column;
|
||||
}
|
||||
.searchInput {
|
||||
width: 60vw;
|
||||
padding: 0 6px;
|
||||
}
|
||||
.search-icon {
|
||||
filter: none;
|
||||
}
|
||||
.searchButton {
|
||||
background: white;
|
||||
color : #2f3640;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user