Files
coco 723ce1af5c a
2026-07-03 15:12:48 +08:00

204 lines
7.6 KiB
Kotlin

plugins {
alias(libs.plugins.kotlin.multiplatform)
id("kobweb-compose")
id("com.varabyte.kobweb.internal.publish")
}
group = "com.varabyte.kobwebx"
version = libs.versions.kobweb.get()
private val GENERATED_SRC_ROOT = "build/generated/icons/src/jsMain/kotlin"
enum class IconCategory {
SOLID,
REGULAR,
BRAND,
}
val generateIconsTask = tasks.register("generateIcons") {
val srcFile = layout.projectDirectory.file("fa-icon-list.txt")
val dstFile =
layout.projectDirectory.file("$GENERATED_SRC_ROOT/com/varabyte/kobweb/silk/components/icons/fa/FaIcons.kt")
inputs.files(srcFile)
outputs.dir(GENERATED_SRC_ROOT)
doLast {
// {SOLID=[ad, address-book, address-card, ...], REGULAR=[address-book, address-card, angry, ...], ... }
val iconRawNames = srcFile.asFile
.readLines().asSequence()
.filter { line -> !line.startsWith("#") }
.map { line ->
// Convert icon name to function name, e.g.
// align-left -> FaAlignLeft
line.split("=", limit = 2).let { parts ->
val category = when (parts[0]) {
"fas" -> IconCategory.SOLID
"far" -> IconCategory.REGULAR
"fab" -> IconCategory.BRAND
else -> throw GradleException("Unexpected category string: ${parts[0]}")
}
val names = parts[1]
category to names.split(",")
}
}
.toMap()
// For each icon name, figure out what categories they are in. This will affect the function signature we generate.
// {ad=[SOLID], address-book=[SOLID, REGULAR], address-card=[SOLID, REGULAR], ...
val iconCategories = mutableMapOf<String, MutableSet<IconCategory>>()
iconRawNames.forEach { entry ->
val category = entry.key
entry.value.forEach { rawName ->
iconCategories.computeIfAbsent(rawName, { mutableSetOf() }).add(category)
}
}
// Sanity check results
iconCategories
.filterNot { entry ->
val categories = entry.value
categories.size == 1 ||
(categories.size == 2 && categories.contains(IconCategory.SOLID) && categories.contains(IconCategory.REGULAR))
}
.let { invalidGroupings ->
if (invalidGroupings.isNotEmpty()) {
throw GradleException("Found unexpected groupings. An icon should only be in its own category OR it can have solid and regular versions: $invalidGroupings")
}
}
// Generate four types of functions: solid only, regular only, solid or regular, and brand
val iconMethodEntries = iconCategories
.map { entry ->
val rawName = entry.key
// Convert e.g. "align-items" to "FaAlignItems"
@Suppress("DEPRECATION") // capitalize is way more readable than a direct replacement
val methodName = "Fa${rawName.split("-").joinToString("") { it.capitalize() }}"
val categories = entry.value
when {
categories.size == 2 -> {
"@Composable fun $methodName(modifier: Modifier = Modifier, style: IconStyle = IconStyle.OUTLINE, size: IconSize? = null) = FaIcon(\"$rawName\", modifier, style.category, size)"
}
categories.contains(IconCategory.SOLID) -> {
"@Composable fun $methodName(modifier: Modifier = Modifier, size: IconSize? = null) = FaIcon(\"$rawName\", modifier, IconCategory.SOLID, size)"
}
categories.contains(IconCategory.REGULAR) -> {
"@Composable fun $methodName(modifier: Modifier = Modifier, size: IconSize? = null) = FaIcon(\"$rawName\", modifier, IconCategory.REGULAR, size)"
}
categories.contains(IconCategory.BRAND) -> {
"@Composable fun $methodName(modifier: Modifier = Modifier, size: IconSize? = null) = FaIcon(\"$rawName\", modifier, IconCategory.BRAND, size)"
}
else -> GradleException("Unhandled icon entry: $entry")
}
}
val iconsCode =
"""
|//@formatter:off
|@file:Suppress("unused", "SpellCheckingInspection")
|
|// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|// THIS FILE IS AUTOGENERATED.
|//
|// Do not edit this file by hand. Instead, update `fa-icon-list.txt` in the module root and run the Gradle
|// task "generateIcons"
|// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|package com.varabyte.kobweb.silk.components.icons.fa
|
|import androidx.compose.runtime.*
|import com.varabyte.kobweb.compose.ui.Modifier
|import com.varabyte.kobweb.compose.ui.toAttrs
|import org.jetbrains.compose.web.dom.Span
|
|enum class IconCategory(internal val className: String) {
| REGULAR("far"),
| SOLID("fas"),
| BRAND("fab");
|}
|
|enum class IconStyle(internal val category: IconCategory) {
| FILLED(IconCategory.SOLID),
| OUTLINE(IconCategory.REGULAR);
|}
|
|// See: https://fontawesome.com/docs/web/style/size
|enum class IconSize(internal val className: String) {
| // Relative sizes
| XXS("fa-2xs"),
| XS("fa-xs"),
| SM("fa-sm"),
| LG("fa-lg"),
| XL("fa-xl"),
| XXL("fa-2xl"),
|
| // Literal sizes
| X1("fa-1x"),
| X2("fa-2x"),
| X3("fa-3x"),
| X4("fa-4x"),
| X5("fa-5x"),
| X6("fa-6x"),
| X7("fa-7x"),
| X8("fa-8x"),
| X9("fa-9x"),
| X10("fa-10x");
|}
|
|@Composable
|fun FaIcon(
| name: String,
| modifier: Modifier,
| style: IconCategory = IconCategory.REGULAR,
| size: IconSize? = null,
|) {
| Span(
| attrs = modifier.toAttrs {
| classes(style.className, "fa-${'$'}name")
| if (size != null) {
| classes(size.className)
| }
| }
| )
|}
|
|${iconMethodEntries.joinToString("\n")}
""".trimMargin()
dstFile.asFile.apply {
parentFile.mkdirs()
writeText(iconsCode)
}
}
}
kotlin {
js {
browser()
}
sourceSets {
jsMain {
kotlin.srcDir(generateIconsTask)
dependencies {
implementation(libs.compose.runtime)
implementation(libs.compose.html.core)
api(projects.frontend.kobwebCompose)
}
}
}
}
kobwebPublication {
artifactName.set("Kobweb Silk Icons (Font Awesome)")
artifactId.set("silk-icons-fa")
description.set("A collection of composables that directly wrap Font Awesome icons.")
}