From a47d9f52a0ec004640df1eb02a9eba6d3b8769f3 Mon Sep 17 00:00:00 2001 From: shabinder Date: Sat, 13 Mar 2021 01:48:03 +0530 Subject: [PATCH] ID3 Tags and File Save for web-app. --- .../multiplatform-setup-test.gradle.kts | 5 +- .../kotlin/multiplatform-setup.gradle.kts | 5 +- .../common/uikit/SpotiFlyerListUi.kt | 5 +- .../common/uikit/SpotiFlyerMainUi.kt | 6 +- common/dependency-injection/build.gradle.kts | 5 +- .../com/shabinder/common/di/AndroidActual.kt | 7 +- .../kotlin/com/shabinder/common/di/Expect.kt | 4 +- .../common/di/FetchPlatformQueryResult.kt | 2 +- .../common/di/providers/SpotifyProvider.kt | 11 +- .../common/di/providers/YoutubeMusic.kt | 3 +- .../common/di/spotify/SpotifyRequests.kt | 2 +- .../com/shabinder/common/di/DesktopActual.kt | 14 +-- .../com/shabinder/common/di/FileSave.kt | 26 +++++ .../com/shabinder/common/di/ID3Writer.kt | 15 +++ .../com/shabinder/common/di/WebActual.kt | 46 ++++++-- .../kotlin/com/shabinder/common/di/WebDir.kt | 72 +++++++++--- .../list/store/SpotiFlyerListStoreProvider.kt | 4 +- web-app/build.gradle.kts | 4 +- web-app/src/main/kotlin/App.kt | 3 +- web-app/src/main/kotlin/client.kt | 11 +- web-app/src/main/kotlin/home/HomeScreen.kt | 17 --- web-app/src/main/kotlin/home/Searchbar.kt | 1 - web-app/src/main/kotlin/list/CoverImage.kt | 25 ++-- .../src/main/kotlin/list/DownloadAllButton.kt | 58 +++++++++ web-app/src/main/kotlin/list/ListScreen.kt | 38 ++++-- web-app/src/main/kotlin/list/LoadingAnim.kt | 37 ++++++ web-app/src/main/kotlin/list/TrackItem.kt | 106 ++++++++++++++++- web-app/src/main/kotlin/navbar/NavBar.kt | 77 ++++++------ web-app/src/main/kotlin/root/RootR.kt | 5 + .../src/main/resources/download-gradient.svg | 49 ++++++++ web-app/src/main/resources/download.svg | 45 +++++++ web-app/src/main/resources/header-dark.jpg | Bin 0 -> 71066 bytes web-app/src/main/resources/left-arrow.svg | 61 ++++++++++ web-app/src/main/resources/styles.css | 110 +++++++++++++++++- 34 files changed, 733 insertions(+), 146 deletions(-) create mode 100644 common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/FileSave.kt create mode 100644 common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/ID3Writer.kt create mode 100644 web-app/src/main/kotlin/list/DownloadAllButton.kt create mode 100644 web-app/src/main/kotlin/list/LoadingAnim.kt create mode 100644 web-app/src/main/resources/download-gradient.svg create mode 100644 web-app/src/main/resources/download.svg create mode 100644 web-app/src/main/resources/header-dark.jpg create mode 100644 web-app/src/main/resources/left-arrow.svg diff --git a/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts index 6f128d50..28d8ccc2 100644 --- a/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts +++ b/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts @@ -7,9 +7,10 @@ kotlin { jvm("desktop") android() //ios() - js { + js() { browser() - nodejs() + //nodejs() + binaries.executable() } sourceSets { named("commonTest") { diff --git a/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts index 68d7a67d..b91a8f22 100644 --- a/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts +++ b/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts @@ -14,9 +14,10 @@ plugins { kotlin { jvm("desktop") android() - js { + js() { browser() - nodejs() + //nodejs() + binaries.executable() } sourceSets { named("commonMain") { diff --git a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt index 75d8033f..6603a385 100644 --- a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt +++ b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.shabinder.common.di.Picture import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails @@ -67,7 +68,7 @@ fun SpotiFlyerListContent( fun TrackCard( track: TrackDetails, downloadTrack:()->Unit, - loadImage:suspend (String)-> ImageBitmap? + loadImage:suspend (String)-> Picture ) { Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) { ImageLoad( @@ -120,7 +121,7 @@ fun CoverImage( title: String, coverURL: String, scope: CoroutineScope, - loadImage: suspend (String) -> ImageBitmap?, + loadImage: suspend (String) -> Picture, modifier: Modifier = Modifier, ) { Column( diff --git a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerMainUi.kt b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerMainUi.kt index 32309738..b9fcb8f1 100644 --- a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerMainUi.kt +++ b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerMainUi.kt @@ -28,10 +28,10 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.shabinder.common.di.Picture import com.shabinder.common.di.giveDonation import com.shabinder.common.di.openPlatform import com.shabinder.common.di.shareApp -import com.shabinder.common.di.showPopUpMessage import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain.HomeCategory import com.shabinder.common.models.DownloadRecord @@ -303,7 +303,7 @@ fun AboutColumn(modifier: Modifier = Modifier) { @Composable fun HistoryColumn( list: List, - loadImage:suspend (String)-> ImageBitmap?, + loadImage:suspend (String)-> Picture, onItemClicked: (String) -> Unit ) { Crossfade(list){ @@ -335,7 +335,7 @@ fun HistoryColumn( @Composable fun DownloadRecordItem( item: DownloadRecord, - loadImage:suspend (String)-> ImageBitmap?, + loadImage:suspend (String)-> Picture, onItemClicked:(String)->Unit ) { Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(end = 8.dp)) { diff --git a/common/dependency-injection/build.gradle.kts b/common/dependency-injection/build.gradle.kts index d34854e1..3a146355 100644 --- a/common/dependency-injection/build.gradle.kts +++ b/common/dependency-injection/build.gradle.kts @@ -50,8 +50,11 @@ kotlin { } jsMain { dependencies { - implementation(Ktor.clientJs) implementation(project(":common:data-models")) + implementation(Ktor.clientJs) + implementation(npm("browser-id3-writer","4.4.0")) + implementation(npm("file-saver","2.0.4")) + //implementation(npm("@types/file-saver","2.0.1",generateExternals = true)) } } } 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 a086de76..e08c0164 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 @@ -4,15 +4,12 @@ import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.net.Uri -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState import androidx.core.content.ContextCompat 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.razorpay.Checkout import com.shabinder.common.database.activityContext -import com.shabinder.common.database.appContext import com.shabinder.common.di.worker.ForegroundService import com.shabinder.common.models.TrackDetails import kotlinx.coroutines.Dispatchers @@ -87,8 +84,8 @@ actual fun queryActiveTracks() { actual suspend fun downloadTracks( list: List, - getYTIDBestMatch:suspend (String,TrackDetails)->String?, - saveFileWithMetaData:suspend (mp3ByteArray:ByteArray, trackDetails: TrackDetails) -> Unit + fetcher: FetchPlatformQueryResult, + dir: Dir ){ if(!list.isNullOrEmpty()){ val serviceIntent = Intent(activityContext, ForegroundService::class.java) 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 79ae1be1..4f3c9218 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 @@ -15,8 +15,8 @@ expect val isInternetAvailable:Boolean expect suspend fun downloadTracks( list: List, - getYTIDBestMatch:suspend (String,TrackDetails)->String?, - saveFileWithMetaData:suspend (mp3ByteArray:ByteArray, trackDetails: TrackDetails) -> Unit + fetcher: FetchPlatformQueryResult, + dir: Dir ) expect fun queryActiveTracks() \ No newline at end of file diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/FetchPlatformQueryResult.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/FetchPlatformQueryResult.kt index d2be47d7..eac9a316 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/FetchPlatformQueryResult.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/FetchPlatformQueryResult.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.withContext class FetchPlatformQueryResult( private val gaanaProvider: GaanaProvider, - private val spotifyProvider: SpotifyProvider, + val spotifyProvider: SpotifyProvider, val youtubeProvider: YoutubeProvider, val youtubeMusic: YoutubeMusic, val youtubeMp3: YoutubeMp3, diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt index 3e3de8ce..c99708c0 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt @@ -19,6 +19,7 @@ package com.shabinder.common.di.providers 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.PlatformQueryResult import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.spotify.Album @@ -41,11 +42,13 @@ class SpotifyProvider( init { logger.d { "Creating Spotify Provider" } - //GlobalScope.launch(Dispatchers.Default) {authenticateSpotify()} + GlobalScope.launch(Dispatchers.Default) { + authenticateSpotifyClient() + } } - override suspend fun authenticateSpotify(): HttpClient?{ - val token = tokenStore.getToken() + override suspend fun authenticateSpotifyClient(override:Boolean): HttpClient?{ + val token = if(override) authenticateSpotify() else tokenStore.getToken() return if(token == null) { logger.d{ "Please Check your Network Connection" } null @@ -69,7 +72,7 @@ class SpotifyProvider( suspend fun query(fullLink: String): PlatformQueryResult?{ if(!this::httpClient.isInitialized){ - authenticateSpotify() + authenticateSpotifyClient() } var spotifyLink = 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 2e265701..10b3531e 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 @@ -249,8 +249,7 @@ class YoutubeMusic constructor( return httpClient.post("https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey"){ contentType(ContentType.Application.Json) headers{ - //append("Content-Type"," application/json") - append("Referer"," https://music.youtube.com/search") + append("referer","https://music.youtube.com/search") } body = buildJsonObject { putJsonObject("context"){ 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 49c95c3a..c4e94bf1 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 @@ -13,7 +13,7 @@ interface SpotifyRequests { val httpClient:HttpClient - suspend fun authenticateSpotify():HttpClient? + suspend fun authenticateSpotifyClient(override:Boolean = false):HttpClient? suspend fun getPlaylist(playlistID: String): Playlist { return httpClient.get("$BASE_URL/playlists/$playlistID") 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 06c209cf..42f8cc25 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 @@ -1,9 +1,5 @@ package com.shabinder.common.di -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape import com.github.kiulian.downloader.YoutubeDownloader import com.github.kiulian.downloader.model.YoutubeVideo import com.github.kiulian.downloader.model.formats.Format @@ -62,20 +58,20 @@ val DownloadProgressFlow: MutableSharedFlow> = Mu actual suspend fun downloadTracks( list: List, - getYTIDBestMatch:suspend (String,TrackDetails)->String?, - saveFileWithMetaData:suspend (mp3ByteArray:ByteArray, trackDetails: TrackDetails) -> Unit + fetcher: FetchPlatformQueryResult, + dir: Dir ){ list.forEach { if (!it.videoID.isNullOrBlank()) {//Video ID already known! - downloadTrack(it.videoID!!, it,saveFileWithMetaData) + downloadTrack(it.videoID!!, it,dir::saveFileWithMetadata) } else { val searchQuery = "${it.title} - ${it.artists.joinToString(",")}" - val videoId = getYTIDBestMatch(searchQuery,it) + val videoId = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery,it) if (videoId.isNullOrBlank()) { DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0 ) { hashMapOf() }.apply { set(it.title,DownloadStatus.Failed) }) } else {//Found Youtube Video ID - downloadTrack(videoId, it,saveFileWithMetaData) + downloadTrack(videoId, it,dir::saveFileWithMetadata) } } } diff --git a/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/FileSave.kt b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/FileSave.kt new file mode 100644 index 00000000..5f120f80 --- /dev/null +++ b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/FileSave.kt @@ -0,0 +1,26 @@ +@file:JsModule("file-saver") +@file:JsNonModule + +package com.shabinder.common.di + +import org.w3c.files.Blob + +external interface FileSaverOptions { + var autoBom: Boolean +} + +external fun saveAs(data: Blob, filename: String = definedExternally, options: FileSaverOptions = definedExternally) + +external fun saveAs(data: Blob) + +external fun saveAs(data: Blob, filename: String = definedExternally) + +external fun saveAs(data: String, filename: String = definedExternally, options: FileSaverOptions = definedExternally) + +external fun saveAs(data: String) + +external fun saveAs(data: String, filename: String = definedExternally) + +external fun saveAs(data: Blob, filename: String = definedExternally, disableAutoBOM: Boolean = definedExternally) + +external fun saveAs(data: String, filename: String = definedExternally, disableAutoBOM: Boolean = definedExternally) \ No newline at end of file diff --git a/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/ID3Writer.kt b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/ID3Writer.kt new file mode 100644 index 00000000..cd8419d5 --- /dev/null +++ b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/ID3Writer.kt @@ -0,0 +1,15 @@ +package com.shabinder.common.di + +import org.khronos.webgl.ArrayBuffer +import org.w3c.files.Blob + +@JsModule("browser-id3-writer") +@JsNonModule +external class ID3Writer(a: ArrayBuffer) { + fun setFrame(frameName:String,frameValue:Any):ID3Writer + fun removeTag() + fun addTag():ArrayBuffer + fun getBlob():Blob + fun getURL():String + fun revokeURL() +} \ No newline at end of file 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 c8236534..520e106d 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,10 +1,13 @@ package com.shabinder.common.di +import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails import io.ktor.client.request.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.collect +import org.khronos.webgl.ArrayBuffer actual fun openPlatform(packageID:String, platformLink:String){ //TODO @@ -50,13 +53,40 @@ val DownloadProgressFlow: MutableSharedFlow> = M actual suspend fun downloadTracks( list: List, - getYTIDBestMatch:suspend (String, TrackDetails)->String?, - saveFileWithMetaData:suspend (mp3ByteArray:ByteArray, trackDetails: TrackDetails) -> Unit -){/* - list.forEach { - if (!it.videoID.isNullOrBlank()) {//Video ID already known! - } else { - + fetcher: FetchPlatformQueryResult, + dir: Dir +){ + withContext(Dispatchers.Default){ + list.forEach { + 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()) { + } else {//Found Youtube Video ID + downloadTrack(videoID, it, fetcher, dir) + } + } } - }*/ + } +} + +suspend fun downloadTrack(videoID: String, track: TrackDetails, fetcher:FetchPlatformQueryResult,dir:Dir) { + val url = fetcher.youtubeMp3.getMp3DownloadLink(videoID) + if(url == null){ + // TODO Handle + println("No URL to Download") + }else { + downloadFile(url).collect { + when(it){ + is DownloadResult.Success -> { + 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}") + } + } + } } 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 6b523f76..e9d66437 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,9 +1,20 @@ package com.shabinder.common.di import co.touchlab.kermit.Kermit +import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.TrackDetails import com.shabinder.database.Database +import kotlinext.js.Object +import kotlinext.js.asJsObject +import kotlinext.js.js +import kotlinext.js.jsObject +import kotlinx.coroutines.flow.collect +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import org.khronos.webgl.ArrayBuffer import org.w3c.dom.ImageBitmap +import org.khronos.webgl.Int8Array actual class Dir actual constructor( private val logger: Kermit, @@ -13,9 +24,10 @@ actual class Dir actual constructor( /*init { createDirectories() }*/ -/* -* TODO -* */ + + /* + * TODO + * */ actual fun fileSeparator(): String = "/" actual fun imageCacheDir(): String = "TODO" + @@ -26,12 +38,9 @@ actual class Dir actual constructor( actual fun isPresent(path: String): Boolean = false - actual fun createDirectory(dirPath:String){ + actual fun createDirectory(dirPath:String){} - } - - actual suspend fun clearCache() { - } + actual suspend fun clearCache() {} actual suspend fun cacheImage(image: Any,path:String) {} @@ -40,6 +49,41 @@ actual class Dir actual constructor( mp3ByteArray: ByteArray, trackDetails: TrackDetails ) { + val writer = ID3Writer(mp3ByteArray.toArrayBuffer()) + val albumArt = downloadFile(trackDetails.albumArtURL) + albumArt.collect { + when(it){ + is DownloadResult.Success -> { + println("Album Art Downloaded Success") + val albumArtObj = js { + this["type"] = 3 + this["data"] = it.byteArray.toArrayBuffer() + this["description"] = "Cover Art" + } + writeTagsAndSave(writer, albumArtObj as Object,trackDetails) + } + is DownloadResult.Error -> { + println("Album Art Downloading Error") + writeTagsAndSave(writer,null,trackDetails) + } + is DownloadResult.Progress -> println("Album Art Downloading: ${it.progress}") + } + } + } + + private suspend fun writeTagsAndSave(writer:ID3Writer, albumArt:Object?, trackDetails: TrackDetails){ + writer.apply { + setFrame("TIT2", trackDetails.title) + setFrame("TPE1", trackDetails.artists) + setFrame("TALB", trackDetails.albumName?:"") + try{trackDetails.year?.substring(0,4)?.toInt()?.let { setFrame("TYER", it) }} catch(e:Exception){} + setFrame("TPE2", trackDetails.artists.joinToString(",")) + setFrame("WOAS", trackDetails.source.toString()) + setFrame("TLEN", trackDetails.durationSec) + albumArt?.let { setFrame("APIC", it) } + } + writer.addTag() + saveAs(writer.getBlob(), "${removeIllegalChars(trackDetails.title)}.mp3") } actual fun addToLibrary(path:String){} @@ -48,14 +92,14 @@ actual class Dir actual constructor( return Picture(url) } - private fun loadCachedImage(cachePath: String): ImageBitmap? { - return null - } + private fun loadCachedImage(cachePath: String): ImageBitmap? = null - private suspend fun freshImage(url:String): ImageBitmap?{ - return null - } + private suspend fun freshImage(url:String): ImageBitmap? = null actual val db: Database? get() = database } + +fun ByteArray.toArrayBuffer():ArrayBuffer{ + return this.unsafeCast().buffer +} \ No newline at end of file diff --git a/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt b/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt index 5cdd4b3a..964fad92 100644 --- a/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt +++ b/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt @@ -60,7 +60,7 @@ internal class SpotiFlyerListStoreProvider( val finalList = intent.trackList.filter { it.downloaded == DownloadStatus.NotDownloaded } if (finalList.isNullOrEmpty()) showPopUpMessage("All Songs are Processed") - else downloadTracks(finalList,fetchQuery.youtubeMusic::getYTIDBestMatch,dir::saveFileWithMetadata) + else downloadTracks(finalList,fetchQuery,dir) val list = intent.trackList.map { if (it.downloaded == DownloadStatus.NotDownloaded) @@ -70,7 +70,7 @@ internal class SpotiFlyerListStoreProvider( dispatch(Result.UpdateTrackList(list.updateTracksStatuses(downloadProgressFlow.replayCache.getOrElse(0){ hashMapOf()}))) } is Intent.StartDownload -> { - downloadTracks(listOf(intent.track),fetchQuery.youtubeMusic::getYTIDBestMatch,dir::saveFileWithMetadata) + downloadTracks(listOf(intent.track),fetchQuery,dir) dispatch(Result.UpdateTrackItem(intent.track.copy(downloaded = DownloadStatus.Queued))) } is Intent.RefreshTracksStatuses -> queryActiveTracks() diff --git a/web-app/build.gradle.kts b/web-app/build.gradle.kts index cb001435..51cce1b5 100644 --- a/web-app/build.gradle.kts +++ b/web-app/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { implementation(kotlin("stdlib-js")) implementation(Decompose.decompose) implementation(Koin.core) + implementation(Ktor.clientJs) implementation(MVIKotlin.mvikotlin) implementation(MVIKotlin.coroutines) implementation(MVIKotlin.mvikotlinMain) @@ -33,7 +34,8 @@ dependencies { } kotlin { - js { + js() { + //useCommonJs() browser { webpackTask { cssSupport.enabled = true diff --git a/web-app/src/main/kotlin/App.kt b/web-app/src/main/kotlin/App.kt index 08ce4c41..3b3ffaa6 100644 --- a/web-app/src/main/kotlin/App.kt +++ b/web-app/src/main/kotlin/App.kt @@ -18,7 +18,8 @@ external interface AppProps : RProps { var dependencies: AppDependencies } -fun RBuilder.app(attrs: AppProps.() -> Unit): ReactElement { +@Suppress("FunctionName") +fun RBuilder.App(attrs: AppProps.() -> Unit): ReactElement { return child(App::class){ this.attrs(attrs) } diff --git a/web-app/src/main/kotlin/client.kt b/web-app/src/main/kotlin/client.kt index 4c93919d..0f0ddc45 100644 --- a/web-app/src/main/kotlin/client.kt +++ b/web-app/src/main/kotlin/client.kt @@ -5,15 +5,16 @@ import com.shabinder.common.di.initKoin import react.dom.render import kotlinx.browser.document import kotlinx.browser.window -import navbar.navBar +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.get fun main() { window.onload = { render(document.getElementById("root")) { - navBar {} - app { + App { dependencies = AppDependencies } } @@ -21,6 +22,7 @@ fun main() { } object AppDependencies : KoinComponent { + val appScope = CoroutineScope(Dispatchers.Default) val logger: Kermit val directories: Dir val fetchPlatformQueryResult: FetchPlatformQueryResult @@ -29,5 +31,8 @@ object AppDependencies : KoinComponent { directories = get() logger = get() fetchPlatformQueryResult = get() + appScope.launch { + //fetchPlatformQueryResult.spotifyProvider.authenticateSpotifyClient(true) + } } } \ No newline at end of file diff --git a/web-app/src/main/kotlin/home/HomeScreen.kt b/web-app/src/main/kotlin/home/HomeScreen.kt index 012c09a8..a51ab5d3 100644 --- a/web-app/src/main/kotlin/home/HomeScreen.kt +++ b/web-app/src/main/kotlin/home/HomeScreen.kt @@ -3,12 +3,7 @@ package home import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain.State import extras.RenderableComponent -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch import kotlinx.css.* import react.* import styled.css @@ -23,17 +18,6 @@ class HomeScreen( override val stateFlow: Flow = model.models - override fun componentDidMount() { - if(!scope.isActive) - scope = CoroutineScope(Dispatchers.Default) - scope.launch { - stateFlow.collect { - println("Updating State = $it") - setState { data = it } - } - } - } - override fun RBuilder.render() { println("Rendering New State = \"${state.data}\" ") styledDiv{ @@ -50,7 +34,6 @@ class HomeScreen( } SearchBar { - println("Search Props ${state.data.link}") link = state.data.link search = model::onLinkSearch onLinkChange = model::onInputLinkChanged diff --git a/web-app/src/main/kotlin/home/Searchbar.kt b/web-app/src/main/kotlin/home/Searchbar.kt index 8d31ed62..126581e9 100644 --- a/web-app/src/main/kotlin/home/Searchbar.kt +++ b/web-app/src/main/kotlin/home/Searchbar.kt @@ -33,7 +33,6 @@ val searchbar = functionalComponent("SearchBar"){ props -> onChangeFunction = { val target = it.target as HTMLInputElement props.onLinkChange(target.value) - println(target.value) } value = props.link } diff --git a/web-app/src/main/kotlin/list/CoverImage.kt b/web-app/src/main/kotlin/list/CoverImage.kt index 7a5294e0..ead5f3a3 100644 --- a/web-app/src/main/kotlin/list/CoverImage.kt +++ b/web-app/src/main/kotlin/list/CoverImage.kt @@ -2,9 +2,7 @@ package list import kotlinx.css.* import kotlinx.html.id -import react.RProps -import react.rFunction -import react.useState +import react.* import styled.css import styled.styledDiv import styled.styledH1 @@ -16,19 +14,25 @@ external interface CoverImageProps : RProps { var coverName: String } -val CoverImage = rFunction("CoverImage"){ props -> - val (coverURL,setCoverURL) = useState(props.coverImageURL) - val (coverName,setCoverName) = useState(props.coverName) +@Suppress("FunctionName") +fun RBuilder.CoverImage(handler: CoverImageProps.() -> Unit): ReactElement { + return child(coverImage){ + attrs { + handler() + } + } +} +private val coverImage = functionalComponent("CoverImage"){ props -> styledDiv { - styledImg(src=coverURL){ + styledImg(src= props.coverImageURL){ css { - height = 300.px - width = 300.px + height = 220.px + width = 220.px } } styledH1 { - +coverName + +props.coverName css { textAlign = TextAlign.center } @@ -40,6 +44,7 @@ val CoverImage = rFunction("CoverImage"){ props -> display = Display.flex alignItems = Align.center flexDirection = FlexDirection.column + marginTop = 12.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 new file mode 100644 index 00000000..489760aa --- /dev/null +++ b/web-app/src/main/kotlin/list/DownloadAllButton.kt @@ -0,0 +1,58 @@ +package list + +import kotlinx.css.* +import kotlinx.html.id +import react.* +import styled.css +import styled.styledDiv +import styled.styledH5 +import styled.styledImg + +external interface DownloadAllButtonProps : RProps { + var isActive:Boolean +} + +@Suppress("FunctionName") +fun RBuilder.DownloadAllButton(handler: DownloadAllButtonProps.() -> Unit): ReactElement { + return child(downloadAllButton){ + attrs { + handler() + } + } +} + +private val downloadAllButton = functionalComponent("DownloadAllButton") { props-> + styledDiv { + 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 = if(props.isActive) Display.flex else Display.none + alignItems = Align.center + } + } +} diff --git a/web-app/src/main/kotlin/list/ListScreen.kt b/web-app/src/main/kotlin/list/ListScreen.kt index 34ae286c..8a5aa508 100644 --- a/web-app/src/main/kotlin/list/ListScreen.kt +++ b/web-app/src/main/kotlin/list/ListScreen.kt @@ -1,40 +1,58 @@ package list import com.shabinder.common.list.SpotiFlyerList +import com.shabinder.common.list.SpotiFlyerList.State import extras.RenderableComponent import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map import kotlinx.css.* import kotlinx.html.id import react.RBuilder -import react.RState import styled.css import styled.styledDiv class ListScreen( props: Props, -) : RenderableComponent(props,initialState = State(SpotiFlyerList.State())) { +) : RenderableComponent(props,initialState = State()) { - override val stateFlow: Flow = model.models.map { State(it) } + override val stateFlow: Flow = model.models override fun RBuilder.render() { + + val result = state.data.queryResult + styledDiv { attrs { id = "list-screen-div" } + + if(result == null){ + LoadingAnim { } + }else{ + CoverImage { + coverImageURL = result.coverUrl + coverName = result.title + } + + DownloadAllButton { + isActive = state.data.trackList.isNotEmpty() + } + + state.data.trackList.forEachIndexed{ index, trackDetails -> + TrackItem { + details = trackDetails + downloadTrack = model::onDownloadClicked + } + } + } + css { classes = mutableListOf("list-screen") display = Display.flex flexDirection = FlexDirection.column flexGrow = 1.0 justifyContent = JustifyContent.center - alignItems = Align.center - backgroundColor = Color.white + alignItems = Align.stretch } } } - - class State( - var data: SpotiFlyerList.State - ):RState } \ No newline at end of file diff --git a/web-app/src/main/kotlin/list/LoadingAnim.kt b/web-app/src/main/kotlin/list/LoadingAnim.kt new file mode 100644 index 00000000..dedab437 --- /dev/null +++ b/web-app/src/main/kotlin/list/LoadingAnim.kt @@ -0,0 +1,37 @@ +package list + +import kotlinx.css.* +import react.* +import styled.css +import styled.styledDiv + + +@Suppress("FunctionName") +fun RBuilder.LoadingAnim(handler: RProps.() -> Unit): ReactElement { + return child(loadingAnim){ + attrs { + handler() + } + } +} + +private val loadingAnim = functionalComponent("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") } } + + css { + classes = mutableListOf("sk-cube-grid") + height = 60.px + width = 60.px + } + } +} \ No newline at end of file diff --git a/web-app/src/main/kotlin/list/TrackItem.kt b/web-app/src/main/kotlin/list/TrackItem.kt index 269e9198..a0ec5739 100644 --- a/web-app/src/main/kotlin/list/TrackItem.kt +++ b/web-app/src/main/kotlin/list/TrackItem.kt @@ -1,13 +1,107 @@ package list -import react.RProps -import react.rFunction +import com.shabinder.common.models.TrackDetails +import kotlinx.css.* +import kotlinx.html.id +import kotlinx.html.js.onClickFunction +import react.* +import styled.* external interface TrackItemProps : RProps { - var coverImageURL: String - var coverName: String + var details:TrackDetails + var downloadTrack:(TrackDetails)->Unit } -val trackItem = rFunction("Track-Item"){ - +@Suppress("FunctionName") +fun RBuilder.TrackItem(handler: TrackItemProps.() -> Unit): ReactElement { + return child(trackItem){ + attrs { + handler() + } + } +} + +private val trackItem = functionalComponent("Track-Item"){ props -> + val details = props.details + styledDiv { + + styledImg(src = details.albumArtURL) { + css { + height = 90.px + width = 90.px + } + } + + styledDiv { + attrs { + id = "text-details" + } + styledDiv { + styledH3 { + + details.title + css { + padding(8.px) + } + } + css { + height = 40.px + display =Display.flex + alignItems = Align.center + } + } + styledDiv { + styledH4 { + + details.artists.joinToString(",") + css { + flexGrow = 1.0 + padding(8.px) + } + } + styledH4 { + + "${details.durationSec} sec" + css { + flexGrow = 1.0 + padding(8.px) + textAlign = TextAlign.right + } + } + 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 = { + props.downloadTrack(details) + } + } + css { + margin(8.px) + } + } + css { + classes = mutableListOf("glow-button") + borderRadius = 100.px + width = 65.px + } + } + + css { + alignItems = Align.center + display =Display.flex + flexDirection = FlexDirection.row + flexGrow = 1.0 + color = Color.white + } + } } diff --git a/web-app/src/main/kotlin/navbar/NavBar.kt b/web-app/src/main/kotlin/navbar/NavBar.kt index 3a74e4b3..4d0c0d75 100644 --- a/web-app/src/main/kotlin/navbar/NavBar.kt +++ b/web-app/src/main/kotlin/navbar/NavBar.kt @@ -6,51 +6,60 @@ import react.* import styled.* -fun RBuilder.navBar(attrs: RProps.() -> Unit): ReactElement{ - return child(NavBar::class){ - this.attrs(attrs) +@Suppress("FunctionName") +fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{ + return child(navBar){ + attrs { + handler() + } } } -@OptIn(ExperimentalJsExport::class) -@JsExport -class NavBar : RComponent() { +external interface NavBarProps:RProps{ + var isBackVisible: Boolean +} - override fun RBuilder.render() { - styledNav { + +private val navBar = functionalComponent("NavBar") { props -> + styledNav { + css { + +NavBarStyles.nav + } + styledImg(src = "left-arrow.svg",alt = "Back Arrow"){ css { - +NavBarStyles.nav + height = 42.px + width = 42.px + display = if(props.isBackVisible) Display.inline else Display.none + filter = "invert(100)" + marginRight = 12.px } - styledImg { - attrs { - src = "spotiflyer.svg" - } + } + 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) + } + } + styledA(href = "https://github.com/Shabinder/SpotiFlyer/"){ + styledImg(src = "github.svg"){ css { height = 42.px width = 42.px } } - styledH1 { - +"SpotiFlyer" - attrs { - id = "appName" - } - css{ - fontSize = 46.px - margin(horizontal = 14.px) - } - } - styledA(href = "https://github.com/Shabinder/SpotiFlyer/"){ - styledImg(src = "github.svg"){ - css { - height = 42.px - width = 42.px - } - } - css { - marginLeft = LinearDimension.auto - } + css { + marginLeft = LinearDimension.auto } } } -} +} \ No newline at end of file diff --git a/web-app/src/main/kotlin/root/RootR.kt b/web-app/src/main/kotlin/root/RootR.kt index 1467c018..279e4220 100644 --- a/web-app/src/main/kotlin/root/RootR.kt +++ b/web-app/src/main/kotlin/root/RootR.kt @@ -6,7 +6,9 @@ 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 import react.RState @@ -18,6 +20,9 @@ class RootR(props: Props) : RenderableRootComponent renderableChild(HomeScreen::class, (component as Child.Main).component) is Child.List -> renderableChild(ListScreen::class, (component as Child.List).component) diff --git a/web-app/src/main/resources/download-gradient.svg b/web-app/src/main/resources/download-gradient.svg new file mode 100644 index 00000000..d74dc881 --- /dev/null +++ b/web-app/src/main/resources/download-gradient.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web-app/src/main/resources/download.svg b/web-app/src/main/resources/download.svg new file mode 100644 index 00000000..1822dce0 --- /dev/null +++ b/web-app/src/main/resources/download.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web-app/src/main/resources/header-dark.jpg b/web-app/src/main/resources/header-dark.jpg new file mode 100644 index 0000000000000000000000000000000000000000..48607e87b51085bf4c047673a6b290bdf3e1bd84 GIT binary patch literal 71066 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�Ew%&oe!HpqK$^^PcogzMty2&DYsZUO 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 literal 0 HcmV?d00001 diff --git a/web-app/src/main/resources/left-arrow.svg b/web-app/src/main/resources/left-arrow.svg new file mode 100644 index 00000000..da079752 --- /dev/null +++ b/web-app/src/main/resources/left-arrow.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web-app/src/main/resources/styles.css b/web-app/src/main/resources/styles.css index 356e2828..9ddd95ef 100644 --- a/web-app/src/main/resources/styles.css +++ b/web-app/src/main/resources/styles.css @@ -2,19 +2,21 @@ font-family: pristine; src: url("pristine_script.ttf"); } +html { + background-image: url("header-dark.jpg"); + font-family: Lora,Helvetica Neue,Helvetica,Arial,sans-serif; +} body, html { width: 100%; height: 100%; margin: 0; - font-family: Lora,Helvetica Neue,Helvetica,Arial,sans-serif; + background-repeat: no-repeat; + background-size: 100% 100%; + background-attachment: fixed; position: relative; color: #fff; caret-color: crimson; - background-color: black; /*background-image: linear-gradient(180deg,rgba(221,0,221,0.50),rgba(221,0,221,0.32),rgba(221,0,221,0.16),rgba(221,0,221,0.08),rgba(221,0,221,0.04),rgba(0,0,0,0));*/ - background-image: url("header.png"); - background-repeat: no-repeat; - background-size: 100% 100%; } #appName{ font-family: pristine, cursive; @@ -90,7 +92,105 @@ body, html { line-height: 40px; width: 0px; } +#download-all-text { + color: black; + display: none; +} +.download-button { + font-size: 1.5rem; + border: 2px solid white; + border-radius: 100px; + width: 40px; + height: 40px; + padding: 5px; + margin: 12px auto; + transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); + justify-content: center; +} +.download-button:hover { + width: 150px; + background-color: white; + box-shadow: 0px 5px 5px rgba(0, 0, 0, 0.2); + color: black; + transition: 0.3s; + justify-content: flex-start; +} + +.download-button:hover #download-all-text { + display: inline; + color: black; +} + +.download-button:hover .download-all-icon { + filter: none; + margin-right: 8px; +} +.download-button:not(hover) .download-all-icon { + filter: invert(100); +} + +.sk-cube-grid { + width: 40px; + height: 40px; + margin: 100px auto; +} + +.sk-cube-grid .sk-cube { + width: 33%; + height: 33%; + background-color: rgb(240, 90, 220); + float: left; + -webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; + animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; +} +.sk-cube-grid .sk-cube1 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; } +.sk-cube-grid .sk-cube2 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; } +.sk-cube-grid .sk-cube3 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; } +.sk-cube-grid .sk-cube4 { + -webkit-animation-delay: 0.1s; + animation-delay: 0.1s; } +.sk-cube-grid .sk-cube5 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; } +.sk-cube-grid .sk-cube6 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; } +.sk-cube-grid .sk-cube7 { + -webkit-animation-delay: 0s; + animation-delay: 0s; } +.sk-cube-grid .sk-cube8 { + -webkit-animation-delay: 0.1s; + animation-delay: 0.1s; } +.sk-cube-grid .sk-cube9 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; } + +@-webkit-keyframes sk-cubeGridScaleDelay { + 0%, 70%, 100% { + -webkit-transform: scale3D(1, 1, 1); + transform: scale3D(1, 1, 1); + } 35% { + -webkit-transform: scale3D(0, 0, 1); + transform: scale3D(0, 0, 1); + } +} + +@keyframes sk-cubeGridScaleDelay { + 0%, 70%, 100% { + -webkit-transform: scale3D(1, 1, 1); + transform: scale3D(1, 1, 1); + } 35% { + -webkit-transform: scale3D(0, 0, 1); + transform: scale3D(0, 0, 1); + } +} @media screen and (max-width: 620px) { .searchBox:hover > .searchInput { width: 150px;