diff --git a/api/README.md b/api/README.md index 34aaed0..ddf7c4a 100644 --- a/api/README.md +++ b/api/README.md @@ -76,12 +76,14 @@ Gets a (paginated) list of all C/C++(-like) types used by the intrinsics. The ty Searches the database using the given filters. The filters are passed as query parameters, and can be combined. All filters are optional. The following filters are available: - `name=[string]`: searches based on the name of the intrinsic; employs fuzzy-search (using `LIKE %it%`) - `return=[string]`: searches based on the return type of the intrinsic; exact search only -- `cpuid=[string]`: searches based on the CPUID of the intrinsic; exact search only -- `tech=[string]`: searches based on the technology of the intrinsic; exact search only -- `category=[string]`: searches based on the category of the intrinsic; exact search only +- `cpuid=[*]`: searches based on the CPUID of the intrinsic; exact search only +- `tech=[*]`: searches based on the technology of the intrinsic; exact search only +- `category=[*]`: searches based on the category of the intrinsic; exact search only - `desc=[string]`: searches based on the description of the intrinsic; employs fuzzy-search (using `LIKE %it%`) - `page=[int]`: specifies the page number to return (default is 0) +Parameters marked by `[*]` are JSON-lists (so you should pass them as `cpuid=["PREFETCHI", "SSE2"]`). They are considered to be OR-ed together (i.e. the results will contain a mix of all intrinsics matching either of the CPUIDs (from the example)). + Passing no filters is equivalent to using `GET /all`, and data is returned in the same format: ```json { @@ -130,4 +132,14 @@ Gets the details for a single, specific intrinsic. The following data is returne } ] } -``` \ No newline at end of file +``` + +### `GET /version` +Gets version information for the data. The following data is returned: +```json +{ + "intelVersion": "M.m.p (Major.minor.patch version as reported by Intel)", + "intelUpdate": "yyyy-MM-dd (date of Intel's last update prior to scraping)", + "scrapeDate": "yyyy-MM-dd (date of last update)" +} +``` diff --git a/api/src/main/kotlin/data/Loader.kt b/api/src/main/kotlin/data/Loader.kt index 872b259..7f3cc4a 100644 --- a/api/src/main/kotlin/data/Loader.kt +++ b/api/src/main/kotlin/data/Loader.kt @@ -1,6 +1,7 @@ package com.jaytux.simd.data import com.fleeksoft.ksoup.Ksoup +import com.fleeksoft.ksoup.parser.Parser import com.jaytux.simd.data.IntrinsicInstructions.xed import kotlinx.coroutines.coroutineScope import kotlinx.datetime.* @@ -84,6 +85,7 @@ object Loader { suspend fun loadXml(xmlFile: String): XmlData = coroutineScope { val xml = Ksoup.parseXml(File(xmlFile).readText(Charsets.UTF_8)) + Parser.xmlParser() val cppTypes = mutableSetOf() val techs = mutableSetOf() @@ -158,13 +160,13 @@ object Loader { args += argName to type } - val desc = it.getElementsByTag("description").firstOrNull()?.text() + val desc = it.getElementsByTag("description").firstOrNull()?.wholeText()?.trim() if(desc == null) { errors += "Missing description element for intrinsic $name" return@forEachIndexed } - val op = it.getElementsByTag("operation").firstOrNull()?.text() + val op = it.getElementsByTag("operation").firstOrNull()?.wholeText()?.trim() val insn = mutableListOf>() it.getElementsByTag("instruction").forEachIndexed { i, ins -> diff --git a/api/src/main/kotlin/server/Endpoints.kt b/api/src/main/kotlin/server/Endpoints.kt index e2a65b8..15dcd66 100644 --- a/api/src/main/kotlin/server/Endpoints.kt +++ b/api/src/main/kotlin/server/Endpoints.kt @@ -4,13 +4,17 @@ import com.jaytux.simd.data.* import com.jaytux.simd.server.RouteCache.register import io.ktor.http.* import io.ktor.http.content.* +import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.datetime.LocalDate import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.like -import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.transactions.transaction import java.util.* @@ -60,32 +64,46 @@ fun Routing.installGetAll() { fun Routing.installSearch() { getPagedRequest("/search", "Search for intrinsics matching certain filters", { 100 }, { IntrinsicSummary(it[Intrinsics.id].value, it[Intrinsics.mnemonic]) }) { + fun resolveAny(key: String, finder: (String) -> SizedIterable): List>? { + return call.request.queryParameters[key]?.let { + try { + val list = Json.decodeFromString>(it) + list.map { + finder(it).firstOrNull()?.id + ?: throw HttpError("Unknown $key: ${list.joinToString(", ")}") + } + } + catch(e: HttpError) { + throw e + } + catch(e: Exception) { + throw HttpError("Malformed $key parameter: $it") + } + } + } + val name = call.request.queryParameters["name"] val returnType = call.request.queryParameters["return"]?.let { CppType.find { CppTypes.name eq it }.firstOrNull() ?: throw HttpError("Unknown return type: $it") } - val cpuid = call.request.queryParameters["cpuid"]?.let { - CPUID.find { CPUIDs.name eq it }.firstOrNull() - ?: throw HttpError("Unknown CPUID: $it") - } - val tech = call.request.queryParameters["tech"]?.let { - Tech.find { Techs.name eq it }.firstOrNull() - ?: throw HttpError("Unknown tech: $it") - } - val category = call.request.queryParameters["category"]?.let { - Category.find { Categories.name eq it }.firstOrNull() - ?: throw HttpError("Unknown category: $it") - } + val anyCpuid = resolveAny("cpuid") { CPUID.find { CPUIDs.name eq it } } + val anyTech = resolveAny("tech") { Tech.find { Techs.name eq it } } + val anyCat = resolveAny("category") { Category.find { Categories.name eq it } } val desc = call.request.queryParameters["desc"] - var results = Intrinsics.selectAll() - name?.let { results = results.where { Intrinsics.mnemonic like "%$it%" } } - returnType?.let { results = results.where { Intrinsics.returnType eq it.id } } - cpuid?.let { results = results.where { Intrinsics.cpuid eq it.id } } - tech?.let { results = results.where { Intrinsics.tech eq it.id } } - category?.let { results = results.where { Intrinsics.category eq it.id } } - desc?.let { results = results.where { Intrinsics.description like "%$it%" } } + var results = Intrinsics.selectAll().where { + val build = listOf( + name?.let { Intrinsics.mnemonic like "%$it%" }, + returnType?.let { Intrinsics.returnType eq it.id }, + anyCpuid?.let { Intrinsics.cpuid inList it }, + anyTech?.let { Intrinsics.tech inList it }, + anyCat?.let { Intrinsics.category inList it }, + desc?.let { Intrinsics.description like "%$it%" } + ).filterNotNull() + + build.fold(Op.TRUE as Op, { acc, op -> op and acc }) + } results.orderAsc(Intrinsics.mnemonic) } diff --git a/api/src/main/resources/logback.xml b/api/src/main/resources/logback.xml index 2e46a79..97606fb 100644 --- a/api/src/main/resources/logback.xml +++ b/api/src/main/resources/logback.xml @@ -8,7 +8,7 @@ - + - \ No newline at end of file + diff --git a/frontend/.fleet/receipt.json b/frontend/.fleet/receipt.json deleted file mode 100644 index e7fffc1..0000000 --- a/frontend/.fleet/receipt.json +++ /dev/null @@ -1,19 +0,0 @@ -// Project generated by Kotlin Multiplatform Wizard -{ - "spec": { - "template_id": "kmt", - "targets": { - "desktop": { - "ui": [ - "compose" - ] - }, - "web": { - "ui": [ - "compose" - ] - } - } - }, - "timestamp": "2025-04-25T08:32:22.469968407Z" -} \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore index 7d9c0e4..c554f29 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -16,3 +16,4 @@ captures !*.xcodeproj/project.xcworkspace/ !*.xcworkspace/contents.xcworkspacedata **/xcshareddata/WorkspaceSettings.xcsettings +**/.env diff --git a/frontend/composeApp/build.gradle.kts b/frontend/composeApp/build.gradle.kts index e892cf8..33a3b0a 100644 --- a/frontend/composeApp/build.gradle.kts +++ b/frontend/composeApp/build.gradle.kts @@ -47,12 +47,14 @@ kotlin { implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.ktor.client.core) implementation(libs.ktor.client.logging) + implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.serialization.kotlinx.json) - implementation(libs.dotenv) implementation(libs.json) implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.serialization.json) implementation(libs.logback.classic) + implementation(libs.material3) + implementation(libs.lucide) } desktopMain.dependencies { implementation(compose.desktop.currentOs) @@ -65,7 +67,6 @@ kotlin { } } - compose.desktop { application { mainClass = "com.jaytux.simd.frontend.MainKt" @@ -77,3 +78,7 @@ compose.desktop { } } } + +compose.resources { + // +} \ No newline at end of file diff --git a/frontend/composeApp/src/commonMain/composeResources/font/AlegreyaBold.ttf b/frontend/composeApp/src/commonMain/composeResources/font/AlegreyaBold.ttf new file mode 100644 index 0000000..fe6306a Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/AlegreyaBold.ttf differ diff --git a/frontend/composeApp/src/commonMain/composeResources/font/AlegreyaItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/AlegreyaItalic.ttf new file mode 100644 index 0000000..20ea1d7 Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/AlegreyaItalic.ttf differ diff --git a/frontend/composeApp/src/commonMain/composeResources/font/AlegreyaMedium.ttf b/frontend/composeApp/src/commonMain/composeResources/font/AlegreyaMedium.ttf new file mode 100644 index 0000000..d04ac69 Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/AlegreyaMedium.ttf differ diff --git a/frontend/composeApp/src/commonMain/composeResources/font/RobotoMonoBold.ttf b/frontend/composeApp/src/commonMain/composeResources/font/RobotoMonoBold.ttf new file mode 100644 index 0000000..d884128 Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/RobotoMonoBold.ttf differ diff --git a/frontend/composeApp/src/commonMain/composeResources/font/RobotoMonoItalic.ttf b/frontend/composeApp/src/commonMain/composeResources/font/RobotoMonoItalic.ttf new file mode 100644 index 0000000..61e5303 Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/RobotoMonoItalic.ttf differ diff --git a/frontend/composeApp/src/commonMain/composeResources/font/RobotoMonoMedium.ttf b/frontend/composeApp/src/commonMain/composeResources/font/RobotoMonoMedium.ttf new file mode 100644 index 0000000..f6c149a Binary files /dev/null and b/frontend/composeApp/src/commonMain/composeResources/font/RobotoMonoMedium.ttf differ diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/App.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/App.kt index 3b2f0b1..3b49883 100644 --- a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/App.kt +++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/App.kt @@ -1,36 +1,32 @@ package com.jaytux.simd.frontend -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.* import androidx.compose.runtime.* -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import org.jetbrains.compose.resources.painterResource +import com.jaytux.simd.frontend.theme.SimdTheme +import com.jaytux.simd.frontend.ui.DataState +import com.jaytux.simd.frontend.ui.ErrorSnackBar +import com.jaytux.simd.frontend.ui.topBar import org.jetbrains.compose.ui.tooling.preview.Preview -import frontend.composeapp.generated.resources.Res -import frontend.composeapp.generated.resources.compose_multiplatform - @Composable @Preview fun App() { - MaterialTheme { - var showContent by remember { mutableStateOf(false) } - Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { - Button(onClick = { showContent = !showContent }) { - Text("Click me!") - } - AnimatedVisibility(showContent) { - val greeting = remember { Greeting().greet() } - Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { - Image(painterResource(Res.drawable.compose_multiplatform), null) - Text("Compose: $greeting") - } + SimdTheme { + val scope = rememberCoroutineScope() + val snackState = remember { SnackbarHostState() } + val data = DataState(scope, { + println(it) + snackState.showSnackbar(it, duration = SnackbarDuration.Short) + }) + + Scaffold( + topBar = { topBar(data) }, + snackbarHost = { SnackbarHost(snackState) { ErrorSnackBar(it) } } + ) { + Surface(Modifier.padding(top = it.calculateTopPadding())) { + mainView(data) } } } diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Greeting.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Greeting.kt deleted file mode 100644 index b324c95..0000000 --- a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Greeting.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.jaytux.simd.frontend - -class Greeting { - private val platform = getPlatform() - - fun greet(): String { - return "Hello, ${platform.name}!" - } -} \ No newline at end of file diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/OrError.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/OrError.kt new file mode 100644 index 0000000..160202b --- /dev/null +++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/OrError.kt @@ -0,0 +1,34 @@ +package com.jaytux.simd.frontend + +import androidx.compose.runtime.Composable + +sealed class Either { + class Left(val value: L) : Either() + class Right(val value: R) : Either() + + fun fold(onLeft: (L) -> T, onRight: (R) -> T): T = when(this) { + is Left -> onLeft(value) + is Right -> onRight(value) + } + + suspend fun foldSuspend(onLeft: suspend (L) -> T, onRight: suspend (R) -> T): T = when(this) { + is Left -> onLeft(value) + is Right -> onRight(value) + } + + @Composable + fun foldCompose(onLeft: @Composable (L) -> T, onRight: @Composable (R) -> T): T = when(this) { + is Left -> onLeft(value) + is Right -> onRight(value) + } +} + +fun T.left() = Either.Left(this) +fun T.right() = Either.Right(this) + +fun Either.map(f: (R) -> X): Either = when(this) { + is Either.Left -> this + is Either.Right -> Either.Right(f(value)) +} + +typealias OrError = Either \ No newline at end of file diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Platform.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Platform.kt index c51c9ff..06ed0dc 100644 --- a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Platform.kt +++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Platform.kt @@ -1,7 +1,10 @@ package com.jaytux.simd.frontend -interface Platform { - val name: String -} +import androidx.compose.runtime.Composable +import com.jaytux.simd.frontend.ui.DataState +import io.ktor.client.* -expect fun getPlatform(): Platform \ No newline at end of file +expect fun getKtorClient(builder: HttpClientConfig<*>.() -> Unit): HttpClient + +@Composable +expect fun mainView(data: DataState): Unit \ No newline at end of file diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Util.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Util.kt new file mode 100644 index 0000000..aef5697 --- /dev/null +++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/Util.kt @@ -0,0 +1,6 @@ +package com.jaytux.simd.frontend + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State + +fun MutableState.immutable(): State = this \ No newline at end of file diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Client.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Client.kt new file mode 100644 index 0000000..45c7b6f --- /dev/null +++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Client.kt @@ -0,0 +1,188 @@ +package com.jaytux.simd.frontend.client + +import com.jaytux.simd.frontend.* +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.delay +import kotlinx.datetime.LocalDate +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.Json +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +@OptIn(ExperimentalUuidApi::class) +object Client { + val httpClient: HttpClient by lazy { getKtorClient{ install(ContentNegotiation) { json() } } } + val baseUrl = "https://simd.jaytux.com/api" + + object UUIDSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Uuid = try { + Uuid.parse(decoder.decodeString()) + } catch (e: IllegalArgumentException) { + throw IllegalArgumentException("Invalid UUID format") + } + + override fun serialize(encoder: Encoder, value: Uuid) = encoder.encodeString(value.toString()) + } + + @Serializable + data class IntrinsicSummary constructor(@Serializable(with = UUIDSerializer::class) val id: Uuid, val name: String) + + @Serializable + data class Param(val name: String, val type: String) + + @Serializable + data class Instruction(val mnemonic: String, val xed: String, val form: String?) + + @Serializable + data class PlatformPerformance(val platform: String, val latency: Float?, val throughput: Float?) + + @Serializable + data class IntrinsicDetails( + @Serializable(with = UUIDSerializer::class) val id: Uuid, + val name: String, + val returnType: String, + val returnVar: String?, + val description: String, + val operations: String?, + val category: String, + val cpuid: String?, + val tech: String, + val params: List, + val instructions: List?, + val performance: List? + ) + + @Serializable + data class Versioning( + val intelVersion: String, val intelUpdate: LocalDate, val scrapeDate: LocalDate + ) + + @Serializable data class Paginated(val page: Long, val totalPages: Long, val items: List) + + interface PaginatedResponse { + fun currentPage(): Long + fun totalPages(): Long + fun currentItems(): List + fun hasNextPage(): Boolean + suspend fun loadNextPage(): OrError + } + + private inline suspend fun throttle(millis: Long, crossinline block: suspend () -> T): T { + delay(millis) + return block() + } + + private suspend inline fun makeBasicRequest(url: String, crossinline builder: URLBuilder.() -> Unit = {}): OrError = try { + val resp = httpClient.get(url) { + header("Accept", "application/json") + expectSuccess = true + url { builder() } + } + resp.body().right() + } catch (e: Exception) { + "Failed to load $url: ${e.message ?: "Unknown error"}".left() + } + + private suspend inline fun getPaginated(url: String): OrError> { + val loader: suspend (Long?) -> OrError> = { page: Long? -> + makeBasicRequest>(url + (if (page != null) "/$page" else "")) + } + + return loader(null).map { p0 -> + object : PaginatedResponse { + var page = p0.page + val total = p0.totalPages + var current = p0.items + + override fun currentItems(): List = current + override fun hasNextPage(): Boolean = page < total + override fun currentPage(): Long = page + override fun totalPages(): Long = total + + override suspend fun loadNextPage(): OrError { + if(hasNextPage()) { + return loader(page + 1).map { + page = it.page + current = it.items + } + } + else { + return "No more pages (for URL ${url})".left() + } + } + + } + } + } + + private suspend inline fun getPaginatedQuery(url: String, crossinline addParams: URLBuilder.() -> Unit = {}): OrError> { + val loader: suspend (Long?) -> OrError> = { page: Long? -> + makeBasicRequest>(url) { + addParams() + if(page != null) parameters.append("page", "$page") + } + } + + return loader(null).map { p0 -> + object : PaginatedResponse { + var page = p0.page + val total = p0.totalPages + var current = p0.items + + override fun currentItems(): List = current + override fun hasNextPage(): Boolean = page < total + override fun currentPage(): Long = page + override fun totalPages(): Long = total + + override suspend fun loadNextPage(): OrError { + if(hasNextPage()) { + return loader(page + 1).map { + page = it.page + current = it.items + } + } + else { + return "No more pages (for URL ${url})".left() + } + } + + } + } + } + + suspend fun getAll(): OrError> = getPaginated("$baseUrl/all") + suspend fun getCpuid(): OrError> = getPaginated("$baseUrl/cpuid") + suspend fun getTech(): OrError> = getPaginated("$baseUrl/tech") + suspend fun getCategory(): OrError> = getPaginated("$baseUrl/category") + suspend fun getTypes(): OrError> = getPaginated("$baseUrl/types") + + suspend fun getSearch( + name: String? = null, returnT: String? = null, cpuid: List = listOf(), + tech: List = listOf(), category: List = listOf(), desc: String? = null + ): OrError> = getPaginatedQuery("$baseUrl/search") { + if(name != null) parameters.append("name", name) + if(returnT != null) parameters.append("returnT", returnT) + if(cpuid.isNotEmpty()) parameters.append("cpuid", Json.encodeToString(cpuid)) + if(tech.isNotEmpty()) parameters.append("tech", Json.encodeToString(tech)) + if(category.isNotEmpty()) parameters.append("category", Json.encodeToString(category)) + if(desc != null) parameters.append("desc", desc) + } + + suspend fun getDetails(id: Uuid): OrError = makeBasicRequest("$baseUrl/details/$id") + suspend fun getVersion(): OrError = makeBasicRequest("$baseUrl/version") +} \ No newline at end of file diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Loader.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Loader.kt new file mode 100644 index 0000000..18dddad --- /dev/null +++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/client/Loader.kt @@ -0,0 +1,53 @@ +package com.jaytux.simd.frontend.client + +import androidx.compose.runtime.mutableStateOf +import com.jaytux.simd.frontend.OrError +import com.jaytux.simd.frontend.immutable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +object Loader { + val progressInit = "Initializing..." + val progressFailed = "Finished (failed)" + val progressFinished = "Finished" + + fun CoroutineScope.loadAll( + client: suspend () -> OrError>, + onError: suspend (String) -> Unit, + onFinishSuccess: suspend (List) -> Unit, + onStatusChange: suspend (String) -> Unit, + onProgress: suspend (List) -> Unit = {}, + throttleBefore: Long = 0, + ) = launch { + delay(throttleBefore) // in case of UI-driven loading + + onStatusChange(progressInit) + + client().foldSuspend({ + onStatusChange(progressFailed) + onError(it) + }) { pg -> + onStatusChange("Loading ${pg.currentPage() + 1}/${pg.totalPages()}") + val res = pg.currentItems().toMutableList() + var anyError = false + + while (pg.hasNextPage() && !anyError) { + pg.loadNextPage().foldSuspend({ + onStatusChange(progressFailed) + onError(it) + anyError = true + }) { + onStatusChange("Loading ${pg.currentPage() + 1}/${pg.totalPages()}") + res.addAll(pg.currentItems()) + onProgress(res) + } + } + + if (!anyError) { + onStatusChange(progressFinished) + onFinishSuccess(res) + } + } + } +} \ No newline at end of file diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Color.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Color.kt new file mode 100644 index 0000000..eff9065 --- /dev/null +++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Color.kt @@ -0,0 +1,225 @@ +package com.jaytux.simd.frontend.theme +import androidx.compose.ui.graphics.Color + +val primaryLight = Color(0xFF005CB9) +val onPrimaryLight = Color(0xFFFFFFFF) +val primaryContainerLight = Color(0xFF5B9BFF) +val onPrimaryContainerLight = Color(0xFF00326A) +val secondaryLight = Color(0xFF176648) +val onSecondaryLight = Color(0xFFFFFFFF) +val secondaryContainerLight = Color(0xFF367F5F) +val onSecondaryContainerLight = Color(0xFFE3FFED) +val tertiaryLight = Color(0xFF8C3D9B) +val onTertiaryLight = Color(0xFFFFFFFF) +val tertiaryContainerLight = Color(0xFFD07BDE) +val onTertiaryContainerLight = Color(0xFF5A036C) +val errorLight = Color(0xFFBA1A1A) +val onErrorLight = Color(0xFFFFFFFF) +val errorContainerLight = Color(0xFFFFDAD6) +val onErrorContainerLight = Color(0xFF93000A) +val backgroundLight = Color(0xFFF9F9FF) +val onBackgroundLight = Color(0xFF191C22) +val surfaceLight = Color(0xFFF9F9FF) +val onSurfaceLight = Color(0xFF191C22) +val surfaceVariantLight = Color(0xFFDEE2F1) +val onSurfaceVariantLight = Color(0xFF424752) +val outlineLight = Color(0xFF727784) +val outlineVariantLight = Color(0xFFC2C6D4) +val scrimLight = Color(0xFF000000) +val inverseSurfaceLight = Color(0xFF2E3037) +val inverseOnSurfaceLight = Color(0xFFEFF0F9) +val inversePrimaryLight = Color(0xFFAAC7FF) +val surfaceDimLight = Color(0xFFD8D9E2) +val surfaceBrightLight = Color(0xFFF9F9FF) +val surfaceContainerLowestLight = Color(0xFFFFFFFF) +val surfaceContainerLowLight = Color(0xFFF2F3FC) +val surfaceContainerLight = Color(0xFFECEDF6) +val surfaceContainerHighLight = Color(0xFFE7E8F0) +val surfaceContainerHighestLight = Color(0xFFE1E2EB) + +val primaryLightMediumContrast = Color(0xFF00356F) +val onPrimaryLightMediumContrast = Color(0xFFFFFFFF) +val primaryContainerLightMediumContrast = Color(0xFF1D6BCC) +val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val secondaryLightMediumContrast = Color(0xFF003F29) +val onSecondaryLightMediumContrast = Color(0xFFFFFFFF) +val secondaryContainerLightMediumContrast = Color(0xFF307A5A) +val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val tertiaryLightMediumContrast = Color(0xFF5D096F) +val onTertiaryLightMediumContrast = Color(0xFFFFFFFF) +val tertiaryContainerLightMediumContrast = Color(0xFF9C4CAC) +val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val errorLightMediumContrast = Color(0xFF740006) +val onErrorLightMediumContrast = Color(0xFFFFFFFF) +val errorContainerLightMediumContrast = Color(0xFFCF2C27) +val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF) +val backgroundLightMediumContrast = Color(0xFFF9F9FF) +val onBackgroundLightMediumContrast = Color(0xFF191C22) +val surfaceLightMediumContrast = Color(0xFFF9F9FF) +val onSurfaceLightMediumContrast = Color(0xFF0E1117) +val surfaceVariantLightMediumContrast = Color(0xFFDEE2F1) +val onSurfaceVariantLightMediumContrast = Color(0xFF313641) +val outlineLightMediumContrast = Color(0xFF4D525E) +val outlineVariantLightMediumContrast = Color(0xFF686D7A) +val scrimLightMediumContrast = Color(0xFF000000) +val inverseSurfaceLightMediumContrast = Color(0xFF2E3037) +val inverseOnSurfaceLightMediumContrast = Color(0xFFEFF0F9) +val inversePrimaryLightMediumContrast = Color(0xFFAAC7FF) +val surfaceDimLightMediumContrast = Color(0xFFC5C6CE) +val surfaceBrightLightMediumContrast = Color(0xFFF9F9FF) +val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF) +val surfaceContainerLowLightMediumContrast = Color(0xFFF2F3FC) +val surfaceContainerLightMediumContrast = Color(0xFFE7E8F0) +val surfaceContainerHighLightMediumContrast = Color(0xFFDBDCE5) +val surfaceContainerHighestLightMediumContrast = Color(0xFFD0D1DA) + +val primaryLightHighContrast = Color(0xFF002B5D) +val onPrimaryLightHighContrast = Color(0xFFFFFFFF) +val primaryContainerLightHighContrast = Color(0xFF004892) +val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF) +val secondaryLightHighContrast = Color(0xFF003321) +val onSecondaryLightHighContrast = Color(0xFFFFFFFF) +val secondaryContainerLightHighContrast = Color(0xFF005439) +val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF) +val tertiaryLightHighContrast = Color(0xFF4F0060) +val onTertiaryLightHighContrast = Color(0xFFFFFFFF) +val tertiaryContainerLightHighContrast = Color(0xFF732584) +val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF) +val errorLightHighContrast = Color(0xFF600004) +val onErrorLightHighContrast = Color(0xFFFFFFFF) +val errorContainerLightHighContrast = Color(0xFF98000A) +val onErrorContainerLightHighContrast = Color(0xFFFFFFFF) +val backgroundLightHighContrast = Color(0xFFF9F9FF) +val onBackgroundLightHighContrast = Color(0xFF191C22) +val surfaceLightHighContrast = Color(0xFFF9F9FF) +val onSurfaceLightHighContrast = Color(0xFF000000) +val surfaceVariantLightHighContrast = Color(0xFFDEE2F1) +val onSurfaceVariantLightHighContrast = Color(0xFF000000) +val outlineLightHighContrast = Color(0xFF272C37) +val outlineVariantLightHighContrast = Color(0xFF444955) +val scrimLightHighContrast = Color(0xFF000000) +val inverseSurfaceLightHighContrast = Color(0xFF2E3037) +val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF) +val inversePrimaryLightHighContrast = Color(0xFFAAC7FF) +val surfaceDimLightHighContrast = Color(0xFFB7B8C1) +val surfaceBrightLightHighContrast = Color(0xFFF9F9FF) +val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF) +val surfaceContainerLowLightHighContrast = Color(0xFFEFF0F9) +val surfaceContainerLightHighContrast = Color(0xFFE1E2EB) +val surfaceContainerHighLightHighContrast = Color(0xFFD3D4DC) +val surfaceContainerHighestLightHighContrast = Color(0xFFC5C6CE) + +val primaryDark = Color(0xFFAAC7FF) +val onPrimaryDark = Color(0xFF002F65) +val primaryContainerDark = Color(0xFF5B9BFF) +val onPrimaryContainerDark = Color(0xFF00326A) +val secondaryDark = Color(0xFF8CD6B0) +val onSecondaryDark = Color(0xFF003825) +val secondaryContainerDark = Color(0xFF367F5F) +val onSecondaryContainerDark = Color(0xFFE3FFED) +val tertiaryDark = Color(0xFFF6ADFF) +val onTertiaryDark = Color(0xFF560068) +val tertiaryContainerDark = Color(0xFFD07BDE) +val onTertiaryContainerDark = Color(0xFF5A036C) +val errorDark = Color(0xFFFFB4AB) +val onErrorDark = Color(0xFF690005) +val errorContainerDark = Color(0xFF93000A) +val onErrorContainerDark = Color(0xFFFFDAD6) +val backgroundDark = Color(0xFF111319) +val onBackgroundDark = Color(0xFFE1E2EB) +val surfaceDark = Color(0xFF111319) +val onSurfaceDark = Color(0xFFE1E2EB) +val surfaceVariantDark = Color(0xFF424752) +val onSurfaceVariantDark = Color(0xFFC2C6D4) +val outlineDark = Color(0xFF8C919E) +val outlineVariantDark = Color(0xFF424752) +val scrimDark = Color(0xFF000000) +val inverseSurfaceDark = Color(0xFFE1E2EB) +val inverseOnSurfaceDark = Color(0xFF2E3037) +val inversePrimaryDark = Color(0xFF005CB9) +val surfaceDimDark = Color(0xFF111319) +val surfaceBrightDark = Color(0xFF363940) +val surfaceContainerLowestDark = Color(0xFF0B0E14) +val surfaceContainerLowDark = Color(0xFF191C22) +val surfaceContainerDark = Color(0xFF1D2026) +val surfaceContainerHighDark = Color(0xFF272A30) +val surfaceContainerHighestDark = Color(0xFF32353B) + +val primaryDarkMediumContrast = Color(0xFFCDDDFF) +val onPrimaryDarkMediumContrast = Color(0xFF002551) +val primaryContainerDarkMediumContrast = Color(0xFF5B9BFF) +val onPrimaryContainerDarkMediumContrast = Color(0xFF000C22) +val secondaryDarkMediumContrast = Color(0xFFA1ECC5) +val onSecondaryDarkMediumContrast = Color(0xFF002C1C) +val secondaryContainerDarkMediumContrast = Color(0xFF569F7C) +val onSecondaryContainerDarkMediumContrast = Color(0xFF000000) +val tertiaryDarkMediumContrast = Color(0xFFFDCDFF) +val onTertiaryDarkMediumContrast = Color(0xFF450054) +val tertiaryContainerDarkMediumContrast = Color(0xFFD07BDE) +val onTertiaryContainerDarkMediumContrast = Color(0xFF1C0023) +val errorDarkMediumContrast = Color(0xFFFFD2CC) +val onErrorDarkMediumContrast = Color(0xFF540003) +val errorContainerDarkMediumContrast = Color(0xFFFF5449) +val onErrorContainerDarkMediumContrast = Color(0xFF000000) +val backgroundDarkMediumContrast = Color(0xFF111319) +val onBackgroundDarkMediumContrast = Color(0xFFE1E2EB) +val surfaceDarkMediumContrast = Color(0xFF111319) +val onSurfaceDarkMediumContrast = Color(0xFFFFFFFF) +val surfaceVariantDarkMediumContrast = Color(0xFF424752) +val onSurfaceVariantDarkMediumContrast = Color(0xFFD8DCEB) +val outlineDarkMediumContrast = Color(0xFFADB2C0) +val outlineVariantDarkMediumContrast = Color(0xFF8B909D) +val scrimDarkMediumContrast = Color(0xFF000000) +val inverseSurfaceDarkMediumContrast = Color(0xFFE1E2EB) +val inverseOnSurfaceDarkMediumContrast = Color(0xFF272A30) +val inversePrimaryDarkMediumContrast = Color(0xFF004690) +val surfaceDimDarkMediumContrast = Color(0xFF111319) +val surfaceBrightDarkMediumContrast = Color(0xFF42444B) +val surfaceContainerLowestDarkMediumContrast = Color(0xFF05070D) +val surfaceContainerLowDarkMediumContrast = Color(0xFF1B1E24) +val surfaceContainerDarkMediumContrast = Color(0xFF25282E) +val surfaceContainerHighDarkMediumContrast = Color(0xFF303339) +val surfaceContainerHighestDarkMediumContrast = Color(0xFF3B3E44) + +val primaryDarkHighContrast = Color(0xFFEBF0FF) +val onPrimaryDarkHighContrast = Color(0xFF000000) +val primaryContainerDarkHighContrast = Color(0xFFA4C3FF) +val onPrimaryContainerDarkHighContrast = Color(0xFF000B20) +val secondaryDarkHighContrast = Color(0xFFBAFFDA) +val onSecondaryDarkHighContrast = Color(0xFF000000) +val secondaryContainerDarkHighContrast = Color(0xFF88D2AD) +val onSecondaryContainerDarkHighContrast = Color(0xFF000E07) +val tertiaryDarkHighContrast = Color(0xFFFFEAFC) +val onTertiaryDarkHighContrast = Color(0xFF000000) +val tertiaryContainerDarkHighContrast = Color(0xFFF5A7FF) +val onTertiaryContainerDarkHighContrast = Color(0xFF1A0022) +val errorDarkHighContrast = Color(0xFFFFECE9) +val onErrorDarkHighContrast = Color(0xFF000000) +val errorContainerDarkHighContrast = Color(0xFFFFAEA4) +val onErrorContainerDarkHighContrast = Color(0xFF220001) +val backgroundDarkHighContrast = Color(0xFF111319) +val onBackgroundDarkHighContrast = Color(0xFFE1E2EB) +val surfaceDarkHighContrast = Color(0xFF111319) +val onSurfaceDarkHighContrast = Color(0xFFFFFFFF) +val surfaceVariantDarkHighContrast = Color(0xFF424752) +val onSurfaceVariantDarkHighContrast = Color(0xFFFFFFFF) +val outlineDarkHighContrast = Color(0xFFEBF0FF) +val outlineVariantDarkHighContrast = Color(0xFFBEC2D1) +val scrimDarkHighContrast = Color(0xFF000000) +val inverseSurfaceDarkHighContrast = Color(0xFFE1E2EB) +val inverseOnSurfaceDarkHighContrast = Color(0xFF000000) +val inversePrimaryDarkHighContrast = Color(0xFF004690) +val surfaceDimDarkHighContrast = Color(0xFF111319) +val surfaceBrightDarkHighContrast = Color(0xFF4D5057) +val surfaceContainerLowestDarkHighContrast = Color(0xFF000000) +val surfaceContainerLowDarkHighContrast = Color(0xFF1D2026) +val surfaceContainerDarkHighContrast = Color(0xFF2E3037) +val surfaceContainerHighDarkHighContrast = Color(0xFF393B42) +val surfaceContainerHighestDarkHighContrast = Color(0xFF44474E) + + + + + + + diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Theme.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Theme.kt new file mode 100644 index 0000000..5e8f96c --- /dev/null +++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Theme.kt @@ -0,0 +1,259 @@ +package com.jaytux.simd.frontend.theme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.Color + +private val lightScheme = lightColorScheme( + primary = primaryLight, + onPrimary = onPrimaryLight, + primaryContainer = primaryContainerLight, + onPrimaryContainer = onPrimaryContainerLight, + secondary = secondaryLight, + onSecondary = onSecondaryLight, + secondaryContainer = secondaryContainerLight, + onSecondaryContainer = onSecondaryContainerLight, + tertiary = tertiaryLight, + onTertiary = onTertiaryLight, + tertiaryContainer = tertiaryContainerLight, + onTertiaryContainer = onTertiaryContainerLight, + error = errorLight, + onError = onErrorLight, + errorContainer = errorContainerLight, + onErrorContainer = onErrorContainerLight, + background = backgroundLight, + onBackground = onBackgroundLight, + surface = surfaceLight, + onSurface = onSurfaceLight, + surfaceVariant = surfaceVariantLight, + onSurfaceVariant = onSurfaceVariantLight, + outline = outlineLight, + outlineVariant = outlineVariantLight, + scrim = scrimLight, + inverseSurface = inverseSurfaceLight, + inverseOnSurface = inverseOnSurfaceLight, + inversePrimary = inversePrimaryLight, + surfaceDim = surfaceDimLight, + surfaceBright = surfaceBrightLight, + surfaceContainerLowest = surfaceContainerLowestLight, + surfaceContainerLow = surfaceContainerLowLight, + surfaceContainer = surfaceContainerLight, + surfaceContainerHigh = surfaceContainerHighLight, + surfaceContainerHighest = surfaceContainerHighestLight, +) + +private val darkScheme = darkColorScheme( + primary = primaryDark, + onPrimary = onPrimaryDark, + primaryContainer = primaryContainerDark, + onPrimaryContainer = onPrimaryContainerDark, + secondary = secondaryDark, + onSecondary = onSecondaryDark, + secondaryContainer = secondaryContainerDark, + onSecondaryContainer = onSecondaryContainerDark, + tertiary = tertiaryDark, + onTertiary = onTertiaryDark, + tertiaryContainer = tertiaryContainerDark, + onTertiaryContainer = onTertiaryContainerDark, + error = errorDark, + onError = onErrorDark, + errorContainer = errorContainerDark, + onErrorContainer = onErrorContainerDark, + background = backgroundDark, + onBackground = onBackgroundDark, + surface = surfaceDark, + onSurface = onSurfaceDark, + surfaceVariant = surfaceVariantDark, + onSurfaceVariant = onSurfaceVariantDark, + outline = outlineDark, + outlineVariant = outlineVariantDark, + scrim = scrimDark, + inverseSurface = inverseSurfaceDark, + inverseOnSurface = inverseOnSurfaceDark, + inversePrimary = inversePrimaryDark, + surfaceDim = surfaceDimDark, + surfaceBright = surfaceBrightDark, + surfaceContainerLowest = surfaceContainerLowestDark, + surfaceContainerLow = surfaceContainerLowDark, + surfaceContainer = surfaceContainerDark, + surfaceContainerHigh = surfaceContainerHighDark, + surfaceContainerHighest = surfaceContainerHighestDark, +) + +private val mediumContrastLightColorScheme = lightColorScheme( + primary = primaryLightMediumContrast, + onPrimary = onPrimaryLightMediumContrast, + primaryContainer = primaryContainerLightMediumContrast, + onPrimaryContainer = onPrimaryContainerLightMediumContrast, + secondary = secondaryLightMediumContrast, + onSecondary = onSecondaryLightMediumContrast, + secondaryContainer = secondaryContainerLightMediumContrast, + onSecondaryContainer = onSecondaryContainerLightMediumContrast, + tertiary = tertiaryLightMediumContrast, + onTertiary = onTertiaryLightMediumContrast, + tertiaryContainer = tertiaryContainerLightMediumContrast, + onTertiaryContainer = onTertiaryContainerLightMediumContrast, + error = errorLightMediumContrast, + onError = onErrorLightMediumContrast, + errorContainer = errorContainerLightMediumContrast, + onErrorContainer = onErrorContainerLightMediumContrast, + background = backgroundLightMediumContrast, + onBackground = onBackgroundLightMediumContrast, + surface = surfaceLightMediumContrast, + onSurface = onSurfaceLightMediumContrast, + surfaceVariant = surfaceVariantLightMediumContrast, + onSurfaceVariant = onSurfaceVariantLightMediumContrast, + outline = outlineLightMediumContrast, + outlineVariant = outlineVariantLightMediumContrast, + scrim = scrimLightMediumContrast, + inverseSurface = inverseSurfaceLightMediumContrast, + inverseOnSurface = inverseOnSurfaceLightMediumContrast, + inversePrimary = inversePrimaryLightMediumContrast, + surfaceDim = surfaceDimLightMediumContrast, + surfaceBright = surfaceBrightLightMediumContrast, + surfaceContainerLowest = surfaceContainerLowestLightMediumContrast, + surfaceContainerLow = surfaceContainerLowLightMediumContrast, + surfaceContainer = surfaceContainerLightMediumContrast, + surfaceContainerHigh = surfaceContainerHighLightMediumContrast, + surfaceContainerHighest = surfaceContainerHighestLightMediumContrast, +) + +private val highContrastLightColorScheme = lightColorScheme( + primary = primaryLightHighContrast, + onPrimary = onPrimaryLightHighContrast, + primaryContainer = primaryContainerLightHighContrast, + onPrimaryContainer = onPrimaryContainerLightHighContrast, + secondary = secondaryLightHighContrast, + onSecondary = onSecondaryLightHighContrast, + secondaryContainer = secondaryContainerLightHighContrast, + onSecondaryContainer = onSecondaryContainerLightHighContrast, + tertiary = tertiaryLightHighContrast, + onTertiary = onTertiaryLightHighContrast, + tertiaryContainer = tertiaryContainerLightHighContrast, + onTertiaryContainer = onTertiaryContainerLightHighContrast, + error = errorLightHighContrast, + onError = onErrorLightHighContrast, + errorContainer = errorContainerLightHighContrast, + onErrorContainer = onErrorContainerLightHighContrast, + background = backgroundLightHighContrast, + onBackground = onBackgroundLightHighContrast, + surface = surfaceLightHighContrast, + onSurface = onSurfaceLightHighContrast, + surfaceVariant = surfaceVariantLightHighContrast, + onSurfaceVariant = onSurfaceVariantLightHighContrast, + outline = outlineLightHighContrast, + outlineVariant = outlineVariantLightHighContrast, + scrim = scrimLightHighContrast, + inverseSurface = inverseSurfaceLightHighContrast, + inverseOnSurface = inverseOnSurfaceLightHighContrast, + inversePrimary = inversePrimaryLightHighContrast, + surfaceDim = surfaceDimLightHighContrast, + surfaceBright = surfaceBrightLightHighContrast, + surfaceContainerLowest = surfaceContainerLowestLightHighContrast, + surfaceContainerLow = surfaceContainerLowLightHighContrast, + surfaceContainer = surfaceContainerLightHighContrast, + surfaceContainerHigh = surfaceContainerHighLightHighContrast, + surfaceContainerHighest = surfaceContainerHighestLightHighContrast, +) + +private val mediumContrastDarkColorScheme = darkColorScheme( + primary = primaryDarkMediumContrast, + onPrimary = onPrimaryDarkMediumContrast, + primaryContainer = primaryContainerDarkMediumContrast, + onPrimaryContainer = onPrimaryContainerDarkMediumContrast, + secondary = secondaryDarkMediumContrast, + onSecondary = onSecondaryDarkMediumContrast, + secondaryContainer = secondaryContainerDarkMediumContrast, + onSecondaryContainer = onSecondaryContainerDarkMediumContrast, + tertiary = tertiaryDarkMediumContrast, + onTertiary = onTertiaryDarkMediumContrast, + tertiaryContainer = tertiaryContainerDarkMediumContrast, + onTertiaryContainer = onTertiaryContainerDarkMediumContrast, + error = errorDarkMediumContrast, + onError = onErrorDarkMediumContrast, + errorContainer = errorContainerDarkMediumContrast, + onErrorContainer = onErrorContainerDarkMediumContrast, + background = backgroundDarkMediumContrast, + onBackground = onBackgroundDarkMediumContrast, + surface = surfaceDarkMediumContrast, + onSurface = onSurfaceDarkMediumContrast, + surfaceVariant = surfaceVariantDarkMediumContrast, + onSurfaceVariant = onSurfaceVariantDarkMediumContrast, + outline = outlineDarkMediumContrast, + outlineVariant = outlineVariantDarkMediumContrast, + scrim = scrimDarkMediumContrast, + inverseSurface = inverseSurfaceDarkMediumContrast, + inverseOnSurface = inverseOnSurfaceDarkMediumContrast, + inversePrimary = inversePrimaryDarkMediumContrast, + surfaceDim = surfaceDimDarkMediumContrast, + surfaceBright = surfaceBrightDarkMediumContrast, + surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast, + surfaceContainerLow = surfaceContainerLowDarkMediumContrast, + surfaceContainer = surfaceContainerDarkMediumContrast, + surfaceContainerHigh = surfaceContainerHighDarkMediumContrast, + surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast, +) + +private val highContrastDarkColorScheme = darkColorScheme( + primary = primaryDarkHighContrast, + onPrimary = onPrimaryDarkHighContrast, + primaryContainer = primaryContainerDarkHighContrast, + onPrimaryContainer = onPrimaryContainerDarkHighContrast, + secondary = secondaryDarkHighContrast, + onSecondary = onSecondaryDarkHighContrast, + secondaryContainer = secondaryContainerDarkHighContrast, + onSecondaryContainer = onSecondaryContainerDarkHighContrast, + tertiary = tertiaryDarkHighContrast, + onTertiary = onTertiaryDarkHighContrast, + tertiaryContainer = tertiaryContainerDarkHighContrast, + onTertiaryContainer = onTertiaryContainerDarkHighContrast, + error = errorDarkHighContrast, + onError = onErrorDarkHighContrast, + errorContainer = errorContainerDarkHighContrast, + onErrorContainer = onErrorContainerDarkHighContrast, + background = backgroundDarkHighContrast, + onBackground = onBackgroundDarkHighContrast, + surface = surfaceDarkHighContrast, + onSurface = onSurfaceDarkHighContrast, + surfaceVariant = surfaceVariantDarkHighContrast, + onSurfaceVariant = onSurfaceVariantDarkHighContrast, + outline = outlineDarkHighContrast, + outlineVariant = outlineVariantDarkHighContrast, + scrim = scrimDarkHighContrast, + inverseSurface = inverseSurfaceDarkHighContrast, + inverseOnSurface = inverseOnSurfaceDarkHighContrast, + inversePrimary = inversePrimaryDarkHighContrast, + surfaceDim = surfaceDimDarkHighContrast, + surfaceBright = surfaceBrightDarkHighContrast, + surfaceContainerLowest = surfaceContainerLowestDarkHighContrast, + surfaceContainerLow = surfaceContainerLowDarkHighContrast, + surfaceContainer = surfaceContainerDarkHighContrast, + surfaceContainerHigh = surfaceContainerHighDarkHighContrast, + surfaceContainerHighest = surfaceContainerHighestDarkHighContrast, +) + +@Immutable +data class ColorFamily( + val color: Color, + val onColor: Color, + val colorContainer: Color, + val onColorContainer: Color +) + +val unspecified_scheme = ColorFamily( + Color.Unspecified, Color.Unspecified, Color.Unspecified, Color.Unspecified +) + +@Composable +fun SimdTheme( + content: @Composable() () -> Unit +) { + MaterialTheme( + colorScheme = darkScheme, + typography = buildTypography(), + content = content + ) +} + diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Type.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Type.kt new file mode 100644 index 0000000..5755020 --- /dev/null +++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/theme/Type.kt @@ -0,0 +1,50 @@ +package com.jaytux.simd.frontend.theme + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.font.FontFamily +import com.jaytux.simd.frontend.App +import frontend.composeapp.generated.resources.* +import frontend.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.Font + +@Composable +fun Roboto() = FontFamily( + Font(Res.font.RobotoMonoMedium), + Font(Res.font.RobotoMonoItalic), + Font(Res.font.RobotoMonoBold), +) + +@Composable +fun Alegreya() = FontFamily( + Font(Res.font.AlegreyaMedium), + Font(Res.font.AlegreyaItalic), + Font(Res.font.AlegreyaBold), +) + +@Composable +fun buildTypography(): Typography { + val baseline = MaterialTheme.typography + val displayFontFamily = Alegreya() + val bodyFontFamily = Roboto() + + val AppTypography = Typography( + displayLarge = baseline.displayLarge.copy(fontFamily = displayFontFamily), + displayMedium = baseline.displayMedium.copy(fontFamily = displayFontFamily), + displaySmall = baseline.displaySmall.copy(fontFamily = displayFontFamily), + headlineLarge = baseline.headlineLarge.copy(fontFamily = displayFontFamily), + headlineMedium = baseline.headlineMedium.copy(fontFamily = displayFontFamily), + headlineSmall = baseline.headlineSmall.copy(fontFamily = displayFontFamily), + titleLarge = baseline.titleLarge.copy(fontFamily = displayFontFamily), + titleMedium = baseline.titleMedium.copy(fontFamily = displayFontFamily), + titleSmall = baseline.titleSmall.copy(fontFamily = displayFontFamily), + bodyLarge = baseline.bodyLarge.copy(fontFamily = bodyFontFamily), + bodyMedium = baseline.bodyMedium.copy(fontFamily = bodyFontFamily), + bodySmall = baseline.bodySmall.copy(fontFamily = bodyFontFamily), + labelLarge = baseline.labelLarge.copy(fontFamily = bodyFontFamily), + labelMedium = baseline.labelMedium.copy(fontFamily = bodyFontFamily), + labelSmall = baseline.labelSmall.copy(fontFamily = bodyFontFamily), + ) + return AppTypography +} \ No newline at end of file diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/ui/DataState.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/ui/DataState.kt new file mode 100644 index 0000000..dcb24c7 --- /dev/null +++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/ui/DataState.kt @@ -0,0 +1,183 @@ +package com.jaytux.simd.frontend.ui + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import com.jaytux.simd.frontend.Either +import com.jaytux.simd.frontend.client.Client +import com.jaytux.simd.frontend.client.Loader +import com.jaytux.simd.frontend.client.Loader.loadAll +import com.jaytux.simd.frontend.immutable +import com.jaytux.simd.frontend.left +import com.jaytux.simd.frontend.right +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.launch +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +@OptIn(ExperimentalUuidApi::class) +class DataState( + val scope: CoroutineScope, + val addSnackBar: suspend (message: String) -> Unit, +) { + data class SubState( + val stateMut: MutableState = mutableStateOf(""), + val dataMut: MutableState> = mutableStateOf(listOf()) + ) { + val state = stateMut.immutable() + val data = dataMut.immutable() + } + + // --- Intrinsic Summaries --- + private val _summary = SubState() + val summaryState = _summary.state + val summaries = _summary.data + + // --- CPUIDs --- + private val _cpuid = SubState() + val cpuidState = _cpuid.state + val cpuid = _cpuid.data + private val _selectedCpuids = mutableStateOf(listOf()) + val selectedCpuids = _selectedCpuids.immutable() + + // --- Techs --- + private val _techs = SubState() + val techsState = _techs.state + val techs = _techs.data + private val _selectedTechs = mutableStateOf(listOf()) + val selectedTechs = _selectedTechs.immutable() + + // --- CppTypes --- + private val _cppTypes = SubState() + val cppTypesState = _cppTypes.state + val cppTypes = _cppTypes.data + private var filterByType: String? = null + + // --- Categories --- + private val _categories = SubState() + val categoriesState = _categories.state + val categories = _categories.data + private val _selectedCats = mutableStateOf(listOf()) + val selectedCats = _selectedCats.immutable() + + // --- Details --- + private val _intrinsics = mutableMapOf?>>() + private val _intrinsicLoaders = mutableMapOf() + + // --- Version --- + private val _version = mutableStateOf(null) + val version = _version.immutable() + + // --- Filtering --- + private var _filterJob: Job? = null + private val _filterData = mutableStateOf(listOf()) + val filterData = _filterData.immutable() + val filterName = mutableStateOf("") + val filterDesc = mutableStateOf("") + + init { + _filterJob = scope.loadAll( + client = { Client.getAll() }, + onError = addSnackBar, + onFinishSuccess = { /*_summary.dataMut.value = it*/ _filterData.value = it }, + onStatusChange = { _summary.stateMut.value = it }, + onProgress = { /*_summary.dataMut.value = it*/ _filterData.value = it } + ) + scope.loadAll( + client = { Client.getCpuid() }, + onError = addSnackBar, + onFinishSuccess = { _cpuid.dataMut.value = it }, + onStatusChange = { _cpuid.stateMut.value = it } + ) + scope.loadAll( + client = { Client.getTech() }, + onError = addSnackBar, + onFinishSuccess = { _techs.dataMut.value = it }, + onStatusChange = { _techs.stateMut.value = it } + ) + scope.loadAll( + client = { Client.getCategory() }, + onError = addSnackBar, + onFinishSuccess = { _categories.dataMut.value = it }, + onStatusChange = { _categories.stateMut.value = it } + ) + scope.loadAll( + client = { Client.getTypes() }, + onError = addSnackBar, + onFinishSuccess = { _cppTypes.dataMut.value = it }, + onStatusChange = { _cppTypes.stateMut.value = it } + ) + + scope.launch { + Client.getVersion().foldSuspend(addSnackBar) { + _version.value = it + } + } + } + + operator fun get(uuid: Uuid): State?> = + _intrinsics.getOrPut(uuid) { mutableStateOf(null) } + + fun toggleTech(idx: Int) { + if (idx in _selectedTechs.value) _selectedTechs.value -= idx + else _selectedTechs.value += idx + + doFilter() + } + + fun toggleCategory(idx: Int) { + if (idx in _selectedCats.value) _selectedCats.value -= idx + else _selectedCats.value += idx + + doFilter() + } + + fun toggleCpuid(idx: Int) { + if (idx in _selectedCpuids.value) _selectedCpuids.value -= idx + else _selectedCpuids.value += idx + + doFilter() + } + + fun setReturn(type: String) { + filterByType = if(type !in cppTypes.value) null else type + } + + fun loadDetails(uuid: Uuid) { + val current = _intrinsicLoaders[uuid] + if(current != null && !current.isActive) return + + _intrinsicLoaders[uuid] = scope.launch { + Client.getDetails(uuid).foldSuspend({ + val msg = "Failed to load details for $uuid: $it" + addSnackBar(msg) + _intrinsics.getOrPut(uuid) { mutableStateOf(null) }.value = msg.left() + }) { + _intrinsics.getOrPut(uuid) { mutableStateOf(null) }.value = it.right() + } + } + } + + fun doFilter() { + _filterJob?.cancel() + _filterJob = scope.loadAll( + client = { + Client.getSearch( + name = filterName.value.ifBlank { null }, + returnT = filterByType, + cpuid = _selectedCpuids.value.map { cpuid.value[it] }, + tech = _selectedTechs.value.map { techs.value[it] }, + category = _selectedCats.value.map { categories.value[it] }, + desc = filterDesc.value.ifBlank { null } + ) + }, + onError = addSnackBar, + onFinishSuccess = { _filterData.value = it }, + onStatusChange = {}, + onProgress = { _filterData.value = it }, + throttleBefore = 250L + ) + } +} \ No newline at end of file diff --git a/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/ui/Widgets.kt b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/ui/Widgets.kt new file mode 100644 index 0000000..e80e9da --- /dev/null +++ b/frontend/composeApp/src/commonMain/kotlin/com/jaytux/simd/frontend/ui/Widgets.kt @@ -0,0 +1,407 @@ +package com.jaytux.simd.frontend.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.PopupProperties +import com.composables.icons.lucide.* +import com.jaytux.simd.frontend.OrError +import com.jaytux.simd.frontend.client.Client +import com.jaytux.simd.frontend.client.Loader +import com.jaytux.simd.frontend.theme.buildTypography +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlin.uuid.ExperimentalUuidApi + +@Composable +fun fontSizeDp(style: TextStyle = LocalTextStyle.current) = with(LocalDensity.current) { + style.fontSize.toDp() +} + +@Composable +fun Spinner(text: String, style: TextStyle = LocalTextStyle.current) = Row { + CircularProgressIndicator(Modifier.size(fontSizeDp(style) * 2).scale(0.5f)) + Spacer(Modifier.width(5.dp)) + Text(text, Modifier.align(Alignment.CenterVertically), style = style) +} + +@Composable +fun ErrorSnackBar(data: SnackbarData) { + Snackbar( + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError, + ) { + Box(Modifier.padding(5.dp)) { + Text(data.visuals.message, modifier = Modifier.align(Alignment.Center)) + } + } +} + +@Composable +fun MicroHeader(text: String, modifier: Modifier = Modifier) = + Text(text, modifier, style = MaterialTheme.typography.bodyLarge) + +@Composable +fun Collapsible(expanded: Boolean, onClick: () -> Unit, content: @Composable () -> Unit) { + Row(Modifier.clickable { onClick() }) { + Icon(if(expanded) Lucide.ChevronDown else Lucide.ChevronRight, "Expand/Collapse") + Spacer(Modifier.width(10.dp)) + content() + } +} + +@Composable +fun Checkable(checked: Boolean, onClick: () -> Unit, content: @Composable () -> Unit) { + Row(Modifier.clickable { onClick() }) { + Icon(if(checked) Lucide.SquareCheckBig else Lucide.Square, "Check/Uncheck") + Spacer(Modifier.width(10.dp)) + content() + } +} + +@Composable +fun Indented(steps: Int = 1, content: @Composable ColumnScope.() -> Unit) { + Row(Modifier.padding(start = (steps * 20).dp)) { + Column { + content() + } + } +} + +fun LazyListScope.FilterBlock( + title: String, + expanded: Boolean, + status: String, + options: List, + selected: List, + onToggleExpand: () -> Unit, + onToggleSelect: (Int) -> Unit, + renderT: @Composable (T) -> Unit +) { + item { + Collapsible(expanded, onToggleExpand) { + MicroHeader("$title (${selected.size} selected)") + } + } + + if(expanded) { + if(options.isEmpty()) { + item { + Indented { + Spinner(status) + } + } + } + else { + itemsIndexed(options) { i, t -> + Indented { + Checkable(i in selected, { onToggleSelect(i) }) { + renderT(t) + } + } + } + } + } +} + +@Composable +fun topBar(data: DataState) = Surface(tonalElevation = 10.dp, shadowElevation = 10.dp, color = MaterialTheme.colorScheme.primaryContainer) { + val version by data.version + Box(Modifier.fillMaxWidth().padding(10.dp)) { + Box(Modifier.align(Alignment.Center)) { + Text( + "C/C++ (SIMD) Intrinsics", + style = MaterialTheme.typography.headlineLarge + ) + } + + version?.let { + Column(Modifier.align(Alignment.CenterEnd)) { + Row { + Text( + "Intel data: ${it.intelVersion} (last update: ${it.intelUpdate})", + style = MaterialTheme.typography.labelSmall + ) + // TODO: add other versioning data (AMD, ARM, ...) when added to DB/backend + } + + Text("Last database update: ${it.scrapeDate}", style = MaterialTheme.typography.labelSmall) + } + } + } +} + +@Composable +fun ColumnScope.FilterColumn(data: DataState) { + var expandTech by remember { mutableStateOf(false) } + var expandCategory by remember { mutableStateOf(false) } + var expandCpuid by remember { mutableStateOf(false) } + val techs by data.techs; val techMsg by data.techsState; val techSel by data.selectedTechs + val cats by data.categories; val catMsg by data.categoriesState; val catSel by data.selectedCats + val cpuid by data.cpuid; val cpuidMsg by data.cpuidState; val cpuidSel by data.selectedCpuids + + Text("Filter intrinsics by:", style = MaterialTheme.typography.headlineSmall) + + LazyColumn { + FilterBlock( + "ISA extensions", expandTech, techMsg, techs, techSel, + { expandTech = !expandTech }, { data.toggleTech(it) } + ) { Text(it) } + FilterBlock( + "Categories", expandCategory, catMsg, cats, catSel, + { expandCategory = !expandCategory }, { data.toggleCategory(it) } + ) { Text(it) } + FilterBlock( + "CPUIDs", expandCpuid, cpuidMsg, cpuid, cpuidSel, + { expandCpuid = !expandCpuid }, { data.toggleCpuid(it) } + ) { Text(it) } + } +} + +@Composable +fun IntrinsicCard( + summary: Client.IntrinsicSummary, + details: OrError?, + requestLoad: suspend (Client.IntrinsicSummary) -> Unit +) { + var expanded by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + Surface( + Modifier.fillMaxWidth().padding(5.dp).clickable { + expanded = !expanded + if(expanded && details == null) scope.launch { requestLoad(summary) } + }, + tonalElevation = 2.dp, + shadowElevation = 2.dp, + shape = MaterialTheme.shapes.medium + ) { + val style = MaterialTheme.typography.labelMedium + + Column(Modifier.padding(10.dp)) { + Text(summary.name, style = MaterialTheme.typography.headlineSmall) + if(expanded) { + Indented { + details?.foldCompose({ + Text(it, color = MaterialTheme.colorScheme.error) + }) { + Text("Synopsis") + Indented { + Row { + Text("${it.returnType} ${it.name}(${it.params.joinToString(", ") { p -> "${p.type} ${p.name}" }})") + Text(" [${it.category}]", color = LocalContentColor.current.copy(alpha = 0.4f)) + } + it.cpuid?.let { cpuid -> Text("CPUID: $cpuid", style = style) } + } + + Spacer(Modifier.height(5.dp)) + Text("Description") + Indented { + Text(it.description, style = style) + it.instructions?.let { insn -> + Spacer(Modifier.height(2.dp)) + Text("Instruction(s):") + Indented { + insn.forEach { ins -> + Text("${ins.mnemonic} ${ins.form ?: ""}", style = style) + } + } + } + } + + it.operations?.let { ops -> + Spacer(Modifier.height(5.dp)) + Text("Operations") + Indented { + Surface(Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.surfaceDim) { + Column { + ops.split("\n").forEach { op -> + val depth = op.takeWhile { it == '\t' }.length + val text = op.drop(depth) + Indented(depth) { Text(text, style = style) } + } + } + } + } + } + + it.performance?.let { perf -> + if(perf.isEmpty()) return@let + Spacer(Modifier.height(5.dp)) + Text("Performance") + Indented { + Row { + Column { + Text("Architecture") + perf.forEach { p -> Text(p.platform, style = style) } + } + + Spacer(Modifier.width(20.dp)) + + Column { + Text("Latency (cycles)") + perf.forEach { p -> Text(p.latency?.toString() ?: "-", style = style) } + } + + Spacer(Modifier.width(20.dp)) + + Column { + Text("Throughput (CPI)") + perf.forEach { p -> Text(p.throughput?.toString() ?: "-", style = style) } + } + } + } + } + } + if(details == null) Text("Loading details...", style = MaterialTheme.typography.bodySmall) + } + } + } + } +} + +@OptIn(ExperimentalUuidApi::class) +@Composable +fun ColumnScope.IntrinsicColumn(data: DataState) { + var nameFilter by data.filterName + var descFilter by data.filterDesc + var retFilter by remember { mutableStateOf("") } + val intrinsicState by data.summaryState + val intrinsicList by data.filterData + val typeOptions by data.cppTypes + val typeMsg by data.cppTypesState + + var expandFilter by remember { mutableStateOf(false) } + + OutlinedTextField( + nameFilter, { nameFilter = it; data.doFilter() }, + Modifier.fillMaxWidth(), + leadingIcon = { Icon(Lucide.SearchCode, "Search") }, + label = { Text("Filter by intrinsic name...") } + ) + + Text( + (if (expandFilter) "Hide" else "Show") + " additional filters", + Modifier.clickable { expandFilter = !expandFilter }.align(Alignment.End), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline + ) + + if (expandFilter) { + var showingOptions by remember { mutableStateOf(false) } + OutlinedTextField( + retFilter, { retFilter = it; showingOptions = true }, + Modifier.clickable { showingOptions = true }.fillMaxWidth(0.66f).onFocusChanged { + showingOptions = it.isFocused + if(!it.isFocused) data.setReturn(retFilter) + }, + trailingIcon = { + Icon(if (showingOptions) Lucide.ChevronUp else Lucide.ChevronDown, "Show options") + }, + label = { Text("Filter by return type...") }, + isError = retFilter !in typeOptions + ) + DropdownMenu( + showingOptions, { showingOptions = false }, + properties = PopupProperties() + ) { + if (typeOptions.isEmpty()) { + DropdownMenuItem( + { Spinner(typeMsg) }, { retFilter = "" } + ) + } else { + DropdownMenuItem( + { Text("(none)") }, { retFilter = "" } + ) + typeOptions.filter{ retFilter in it }.take(10).forEach { it -> + DropdownMenuItem( + { Text(it) }, { retFilter = it } + ) + } + } + } + + OutlinedTextField( + descFilter, + { descFilter = it; data.doFilter() }, + Modifier.fillMaxWidth(), + label = { Text("Filter by description...") }) + } + + if(intrinsicList.isEmpty()) { + Box(Modifier.fillMaxSize()) { + Column(Modifier.align(Alignment.Center)) { + when(intrinsicState) { + Loader.progressFinished -> Text("No results for your query") + Loader.progressFailed -> { + Text("An error occurred while processing your query") + } + else -> { + Spinner(intrinsicState) + } + } + } + } + } + else { + if(intrinsicState != Loader.progressFinished) { + Spinner(intrinsicState, MaterialTheme.typography.bodySmall) + } + + LazyColumn { + items(intrinsicList) { it -> + val details by data[it.id] + IntrinsicCard(it, details) { data.loadDetails(it.id) } + } + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/MainView.jvm.kt b/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/MainView.jvm.kt new file mode 100644 index 0000000..b38b2ab --- /dev/null +++ b/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/MainView.jvm.kt @@ -0,0 +1,35 @@ +package com.jaytux.simd.frontend + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.jaytux.simd.frontend.ui.DataState +import com.jaytux.simd.frontend.ui.FilterColumn +import com.jaytux.simd.frontend.ui.IntrinsicColumn +import com.jaytux.simd.frontend.ui.topBar + +@Composable +actual fun mainView(data: DataState) = Row { + Surface( + Modifier.weight(0.25f).fillMaxSize(), + tonalElevation = 3.dp, + shadowElevation = 3.dp, + color = MaterialTheme.colorScheme.primary, + shape = MaterialTheme.shapes.medium.copy(topStart = CornerSize(0.dp), topEnd = CornerSize(0.dp)) + ) { + Column(Modifier.padding(5.dp)) { + FilterColumn(data) + } + } + Column(Modifier.weight(0.66f).padding(15.dp)) { + IntrinsicColumn(data) + } +} \ No newline at end of file diff --git a/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/Platform.jvm.kt b/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/Platform.jvm.kt index c077abb..0794c28 100644 --- a/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/Platform.jvm.kt +++ b/frontend/composeApp/src/desktopMain/kotlin/com/jaytux/simd/frontend/Platform.jvm.kt @@ -1,7 +1,10 @@ package com.jaytux.simd.frontend -class JVMPlatform: Platform { - override val name: String = "Java ${System.getProperty("java.version")}" -} +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import java.awt.Desktop +import java.net.URI -actual fun getPlatform(): Platform = JVMPlatform() \ No newline at end of file +actual fun getKtorClient(builder: HttpClientConfig<*>.() -> Unit): HttpClient = HttpClient(CIO) { + builder() +} \ No newline at end of file diff --git a/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/MainView.wasmJs.kt b/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/MainView.wasmJs.kt new file mode 100644 index 0000000..25ce870 --- /dev/null +++ b/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/MainView.wasmJs.kt @@ -0,0 +1,35 @@ +package com.jaytux.simd.frontend + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.jaytux.simd.frontend.ui.DataState +import com.jaytux.simd.frontend.ui.FilterColumn +import com.jaytux.simd.frontend.ui.IntrinsicColumn +import com.jaytux.simd.frontend.ui.topBar + +@Composable +actual fun mainView(data: DataState) = Row { + Surface( + Modifier.weight(0.25f).fillMaxSize(), + tonalElevation = 3.dp, + shadowElevation = 3.dp, + color = MaterialTheme.colorScheme.primary, + shape = MaterialTheme.shapes.medium.copy(topStart = CornerSize(0.dp), topEnd = CornerSize(0.dp)) + ) { + Column(Modifier.padding(5.dp)) { + FilterColumn(data) + } + } + Column(Modifier.weight(0.66f).padding(15.dp)) { + IntrinsicColumn(data) + } +} \ No newline at end of file diff --git a/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/Platform.wasmJs.kt b/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/Platform.wasmJs.kt index 235be28..b0c4e4b 100644 --- a/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/Platform.wasmJs.kt +++ b/frontend/composeApp/src/wasmJsMain/kotlin/com/jaytux/simd/frontend/Platform.wasmJs.kt @@ -1,7 +1,10 @@ package com.jaytux.simd.frontend -class WasmPlatform: Platform { - override val name: String = "Web with Kotlin/Wasm" -} +import androidx.compose.runtime.Composable +import io.ktor.client.* +import io.ktor.client.engine.js.* +import kotlinx.browser.window -actual fun getPlatform(): Platform = WasmPlatform() \ No newline at end of file +actual fun getKtorClient(builder: HttpClientConfig<*>.() -> Unit): HttpClient = HttpClient(Js) { + builder() +} \ No newline at end of file diff --git a/frontend/gradle/libs.versions.toml b/frontend/gradle/libs.versions.toml index 34b12b8..f0e8658 100644 --- a/frontend/gradle/libs.versions.toml +++ b/frontend/gradle/libs.versions.toml @@ -9,6 +9,7 @@ dotenv = "6.5.1" serialization = "1.8.1" logback = "1.5.6" json = "20231013" +ui-text-google-fonts = "1.8.0" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -22,14 +23,17 @@ ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" } ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } +ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } -dotenv = { module = "io.github.cdimascio:dotenv-kotlin", version.ref = "dotenv" } json = { module = "org.json:json", version.ref = "json" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.6.2" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } +material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "compose-multiplatform" } +lucide = { module = "com.composables:icons-lucide", version="1.0.0" } + [plugins] composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } diff --git a/frontend/kotlin-js-store/yarn.lock b/frontend/kotlin-js-store/yarn.lock index 7df9e5c..ac81ac1 100644 --- a/frontend/kotlin-js-store/yarn.lock +++ b/frontend/kotlin-js-store/yarn.lock @@ -52,6 +52,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@js-joda/core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" + integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.5" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" @@ -2837,6 +2842,11 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +ws@8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + ws@^8.13.0: version "8.18.1" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb"