total rework

This commit is contained in:
InsanusMokrassar committed 2022-11-16 00:56:24 +06:00
1 parent 53a76c7a73
commit 8430e68167
69 files changed
+904 -1586

No files matched your search

@@ -5,18 +5,26 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultDivider
expect object BuilderViewDrawer : Drawer<BuilderView>
@Composable
expect fun TopAppBar(
config: Config,
saveAvailable: Boolean,
onSaveAvailable: (Boolean) -> Unit,
onNewConfig: (Config) -> Unit
)
class BuilderView : View() {
internal val projectTypeView = ProjectTypeView()
internal val mavenInfoView = MavenInfoView()
internal val licensesView = LicensesView()
internal val projectTypeView by mutableStateOf(ProjectTypeView())
internal val mavenInfoView by mutableStateOf(MavenInfoView())
internal val licensesView by mutableStateOf(LicensesView())
internal var saveAvailableState by mutableStateOf(false)
var config: Config
get() = Config(licensesView.licenses, mavenInfoView.mavenConfig, projectTypeView.projectType)
get() {
return Config(licensesView.licenses, mavenInfoView.mavenConfig, projectTypeView.projectType)
}
set(value) {
licensesView.licenses = value.licenses
mavenInfoView.mavenConfig = value.mavenConfig
@@ -26,6 +34,20 @@ class BuilderView : View() {
@Composable
override fun build() {
with(BuilderViewDrawer) { draw() }
TopAppBar(
config,
saveAvailableState,
{
saveAvailableState = it
}
) {
config = it
}
projectTypeView.build()
DefaultDivider()
licensesView.build()
DefaultDivider()
mavenInfoView.build()
}
}
@@ -1,10 +1,12 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.Developer
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
class DeveloperState(
id: String = "",
@@ -21,11 +23,32 @@ class DeveloperState(
private fun Developer.toDeveloperState() = DeveloperState(id, name, eMail)
class DevelopersView : ListView<DeveloperState>("Developers info") {
var developers = mutableStateListOf<Developer>()
var developers: List<Developer>
get() = itemsList.map { it.toDeveloper() }
set(value) {
itemsList.apply {
clear()
addAll(value.map { it.toDeveloperState() })
}
}
override val addItemText: String = "Add developer"
override val removeItemText: String = "Remove developer"
override fun createItem(): DeveloperState = DeveloperState()
@Composable
override fun buildView(item: DeveloperState) {
CommonTextField(
item.id,
"Developer username",
) { item.id = it }
CommonTextField(
item.name,
"Developer name",
) { item.name = it }
CommonTextField(
item.eMail,
"Developer E-Mail",
) { item.eMail = it }
}
}
@@ -1,12 +1,15 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.License
import dev.inmo.kmppscriptbuilder.core.models.getLicenses
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import io.ktor.client.HttpClient
import kotlinx.coroutines.CoroutineScope
@@ -38,8 +41,14 @@ class LicensesView: VerticalView("Licenses") {
licensesListState.addAll(value.map { it.toLicenseState() })
}
internal val availableLicensesState = mutableStateListOf<License>()
internal val licensesOffersToShow = mutableStateListOf<License>()
internal var licenseSearchFilter by mutableStateOf("")
internal val searchFieldFocused = mutableStateOf(false)
internal val licensesOffersToShow = derivedStateOf {
val query = licenseSearchFilter.lowercase()
availableLicensesState.filter {
it.title.lowercase().contains(query)
}
}
init {
CoroutineScope(Dispatchers.Default).launch {
@@ -50,6 +59,16 @@ class LicensesView: VerticalView("Licenses") {
}
override val content: @Composable () -> Unit = {
CommonTextField(
licenseSearchFilter,
"Search filter",
onFocusChanged = {
searchFieldFocused.value = it
}
) { filterText ->
licenseSearchFilter = filterText
}
with(LicensesDrawer) { draw() }
}
}
@@ -18,7 +18,7 @@ abstract class ListView<T>(title: String) : VerticalView(title) {
protected val drawer = ListViewDrawer<T>()
override val content: () -> Unit = {
override val content: @Composable () -> Unit = {
with(drawer) {
draw()
}
@@ -2,9 +2,17 @@ package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.*
import dev.inmo.kmppscriptbuilder.core.models.GpgSigning
import dev.inmo.kmppscriptbuilder.core.models.MavenConfig
import dev.inmo.kmppscriptbuilder.core.models.SonatypeRepository
import dev.inmo.kmppscriptbuilder.core.models.defaultProjectDescription
import dev.inmo.kmppscriptbuilder.core.models.defaultProjectName
import dev.inmo.kmppscriptbuilder.core.ui.utils.ButtonsPanel
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.ui.utils.SwitchWithLabel
expect object MavenInfoDrawer : Drawer<MavenInfoView>
expect class GpgSigningOptionDrawer : Drawer<GpgSigning>
expect fun GpgSigningOptionDrawerWithView(view: MavenInfoView): GpgSigningOptionDrawer
class MavenInfoView : VerticalView("Project information") {
internal var projectNameProperty by mutableStateOf("")
@@ -43,10 +51,46 @@ class MavenInfoView : VerticalView("Project information") {
publishToMavenCentralProperty = value.repositories.any { it == SonatypeRepository }
developersView.developers = value.developers
repositoriesView.repositories = value.repositories.filter { it != SonatypeRepository }
// developersView.developers = value.developers
}
private val gpgSigningDrawer = GpgSigningOptionDrawerWithView(this)
override val content: @Composable () -> Unit = {
with (MavenInfoDrawer) { draw() }
CommonTextField(
projectNameProperty,
"Public project name",
) { projectNameProperty = it }
CommonTextField(
projectDescriptionProperty,
"Public project description",
) { projectDescriptionProperty = it }
CommonTextField(
projectUrlProperty,
"Public project URL",
) { projectUrlProperty = it }
CommonTextField(
projectVcsUrlProperty,
"Public project VCS URL (with .git)",
) { projectVcsUrlProperty = it }
ButtonsPanel(
GpgSigning.Disabled,
GpgSigning.Optional,
GpgSigning.Enabled
) {
with(gpgSigningDrawer) {
with (it) {
draw()
}
}
}
SwitchWithLabel(
"Include publication to MavenCentral",
publishToMavenCentralProperty,
placeSwitchAtTheStart = true
) { publishToMavenCentralProperty = it }
developersView.build()
repositoriesView.build()
}
}
@@ -4,16 +4,31 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.JSProjectType
import dev.inmo.kmppscriptbuilder.core.models.JVMProjectType
import dev.inmo.kmppscriptbuilder.core.models.MultiplatformProjectType
import dev.inmo.kmppscriptbuilder.core.models.ProjectType
import dev.inmo.kmppscriptbuilder.core.ui.utils.ButtonsPanel
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
expect object ProjectTypeDrawer : Drawer<ProjectTypeView>
expect class ProjectTypeDrawer : Drawer<ProjectType>
expect fun ProjectTypeDrawerWithView(view: ProjectTypeView): ProjectTypeDrawer
class ProjectTypeView : VerticalView("Project type") {
var projectType by mutableStateOf<ProjectType>(MultiplatformProjectType)
private val typeDrawer = ProjectTypeDrawerWithView(this)
override val content: @Composable () -> Unit = {
with(ProjectTypeDrawer) { draw() }
ButtonsPanel(
MultiplatformProjectType,
JVMProjectType,
JSProjectType
) {
with(typeDrawer) {
with (it) {
draw()
}
}
}
}
}
@@ -2,9 +2,11 @@ package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
class RepositoryState(
@@ -19,8 +21,6 @@ class RepositoryState(
private fun MavenPublishingRepository.toRepositoryState() = RepositoryState(name, url)
expect object RepositoryStateDrawer : Drawer<RepositoryState>
class RepositoriesView : ListView<RepositoryState>("Repositories info") {
var repositories: List<MavenPublishingRepository>
get() = itemsList.map { it.toRepository() }
@@ -38,7 +38,14 @@ class RepositoriesView : ListView<RepositoryState>("Repositories info") {
@Composable
override fun buildView(item: RepositoryState) {
with(RepositoryStateDrawer) { with(item) { draw() } }
CommonTextField(
item.name,
"Repository name",
) { item.name = it }
CommonTextField(
item.url,
"Repository url",
) { item.url = it }
}
}
@@ -2,7 +2,7 @@ package dev.inmo.kmppscriptbuilder.core.ui.utils
import androidx.compose.runtime.Composable
interface Drawer<T> {
fun interface Drawer<T> {
@Composable
fun T.draw()
}
@@ -9,7 +9,12 @@ expect fun TitleText(text: String)
expect fun CommonText(text: String, onClick: (() -> Unit)? = null)
@Composable
expect fun CommonTextField(presetText: String, hint: String, onChange: (String) -> Unit)
expect fun CommonTextField(
presetText: String,
hint: String,
onFocusChanged: (Boolean) -> Unit = {},
onChange: (String) -> Unit
)
@Composable
expect fun SwitchWithLabel(
@@ -19,3 +24,18 @@ expect fun SwitchWithLabel(
switchEnabled: Boolean = true,
onCheckedChange: (Boolean) -> Unit
)
@Composable
expect fun <T> ButtonsPanel(
data: Iterable<T>,
itemDrawer: @Composable (T) -> Unit
)
@Composable
fun <T> ButtonsPanel(
vararg data: T,
itemDrawer: @Composable (T) -> Unit
) = ButtonsPanel(data.toList(), itemDrawer)
@Composable
expect fun DefaultDivider()
@@ -5,16 +5,7 @@ import dev.inmo.micro_utils.common.MPPFile
internal const val appExtension = "kpsb"
private var lastFile: MPPFile? = null
fun loadConfigFile(file: MPPFile): Config {
lastFile = file
return serialFormat.decodeFromString(Config.serializer(), file.text())
}
expect fun MPPFile.text(): String
expect fun loadConfig(): Config?
expect fun openNewConfig(onParsed: (Config) -> Unit)
expect fun saveConfig(config: Config): Boolean
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path fill="white" d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2M18 20H6V4H13V9H18V20M16 11V18.1L13.9 16L11.1 18.8L8.3 16L11.1 13.2L8.9 11H16Z" /></svg>

After

Width:  |  Height:  |  Size: 463 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path fill="white" d="M6.1,10L4,18V8H21A2,2 0 0,0 19,6H12L10,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H19C19.9,20 20.7,19.4 20.9,18.5L23.2,10H6.1M19,18H6L7.6,12H20.6L19,18Z" /></svg>

After

Width:  |  Height:  |  Size: 452 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path fill="white" d="M4 19H10V21H4C2.89 21 2 20.1 2 19V5C2 3.9 2.89 3 4 3H16L20 7V9.12L18 11.12V7.83L15.17 5H4V19M14 10V6H5V10H14M20.42 12.3C20.31 12.19 20.18 12.13 20.04 12.13C19.9 12.13 19.76 12.19 19.65 12.3L18.65 13.3L20.7 15.35L21.7 14.35C21.92 14.14 21.92 13.79 21.7 13.58L20.42 12.3M12 19.94V22H14.06L20.12 15.93L18.07 13.88L12 19.94M14 15C14 13.34 12.66 12 11 12S8 13.34 8 15 9.34 18 11 18C11.04 18 11.08 18 11.13 18L14 15.13C14 15.09 14 15.05 14 15" /></svg>

After

Width:  |  Height:  |  Size: 744 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path fill="white" d="M17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H19C20.1 21 21 20.1 21 19V7L17 3M19 19H5V5H16.17L19 7.83V19M12 12C10.34 12 9 13.34 9 15S10.34 18 12 18 15 16.66 15 15 13.66 12 12 12M6 6H15V10H6V6Z" /></svg>

After

Width:  |  Height:  |  Size: 502 B

@@ -0,0 +1,14 @@
package dev.inmo.kmppscriptbuilder.core
import dev.inmo.kmppscriptbuilder.core.ui.BuilderView
import kotlinx.browser.window
import org.jetbrains.compose.web.renderComposableInBody
fun main() {
window.addEventListener("load", {
val builder = BuilderView()
renderComposableInBody {
builder.build()
}
})
}
@@ -0,0 +1,25 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.elements.DefaultButton
import dev.inmo.jsuikit.modifiers.UIKitButton
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.kmppscriptbuilder.core.models.GpgSigning
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual class GpgSigningOptionDrawer(
private val mavenInfoView: MavenInfoView
) : Drawer<GpgSigning> {
@Composable
override fun GpgSigning.draw() {
if (mavenInfoView.gpgSignProperty == this) {
DefaultButton(name, UIKitButton.Type.Primary)
} else {
DefaultButton(name, UIKitButton.Type.Default) {
mavenInfoView.gpgSignProperty = this
}
}
}
}
actual fun GpgSigningOptionDrawerWithView(view: MavenInfoView): GpgSigningOptionDrawer = GpgSigningOptionDrawer(mavenInfoView = view)
@@ -0,0 +1,59 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.elements.DefaultButton
import dev.inmo.jsuikit.elements.Divider
import dev.inmo.jsuikit.modifiers.UIKitButton
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.jsuikit.modifiers.builder
import dev.inmo.jsuikit.utils.Attrs
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import org.jetbrains.compose.web.dom.Div
actual object LicensesDrawer : Drawer<LicensesView> {
@Composable
override fun LicensesView.draw() {
dev.inmo.jsuikit.elements.List(
licensesOffersToShow.value,
Attrs {
if (!searchFieldFocused.value) {
hidden()
}
}
) {
DefaultButton(
it.title,
UIKitButton.Type.Text
) { _ ->
licensesListState.add(it.toLicenseState())
licenseSearchFilter = ""
}
Divider.Common()
}
DefaultButton("Add empty license", UIKitButton.Type.Primary, UIKitMargin.Small) {
licensesListState.add(LicenseState())
}
licensesListState.forEach { license ->
Div(UIKitMargin.Small.builder()) {
CommonTextField(
license.id,
"License ID",
) { license.id = it }
CommonTextField(
license.title,
"License title",
) { license.title = it }
CommonTextField(
license.url ?: "",
"License URL",
) { license.url = it }
DefaultButton("Remove", UIKitButton.Type.Danger, UIKitMargin.Small) {
licensesListState.remove(license)
}
}
}
}
}
@@ -0,0 +1,24 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.elements.DefaultButton
import dev.inmo.jsuikit.modifiers.UIKitButton
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.jsuikit.modifiers.builder
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import org.jetbrains.compose.web.dom.Div
actual class ListViewDrawer<T> : Drawer<ListView<T>> {
@Composable
override fun ListView<T>.draw() {
DefaultButton(addItemText, UIKitButton.Type.Primary ) { itemsList.add(createItem()) }
itemsList.forEach { item ->
Div(UIKitMargin.Small.builder()) {
buildView(item)
DefaultButton(removeItemText, UIKitButton.Type.Danger, UIKitMargin.Small) {
itemsList.remove(item)
}
}
}
}
}
@@ -0,0 +1,25 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.elements.DefaultButton
import dev.inmo.jsuikit.modifiers.UIKitButton
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.kmppscriptbuilder.core.models.ProjectType
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual class ProjectTypeDrawer(
private val projectTypeView: ProjectTypeView
) : Drawer<ProjectType> {
@Composable
override fun ProjectType.draw() {
if (projectTypeView.projectType == this) {
DefaultButton(name, UIKitButton.Type.Primary)
} else {
DefaultButton(name, UIKitButton.Type.Default) {
projectTypeView.projectType = this
}
}
}
}
actual fun ProjectTypeDrawerWithView(view: ProjectTypeView): ProjectTypeDrawer = ProjectTypeDrawer(projectTypeView = view)
@@ -0,0 +1,82 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.elements.Icon
import dev.inmo.jsuikit.elements.NavItemElement
import dev.inmo.jsuikit.elements.Navbar
import dev.inmo.jsuikit.elements.NavbarNav
import dev.inmo.jsuikit.elements.drawAsLink
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.jsuikit.modifiers.UIKitPadding
import dev.inmo.jsuikit.modifiers.UIKitText
import dev.inmo.jsuikit.modifiers.UIKitTooltipModifier
import dev.inmo.jsuikit.modifiers.builder
import dev.inmo.jsuikit.modifiers.include
import dev.inmo.jsuikit.utils.AttrsWithContentBuilder
import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.core.utils.exportGradle
import dev.inmo.kmppscriptbuilder.core.utils.openNewConfig
import dev.inmo.kmppscriptbuilder.core.utils.saveConfig
import org.jetbrains.compose.web.dom.A
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.Img
import org.jetbrains.compose.web.dom.Text
@Composable
actual fun TopAppBar(
config: Config,
saveAvailable: Boolean,
onSaveAvailable: (Boolean) -> Unit,
onNewConfig: (Config) -> Unit
) {
Navbar(
leftBuilder = AttrsWithContentBuilder {
Div(
{
onClick {
console.log(config)
}
include(UIKitPadding.Size.Small, UIKitText.Style.Lead)
}
) {
Text("Kotlin publication scripts builder")
}
Div(UIKitMargin.Small.builder()) {
A("https://github.com/InsanusMokrassar/KotlinPublicationScriptsBuilder") {
Img("https://img.shields.io/github/stars/InsanusMokrassar/KotlinPublicationScriptsBuilder?label=Github&style=plastic")
}
}
},
rightBuilder = AttrsWithContentBuilder {
NavbarNav(
AttrsWithContentBuilder {
NavItemElement(
UIKitTooltipModifier("Open file")
) {
Icon.Storage.Pull.drawAsLink {
openNewConfig(onNewConfig)
}
}
},
AttrsWithContentBuilder {
NavItemElement(
UIKitTooltipModifier("Save config")
) {
Icon.Storage.Push.drawAsLink {
saveConfig(config)
}
}
},
AttrsWithContentBuilder {
NavItemElement(
UIKitTooltipModifier("Export gradle script")
) {
Icon.Storage.Upload.drawAsLink {
exportGradle(config)
}
}
},
)
}
)
}
@@ -1,6 +1,10 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.modifiers.UIKitForm
import dev.inmo.jsuikit.modifiers.builder
import org.jetbrains.compose.web.dom.Legend
import org.jetbrains.compose.web.dom.Text
actual abstract class View {
@Composable
@@ -9,4 +13,8 @@ actual abstract class View {
@Composable
actual fun View.DrawVertically(title: String, block: @Composable () -> Unit) {
Legend(UIKitForm.Legend.builder()) {
Text(title)
}
block()
}
@@ -0,0 +1,107 @@
package dev.inmo.kmppscriptbuilder.core.ui.utils
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.elements.DefaultButton
import dev.inmo.jsuikit.elements.DefaultInput
import dev.inmo.jsuikit.elements.Divider
import dev.inmo.jsuikit.elements.Flex
import dev.inmo.jsuikit.elements.Icon
import dev.inmo.jsuikit.elements.Label
import dev.inmo.jsuikit.elements.drawAsFormInputPart
import dev.inmo.jsuikit.modifiers.UIKitButton
import dev.inmo.jsuikit.modifiers.UIKitFlex
import dev.inmo.jsuikit.modifiers.UIKitForm
import dev.inmo.jsuikit.modifiers.UIKitInverse
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.jsuikit.modifiers.UIKitText
import dev.inmo.jsuikit.modifiers.UIKitUtility
import dev.inmo.jsuikit.modifiers.builder
import dev.inmo.jsuikit.modifiers.include
import dev.inmo.jsuikit.utils.Attrs
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.Legend
import org.jetbrains.compose.web.dom.Text
@Composable
actual fun TitleText(text: String) {
Legend(UIKitForm.Legend.builder()) {
Text(text)
}
}
@Composable
actual fun CommonText(text: String, onClick: (() -> Unit)?) {
Div(
{
onClick ?.let {
this.onClick { _ ->
it()
}
}
}
) {
Text(text)
}
}
@Composable
actual fun CommonTextField(
presetText: String,
hint: String,
onFocusChanged: (Boolean) -> Unit,
onChange: (String) -> Unit
) {
DefaultInput(
InputType.Text,
presetText,
false,
placeholder = hint,
attributesCustomizer = {
onFocusIn { onFocusChanged(true) }
onFocusOut { onFocusChanged(false) }
},
onChange = onChange
)
}
@Composable
actual fun SwitchWithLabel(
label: String,
checked: Boolean,
placeSwitchAtTheStart: Boolean,
switchEnabled: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
DefaultButton(
if (checked) {
UIKitButton.Type.Primary
} else {
UIKitButton.Type.Default
},
disabled = !switchEnabled,
onClick = {
onCheckedChange(!checked)
},
attributesCustomizer = {
include(UIKitUtility.Inline)
}
) {
if (checked) {
Icon.App.Check.drawAsFormInputPart(UIKitInverse.Light)
}
Text(label)
}
}
@Composable
actual fun <T> ButtonsPanel(data: Iterable<T>, itemDrawer: @Composable (T) -> Unit) {
Flex(UIKitFlex.Alignment.Vertical.Middle, UIKitMargin.Small) {
data.forEach { itemDrawer(it) }
}
}
@Composable
actual fun DefaultDivider() {
Divider.Common()
}
@@ -0,0 +1,83 @@
package dev.inmo.kmppscriptbuilder.core.utils
import dev.inmo.kmppscriptbuilder.core.models.Config
import kotlinx.browser.document
import kotlinx.dom.appendElement
import org.w3c.dom.HTMLAnchorElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.url.URL
import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag
import org.w3c.files.FileReader
import org.w3c.files.get
fun saveFile(content: String, filename: String) {
val a = document.body!!.appendElement("a") {
setAttribute("style", "visibility:hidden; display: none")
} as HTMLAnchorElement
val blob = Blob(arrayOf(content), BlobPropertyBag(
"application/*;charset=utf-8"
))
val url = URL.createObjectURL(blob)
a.href = url
a.download = filename
a.click()
URL.revokeObjectURL(url)
a.remove()
}
actual fun openNewConfig(onParsed: (Config) -> Unit) {
val targetInput = document.body!!.appendElement("input") {
setAttribute("style", "visibility:hidden; display: none")
} as HTMLInputElement
targetInput.type = "file"
targetInput.onchange = {
targetInput.files ?.also { files ->
for (i in (0 until files.length) ) {
files[i] ?.also { file ->
val reader = FileReader()
reader.onload = {
val content = it.target.asDynamic().result as String
onParsed(serialFormat.decodeFromString(Config.serializer(), content))
false
}
reader.readAsText(file)
}
}
}
}
targetInput.click()
targetInput.remove()
}
actual fun saveConfig(config: Config): Boolean {
saveFile(
serialFormat.encodeToString(Config.serializer(), config),
"publish.kpsb"
)
return true
}
actual fun exportGradle(config: Config): Boolean {
val filename = "publish.gradle"
val content = config.run {
type.buildMavenGradleConfig(
mavenConfig,
licenses
)
}
saveFile(content, filename)
return true
}
actual fun saveAs(config: Config): Boolean {
saveFile(
serialFormat.encodeToString(Config.serializer(), config),
"publish.kpsb"
)
return true
}
@@ -0,0 +1,6 @@
package dev.inmo.kmppscriptbuilder.core.utils
actual fun openLink(link: String): Boolean {
dev.inmo.micro_utils.common.openLink(link)
return true
}
+1
View File
@@ -0,0 +1 @@
/*! UIkit 3.15.12 | https://www.getuikit.com | (c) 2014 - 2022 YOOtheme | MIT License */html{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:16px;font-weight:400;line-height:1.5;-webkit-text-size-adjust:100%;background:#fff;color:#666}body{margin:0}.uk-link,a{color:#1e87f0;text-decoration:none;cursor:pointer}.uk-link-toggle:hover .uk-link,.uk-link:hover,a:hover{color:#0f6ecd;text-decoration:underline}abbr[title]{text-decoration:underline dotted;-webkit-text-decoration-style:dotted}b,strong{font-weight:bolder}:not(pre)>code,:not(pre)>kbd,:not(pre)>samp{font-family:Consolas,monaco,monospace;font-size:.875rem;color:#f0506e;white-space:nowrap;padding:2px 6px;background:#f8f8f8}em{color:#f0506e}ins{background:#ffd;color:#666;text-decoration:none}mark{background:#ffd;color:#666}q{font-style:italic}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}audio,canvas,iframe,img,svg,video{vertical-align:middle}canvas,img,svg,video{max-width:100%;height:auto;box-sizing:border-box}img:not([src]){visibility:hidden;min-width:1px}iframe{border:0}address,dl,fieldset,figure,ol,p,pre,ul{margin:0 0 20px 0}*+address,*+dl,*+fieldset,*+figure,*+ol,*+p,*+pre,*+ul{margin-top:20px}.uk-h1,.uk-h2,.uk-h3,.uk-h4,.uk-h5,.uk-h6,.uk-heading-2xlarge,.uk-heading-large,.uk-heading-medium,.uk-heading-small,.uk-heading-xlarge,h1,h2,h3,h4,h5,h6{margin:0 0 20px 0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-weight:400;color:#333;text-transform:none}*+.uk-h1,*+.uk-h2,*+.uk-h3,*+.uk-h4,*+.uk-h5,*+.uk-h6,*+.uk-heading-2xlarge,*+.uk-heading-large,*+.uk-heading-medium,*+.uk-heading-small,*+.uk-heading-xlarge,*+h1,*+h2,*+h3,*+h4,*+h5,*+h6{margin-top:40px}.uk-h1,h1{font-size:2.23125rem;line-height:1.2}.uk-h2,h2{font-size:1.7rem;line-height:1.3}.uk-h3,h3{font-size:1.5rem;line-height:1.4}.uk-h4,h4{font-size:1.25rem;line-height:1.4}.uk-h5,h5{font-size:16px;line-height:1.4}.uk-h6,h6{font-size:.875rem;line-height:1.4}@media (min-width:960px){.uk-h1,h1{font-size:2.625rem}.uk-h2,h2{font-size:2rem}}ol,ul{padding-left:30px}ol>li>ol,ol>li>ul,ul>li>ol,ul>li>ul{margin:0}dt{font-weight:700}dd{margin-left:0}.uk-hr,hr{overflow:visible;text-align:inherit;margin:0 0 20px 0;border:0;border-top:1px solid #e5e5e5}*+.uk-hr,*+hr{margin-top:20px}address{font-style:normal}blockquote{margin:0 0 20px 0;font-size:1.25rem;line-height:1.5;font-style:italic;color:#333}*+blockquote{margin-top:20px}blockquote p:last-of-type{margin-bottom:0}blockquote footer{margin-top:10px;font-size:.875rem;line-height:1.5;color:#666}blockquote footer::before{content:"— "}pre{font:0.875rem/1.5 Consolas,monaco,monospace;color:#666;-moz-tab-size:4;tab-size:4;overflow:auto;padding:10px;border:1px solid #e5e5e5;border-radius:3px;background:#fff}pre code{font-family:Consolas,monaco,monospace}:focus{outline:0}:focus-visible{outline:2px dotted #333}::selection{background:#39f;color:#fff;text-shadow:none}details,main{display:block}summary{display:list-item}template{display:none}:root{--uk-breakpoint-s:640px;--uk-breakpoint-m:960px;--uk-breakpoint-l:1200px;--uk-breakpoint-xl:1600px}.uk-link-muted a,.uk-link-toggle .uk-link-muted,a.uk-link-muted{color:#999}.uk-link-muted a:hover,.uk-link-toggle:hover .uk-link-muted,a.uk-link-muted:hover{color:#666}.uk-link-text a,.uk-link-toggle .uk-link-text,a.uk-link-text{color:inherit}.uk-link-text a:hover,.uk-link-toggle:hover .uk-link-text,a.uk-link-text:hover{color:#999}.uk-link-heading a,.uk-link-toggle .uk-link-heading,a.uk-link-heading{color:inherit}.uk-link-heading a:hover,.uk-link-toggle:hover .uk-link-heading,a.uk-link-heading:hover{color:#1e87f0;text-decoration:none}.uk-link-reset a,a.uk-link-reset{color:inherit!important;text-decoration:none!important}.uk-link-toggle{color:inherit!important;text-decoration:none!important}.uk-heading-small{font-size:2.6rem;line-height:1.2}.uk-heading-medium{font-size:2.8875rem;line-height:1.1}.uk-heading-large{font-size:3.4rem;line-height:1.1}.uk-heading-xlarge{font-size:4rem;line-height:1}.uk-heading-2xlarge{font-size:6rem;line-height:1}@media (min-width:960px){.uk-heading-small{font-size:3.25rem}.uk-heading-medium{font-size:3.5rem}.uk-heading-large{font-size:4rem}.uk-heading-xlarge{font-size:6rem}.uk-heading-2xlarge{font-size:8rem}}@media (min-width:1200px){.uk-heading-medium{font-size:4rem}.uk-heading-large{font-size:6rem}.uk-heading-xlarge{font-size:8rem}.uk-heading-2xlarge{font-size:11rem}}.uk-heading-divider{padding-bottom:calc(5px + .1em);border-bottom:calc(.2px + .05em) solid #e5e5e5}.uk-heading-bullet{position:relative}.uk-heading-bullet::before{content:"";display:inline-block;position:relative;top:calc(-.1 * 1em);vertical-align:middle;height:calc(4px + .7em);marLine truncated
+72
View File
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Kotlin Publication Scripts Builder</title>
<!-- UIkit CSS -->
<link rel="stylesheet" href="./css/uikit.min.css" />
</head>
<body>
<!-- <form class="uk-padding-small">-->
<!-- <fieldset class="uk-fieldset">-->
<!-- <legend class="uk-legend">Project type</legend>-->
<!-- <div class="uk-padding-small">-->
<!-- <ul class="uk-subnav uk-subnav-pill">-->
<!-- <li id="mppProjectType" class="uk-active"><a href="#">Multiplatform</a></li>-->
<!-- <li id="jvmProjectType"><a href="#">JVM</a></li>-->
<!-- <li id="jsProjectType"><a href="#">JS</a></li>-->
<!-- </ul>-->
<!-- </div>-->
<!-- <legend class="uk-legend">Licenses</legend>-->
<!-- <div id="licensesListDiv" class="uk-padding-small"></div>-->
<!-- <legend class="uk-legend">Project information</legend>-->
<!-- <div class="uk-padding-small">-->
<!-- <div class="uk-margin uk-width-1-1">-->
<!-- <label class="uk-form-label" for="projectNameInput">Public project name</label>-->
<!-- <input id="projectNameInput" class="uk-input uk-width-expand" type="text" placeholder="${project.name}">-->
<!-- </div>-->
<!-- <div class="uk-margin uk-width-1-1">-->
<!-- <label class="uk-form-label" for="projectDescriptionInput">Public project description</label>-->
<!-- <input id="projectDescriptionInput" class="uk-input uk-width-expand" type="text" placeholder="${project.name}">-->
<!-- </div>-->
<!-- <div class="uk-margin uk-width-1-1">-->
<!-- <label class="uk-form-label" for="projectUrlInput">Public project URL</label>-->
<!-- <input id="projectUrlInput" class="uk-input uk-width-expand" type="text" placeholder="Type url to github or other source with readme">-->
<!-- </div>-->
<!-- <div class="uk-margin uk-width-1-1">-->
<!-- <label class="uk-form-label" for="projectVCSUrlInput">Public project VCS URL (with .git)</label>-->
<!-- <input id="projectVCSUrlInput" class="uk-input uk-width-expand" type="text" placeholder="Type url to github .git file">-->
<!-- </div>-->
<!-- <div class="uk-margin">-->
<!-- <label>GPG Signing</label>-->
<!-- <div class="uk-padding-small">-->
<!-- <ul class="uk-subnav uk-subnav-pill">-->
<!-- <li id="disableGpgSigning" class="uk-active" uk-tooltip="title: Signing will not be added"><a href="#">Disabled</a></li>-->
<!-- <li id="optionalGpgSigning" uk-tooltip="title: Signing will be added, but disabled in case of absence 'signatory.keyId'"><a href="#">Optional</a></li>-->
<!-- <li id="enableGpgSigning" uk-tooltip="title: Signing will be always enabled"><a href="#">Enabled</a></li>-->
<!-- </ul>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="uk-margin">-->
<!-- <label><input id="includeMavenCentralTargetRepoToggle" class="uk-checkbox" type="checkbox"> Include publication to MavenCentral</label>-->
<!-- </div>-->
<!-- </div>-->
<!-- <legend class="uk-legend">Developers info</legend>-->
<!-- <div id="developersListDiv" class="uk-padding-small"></div>-->
<!-- <legend class="uk-legend">Repositories info</legend>-->
<!-- <div id="repositoriesListDiv" class="uk-padding-small"></div>-->
<!-- </fieldset>-->
<!-- </form>-->
<!-- UIkit JS -->
<script src="./js/uikit.min.js"></script>
<script src="./js/uikit-icons.min.js"></script>
<!-- Internal JS -->
<script src="kmppscriptbuilder.core.js"></script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
/*! UIkit 3.15.12 | https://www.getuikit.com | (c) 2014 - 2022 YOOtheme | MIT License */(function(t,i){typeof exports=="object"&&typeof module<"u"?module.exports=i():typeof define=="function"&&define.amd?define("uikiticons",i):(t=typeof globalThis<"u"?globalThis:t||self,t.UIkitIcons=i())})(this,function(){"use strict";function t(i){t.installed||i.icon.add({"500px":'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M9.624,11.866c-0.141,0.132,0.479,0.658,0.662,0.418c0.051-0.046,0.607-0.61,0.662-0.664c0,0,0.738,0.719,0.814,0.719 c0.1,0,0.207-0.055,0.322-0.17c0.27-0.269,0.135-0.416,0.066-0.495l-0.631-0.616l0.658-0.668c0.146-0.156,0.021-0.314-0.1-0.449 c-0.182-0.18-0.359-0.226-0.471-0.125l-0.656,0.654l-0.654-0.654c-0.033-0.034-0.08-0.045-0.124-0.045 c-0.079,0-0.191,0.068-0.307,0.181c-0.202,0.202-0.247,0.351-0.133,0.462l0.665,0.665L9.624,11.866z"/><path d="M11.066,2.884c-1.061,0-2.185,0.248-3.011,0.604c-0.087,0.034-0.141,0.106-0.15,0.205C7.893,3.784,7.919,3.909,7.982,4.066 c0.05,0.136,0.187,0.474,0.452,0.372c0.844-0.326,1.779-0.507,2.633-0.507c0.963,0,1.9,0.191,2.781,0.564 c0.695,0.292,1.357,0.719,2.078,1.34c0.051,0.044,0.105,0.068,0.164,0.068c0.143,0,0.273-0.137,0.389-0.271 c0.191-0.214,0.324-0.395,0.135-0.575c-0.686-0.654-1.436-1.138-2.363-1.533C13.24,3.097,12.168,2.884,11.066,2.884z"/><path d="M16.43,15.747c-0.092-0.028-0.242,0.05-0.309,0.119l0,0c-0.652,0.652-1.42,1.169-2.268,1.521 c-0.877,0.371-1.814,0.551-2.779,0.551c-0.961,0-1.896-0.189-2.775-0.564c-0.848-0.36-1.612-0.879-2.268-1.53 c-0.682-0.688-1.196-1.455-1.529-2.268c-0.325-0.799-0.471-1.643-0.471-1.643c-0.045-0.24-0.258-0.249-0.567-0.203 c-0.128,0.021-0.519,0.079-0.483,0.36v0.01c0.105,0.644,0.289,1.284,0.545,1.895c0.417,0.969,1.002,1.849,1.756,2.604 c0.757,0.754,1.636,1.34,2.604,1.757C8.901,18.785,9.97,19,11.088,19c1.104,0,2.186-0.215,3.188-0.645 c1.838-0.896,2.604-1.757,2.604-1.757c0.182-0.204,0.227-0.317-0.1-0.643C16.779,15.956,16.525,15.774,16.43,15.747z"/><path d="M5.633,13.287c0.293,0.71,0.723,1.341,1.262,1.882c0.54,0.54,1.172,0.971,1.882,1.264c0.731,0.303,1.509,0.461,2.298,0.461 c0.801,0,1.578-0.158,2.297-0.461c0.711-0.293,1.344-0.724,1.883-1.264c0.543-0.541,0.971-1.172,1.264-1.882 c0.314-0.721,0.463-1.5,0.463-2.298c0-0.79-0.148-1.569-0.463-2.289c-0.293-0.699-0.721-1.329-1.264-1.881 c-0.539-0.541-1.172-0.959-1.867-1.263c-0.721-0.303-1.5-0.461-2.299-0.461c-0.802,0-1.613,0.159-2.322,0.461 c-0.577,0.25-1.544,0.867-2.119,1.454v0.012V2.108h8.16C15.1,2.104,15.1,1.69,15.1,1.552C15.1,1.417,15.1,1,14.809,1H5.915 C5.676,1,5.527,1.192,5.527,1.384v6.84c0,0.214,0.273,0.372,0.529,0.428c0.5,0.105,0.614-0.056,0.737-0.224l0,0 c0.18-0.273,0.776-0.884,0.787-0.894c0.901-0.905,2.117-1.408,3.416-1.408c1.285,0,2.5,0.501,3.412,1.408 c0.914,0.914,1.408,2.122,1.408,3.405c0,1.288-0.508,2.496-1.408,3.405c-0.9,0.896-2.152,1.406-3.438,1.406 c-0.877,0-1.711-0.229-2.433-0.671v-4.158c0-0.553,0.237-1.151,0.643-1.614c0.462-0.519,1.094-0.799,1.782-0.799 c0.664,0,1.293,0.253,1.758,0.715c0.459,0.459,0.709,1.071,0.709,1.723c0,1.385-1.094,2.468-2.488,2.468 c-0.273,0-0.769-0.121-0.781-0.125c-0.281-0.087-0.405,0.306-0.438,0.436c-0.159,0.496,0.079,0.585,0.123,0.607 c0.452,0.137,0.743,0.157,1.129,0.157c1.973,0,3.572-1.6,3.572-3.57c0-1.964-1.6-3.552-3.572-3.552c-0.97,0-1.872,0.36-2.546,1.038 c-0.656,0.631-1.027,1.487-1.027,2.322v3.438v-0.011c-0.372-0.42-0.732-1.041-0.981-1.682c-0.102-0.248-0.315-0.202-0.607-0.113 c-0.135,0.035-0.519,0.157-0.44,0.439C5.372,12.799,5.577,13.164,5.633,13.287z"/></svg>',album:'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><rect x="5" y="2" width="10" height="1"/><rect x="3" y="4" width="14" height="1"/><rect fill="none" stroke="#000" x="1.5" y="6.5" width="17" height="11"/></svg>',"arrow-down":'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><polygon points="10.5,16.08 5.63,10.66 6.37,10 10.5,14.58 14.63,10 15.37,10.66"/><line fill="none" stroke="#000" x1="10.5" y1="4" x2="10.5" y2="15"/></svg>',"arrow-left":'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><polyline fill="none" stroke="#000" points="10 14 5 9.5 10 5"/><line fill="none" stroke="#000" x1="16" y1="9.5" x2="5" y2="9.52"/></svg>',"arrow-right":'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><polyline fill="none" stroke="#000" points="10 5 15 9.5 10 14"/><line fill="none" stroke="#000" x1="4" y1="9.5" x2="15" y2="9.5"/></svg>',"arrow-up":'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><polygon points="10.5,4 15.37,9.4 14.63,10.08 10.5,5.49 6.37,10.08 5.63,9.4"/><line fill="none" stroke="#000" x1="10.5" y1="16" x2="10.5" y2="5"/></svg>',bag:'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="#000" d="M7.5,7.5V4A2.48,2.48,0,0,1,10,1.5,2.54,2.54,0,0,1,12.5,4V7.5"/><polygon fill="none" stroke="#000" points="16.5 7.5 3.5 7.5 2.5 18.Line truncated
+1
View File
@@ -0,0 +1 @@
/*! UIkit 3.15.12 | https://www.getuikit.com | (c) 2014 - 2022 YOOtheme | MIT License */(function(ue,de){typeof exports=="object"&&typeof module<"u"?module.exports=de():typeof define=="function"&&define.amd?define("uikit",de):(ue=typeof globalThis<"u"?globalThis:ue||self,ue.UIkit=de())})(this,function(){"use strict";const{hasOwnProperty:ue,toString:de}=Object.prototype;function Bt(t,e){return ue.call(t,e)}const uo=/\B([A-Z])/g,Xt=ft(t=>t.replace(uo,"-$1").toLowerCase()),fo=/-(\w)/g,fe=ft(t=>t.replace(fo,us)),St=ft(t=>t.length?us(null,t.charAt(0))+t.slice(1):"");function us(t,e){return e?e.toUpperCase():""}function lt(t,e){return t==null||t.startsWith==null?void 0:t.startsWith(e)}function Gt(t,e){return t==null||t.endsWith==null?void 0:t.endsWith(e)}function p(t,e){return t==null||t.includes==null?void 0:t.includes(e)}function bt(t,e){return t==null||t.findIndex==null?void 0:t.findIndex(e)}const{isArray:Q,from:xi}=Array,{assign:xt}=Object;function mt(t){return typeof t=="function"}function Tt(t){return t!==null&&typeof t=="object"}function yt(t){return de.call(t)==="[object Object]"}function Jt(t){return Tt(t)&&t===t.window}function De(t){return $i(t)===9}function yi(t){return $i(t)>=1}function Kt(t){return $i(t)===1}function $i(t){return!Jt(t)&&Tt(t)&&t.nodeType}function Me(t){return typeof t=="boolean"}function D(t){return typeof t=="string"}function Zt(t){return typeof t=="number"}function Dt(t){return Zt(t)||D(t)&&!isNaN(t-parseFloat(t))}function pe(t){return!(Q(t)?t.length:Tt(t)?Object.keys(t).length:!1)}function R(t){return t===void 0}function ki(t){return Me(t)?t:t==="true"||t==="1"||t===""?!0:t==="false"||t==="0"?!1:t}function Ct(t){const e=Number(t);return isNaN(e)?!1:e}function v(t){return parseFloat(t)||0}function j(t){return y(t)[0]}function y(t){return t&&(yi(t)?[t]:Array.from(t).filter(yi))||[]}function Mt(t){var e;if(Jt(t))return t;t=j(t);const i=De(t)?t:(e=t)==null?void 0:e.ownerDocument;return(i==null?void 0:i.defaultView)||window}function ge(t,e){return t===e||Tt(t)&&Tt(e)&&Object.keys(t).length===Object.keys(e).length&&$t(t,(i,s)=>i===e[s])}function Si(t,e,i){return t.replace(new RegExp(e+"|"+i,"g"),s=>s===e?i:e)}function Qt(t){return t[t.length-1]}function $t(t,e){for(const i in t)if(e(t[i],i)===!1)return!1;return!0}function Ne(t,e){return t.slice().sort((i,s)=>{let{[e]:n=0}=i,{[e]:o=0}=s;return n>o?1:o>n?-1:0})}function ds(t,e){const i=new Set;return t.filter(s=>{let{[e]:n}=s;return i.has(n)?!1:i.add(n)})}function U(t,e,i){return e===void 0&&(e=0),i===void 0&&(i=1),Math.min(Math.max(Ct(t)||0,e),i)}function T(){}function Ti(){for(var t=arguments.length,e=new Array(t),i=0;i<t;i++)e[i]=arguments[i];return[["bottom","top"],["right","left"]].every(s=>{let[n,o]=s;return Math.min(...e.map(r=>{let{[n]:a}=r;return a}))-Math.max(...e.map(r=>{let{[o]:a}=r;return a}))>0})}function ze(t,e){return t.x<=e.right&&t.x>=e.left&&t.y<=e.bottom&&t.y>=e.top}function Ci(t,e,i){const s=e==="width"?"height":"width";return{[s]:t[e]?Math.round(i*t[s]/t[e]):t[s],[e]:i}}function fs(t,e){t={...t};for(const i in t)t=t[i]>e[i]?Ci(t,i,e[i]):t;return t}function po(t,e){t=fs(t,e);for(const i in t)t=t[i]<e[i]?Ci(t,i,e[i]):t;return t}const Fe={ratio:Ci,contain:fs,cover:po};function Ut(t,e,i,s){i===void 0&&(i=0),s===void 0&&(s=!1),e=y(e);const{length:n}=e;return n?(t=Dt(t)?Ct(t):t==="next"?i+1:t==="previous"?i-1:e.indexOf(j(t)),s?U(t,0,n-1):(t%=n,t<0?t+n:t)):-1}function ft(t){const e=Object.create(null);return i=>e[i]||(e[i]=t(i))}class He{constructor(){this.promise=new Promise((e,i)=>{this.reject=i,this.resolve=e})}}function w(t,e,i){if(Tt(e)){for(const n in e)w(t,n,e[n]);return}if(R(i)){var s;return(s=j(t))==null?void 0:s.getAttribute(e)}else for(const n of y(t))mt(i)&&(i=i.call(n,w(n,e))),i===null?me(n,e):n.setAttribute(e,i)}function It(t,e){return y(t).some(i=>i.hasAttribute(e))}function me(t,e){const i=y(t);for(const s of e.split(" "))for(const n of i)n.removeAttribute(s)}function ot(t,e){for(const i of[e,"data-"+e])if(It(t,i))return w(t,i)}const go={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,menuitem:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0};function Ii(t){return y(t).some(e=>go[e.tagName.toLowerCase()])}function q(t){return y(t).some(e=>e.offsetWidth||e.offsetHeight||e.getClientRects().length)}const ve="input,select,textarea,button";function Pi(t){return y(t).some(e=>F(e,ve))}const Le=ve+",a[href],[tabindex]";function We(t){return F(t,Le)}function E(t){var e;return(e=j(t))==null?void 0:e.parentElement}function Re(t,e){return y(t).filter(i=>F(i,e))}function F(t,e){return y(t).some(i=>i.matches(e))}function et(t,e){return Kt(t)?t.closest(lt(e,">")?e.slice(1):e):y(t).map(i=>et(i,e)).filter(Boolean)}function P(t,e){return D(e)?!!et(t,e):j(e).contains(j(t))}function te(t,e){const i=[];for(;t=E(t);)(!e||F(t,e))&&i.push(t);return i}function O(t,e){t=j(t);const i=t?y(t.children):[];return e?Re(i,e):i}function ee(t,e){return e?y(t).indexOf(j(e)):O(E(t)).indexOf(t)}function ht(t,e){returLine truncated
@@ -0,0 +1,68 @@
package dev.inmo.kmppscriptbuilder.core
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import dev.inmo.kmppscriptbuilder.core.ui.BuilderView
import dev.inmo.kmppscriptbuilder.core.utils.loadConfigFile
import java.io.File
//private val uncaughtExceptionsBC = BroadcastChannel<DefaultErrorHandler.ErrorEvent>(Channel.CONFLATED)
//val uncaughtExceptionsFlow: Flow<DefaultErrorHandler.ErrorEvent> = uncaughtExceptionsBC.asFlow()
fun main(args: Array<String>) = application {
Window(onCloseRequest = ::exitApplication, title = "Kotlin Multiplatform Publishing Builder") {
val builder = BuilderView()
MaterialTheme(
Colors(
primary = Color(0x01, 0x57, 0x9b),
primaryVariant = Color(0x00, 0x2f, 0x6c),
secondary = Color(0xb2, 0xeb, 0xf2),
secondaryVariant = Color(0x81, 0xb9, 0xbf),
background = Color(0xe1, 0xe2, 0xe1),
surface = Color(0xf5, 0xf5, 0xf6),
error = Color(0xb7, 0x1c, 0x1c),
onPrimary = Color.White,
onSecondary = Color.Black,
onBackground = Color.Black,
onSurface = Color.Black,
onError = Color.White,
isLight = MaterialTheme.colors.isLight,
)
) {
Box(
Modifier.fillMaxSize()
.background(color = Color(245, 245, 245))
) {
val stateVertical = rememberScrollState(0)
Box(
modifier = Modifier
.fillMaxSize()
.verticalScroll(stateVertical)
) {
Column {
builder.build()
}
}
VerticalScrollbar(
modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(),
adapter = rememberScrollbarAdapter(stateVertical)
)
}
}
if (args.isNotEmpty()) {
val config = loadConfigFile(File(args.first()))
builder.config = config
}
}
}
@@ -1,65 +0,0 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Divider
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonText
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.utils.exportGradle
import dev.inmo.kmppscriptbuilder.core.utils.loadConfig
import dev.inmo.kmppscriptbuilder.core.utils.saveAs
import dev.inmo.kmppscriptbuilder.core.utils.saveConfig
actual object BuilderViewDrawer : Drawer<BuilderView> {
override fun BuilderView.draw() {
Box(Modifier.fillMaxSize()) {
Column() {
TopAppBar(
@Composable {
Text("Kotlin publication scripts builder", Modifier.clickable { println(config) })
},
actions = {
createIcon("Open file", "images/open_file.svg") {
loadConfig()?.also {
config = it
}
}
if (saveAvailableState) {
createIcon("Save", "images/save_file.svg") {
saveConfig(config)
}
}
if (saveAvailableState) {
createIcon("Export Gradle script", "images/export_gradle.svg") {
exportGradle(config)
}
}
createIcon("Save as", "images/save_as.svg") {
if (saveAs(config)) {
saveAvailableState = true
}
}
}
)
Column(Modifier.padding(8.dp)) {
projectTypeView.init()
Divider()
licensesView.init()
Divider()
mavenInfoView.init()
}
}
}
}
}
@@ -0,0 +1,39 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.models.GpgSigning
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.ui.utils.SwitchWithLabel
actual class GpgSigningOptionDrawer(
private val mavenInfoView: MavenInfoView
) : Drawer<GpgSigning> {
@Composable
override fun GpgSigning.draw() {
if (mavenInfoView.gpgSignProperty == this) {
Button({}, Modifier.padding(8.dp)) {
Text(name)
}
} else {
OutlinedButton(
{
mavenInfoView.gpgSignProperty = this
},
Modifier.padding(8.dp)
) {
Text(name)
}
}
}
}
actual fun GpgSigningOptionDrawerWithView(view: MavenInfoView): GpgSigningOptionDrawer = GpgSigningOptionDrawer(mavenInfoView = view)
@@ -4,6 +4,9 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.Divider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonText
@@ -11,44 +14,39 @@ import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual object LicensesDrawer : Drawer<LicensesView> {
@Composable
override fun LicensesView.draw() {
CommonTextField(licenseSearchFilter, "Search filter") { filterText ->
licenseSearchFilter = filterText
licensesOffersToShow.clear()
if (licenseSearchFilter.isNotEmpty()) {
licensesOffersToShow.addAll(
availableLicensesState.filter { filterText.all { symbol -> symbol.lowercaseChar() in it.title } }
)
}
}
Column {
licensesOffersToShow.forEach {
Column(Modifier.padding(16.dp, 8.dp, 8.dp, 8.dp)) {
CommonText(it.title) {
licensesListState.add(it.toLicenseState())
licenseSearchFilter = ""
licensesOffersToShow.clear()
if (searchFieldFocused.value) {
Column {
licensesOffersToShow.value.forEach {
Column(Modifier.padding(16.dp, 8.dp, 8.dp, 8.dp)) {
CommonText(it.title) {
licensesListState.add(it.toLicenseState())
licenseSearchFilter = ""
}
Divider()
}
Divider()
}
}
}
Button({ licensesListState.add(LicenseState()) }, Modifier.padding(8.dp)) {
CommonText("Add empty license",)
CommonText("Add empty license")
}
licensesListState.forEach { license ->
Column(Modifier.padding(8.dp)) {
CommonTextField(
license.id,
"License ID"
"License ID",
) { license.id = it }
CommonTextField(
license.title,
"License title"
"License title",
) { license.title = it }
CommonTextField(
license.url ?: "",
"License URL"
"License URL",
) { license.url = it }
Button({ licensesListState.remove(license) }, Modifier.padding(8.dp)) {
CommonText("Remove",)
@@ -3,12 +3,14 @@ package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonText
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual class ListViewDrawer<T> : Drawer<ListView<T>> {
@Composable
override fun ListView<T>.draw() {
Button({ itemsList.add(createItem()) }) {
CommonText(addItemText,)
@@ -1,68 +0,0 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.models.GpgSigning
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.ui.utils.SwitchWithLabel
actual object MavenInfoDrawer : Drawer<MavenInfoView> {
@Composable
private fun MavenInfoView.addGpgSigningButton(gpgSigning: GpgSigning) {
if (gpgSignProperty == gpgSigning) {
Button({}, Modifier.padding(8.dp)) {
Text(gpgSigning.name)
}
} else {
OutlinedButton(
{
gpgSignProperty = gpgSigning
},
Modifier.padding(8.dp)
) {
Text(gpgSigning.name)
}
}
}
override fun MavenInfoView.draw() {
CommonTextField(
projectNameProperty,
"Public project name"
) { projectNameProperty = it }
CommonTextField(
projectDescriptionProperty,
"Public project description"
) { projectDescriptionProperty = it }
CommonTextField(
projectUrlProperty,
"Public project URL"
) { projectUrlProperty = it }
CommonTextField(
projectVcsUrlProperty,
"Public project VCS URL (with .git)"
) { projectVcsUrlProperty = it }
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Gpg Signing: ")
addGpgSigningButton(GpgSigning.Disabled)
addGpgSigningButton(GpgSigning.Optional)
addGpgSigningButton(GpgSigning.Enabled)
}
SwitchWithLabel(
"Include publication to MavenCentral",
publishToMavenCentralProperty,
placeSwitchAtTheStart = true
) { publishToMavenCentralProperty = it }
developersView.init()
repositoriesView.init()
}
}
@@ -15,30 +15,26 @@ import dev.inmo.kmppscriptbuilder.core.models.MultiplatformProjectType
import dev.inmo.kmppscriptbuilder.core.models.ProjectType
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual object ProjectTypeDrawer : Drawer<ProjectTypeView> {
actual class ProjectTypeDrawer(
private val projectTypeView: ProjectTypeView
) : Drawer<ProjectType> {
@Composable
private fun ProjectTypeView.addProjectTypeButton(newProjectType: ProjectType) {
if (projectType == newProjectType) {
override fun ProjectType.draw() {
if (projectTypeView.projectType == this) {
Button({}, Modifier.padding(8.dp)) {
Text(newProjectType.name)
Text(name)
}
} else {
OutlinedButton(
{
projectType = newProjectType
projectTypeView.projectType = this
},
Modifier.padding(8.dp)
) {
Text(newProjectType.name)
Text(name)
}
}
}
override fun ProjectTypeView.draw() {
Row(verticalAlignment = Alignment.CenterVertically) {
addProjectTypeButton(MultiplatformProjectType)
addProjectTypeButton(JVMProjectType)
addProjectTypeButton(JSProjectType)
}
}
}
actual fun ProjectTypeDrawerWithView(view: ProjectTypeView): ProjectTypeDrawer = ProjectTypeDrawer(projectTypeView = view)
@@ -1,18 +0,0 @@
package dev.inmo.kmppscriptbuilder.core.ui
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual object RepositoryStateDrawer : Drawer<RepositoriesView> {
override fun RepositoriesView.draw() {
CommonTextField(
item.name,
"Repository name"
) { item.name = it }
CommonTextField(
item.url,
"Repository url"
) { item.url = it }
}
}
@@ -0,0 +1,88 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.TooltipArea
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.primarySurface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.core.utils.exportGradle
import dev.inmo.kmppscriptbuilder.core.utils.openNewConfig
import dev.inmo.kmppscriptbuilder.core.utils.saveAs
import dev.inmo.kmppscriptbuilder.core.utils.saveConfig
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun createIcon(
tooltip: String,
resource: String,
onClick: () -> Unit
) {
TooltipArea(
tooltip = {
Surface(
modifier = Modifier.shadow(4.dp),
color = MaterialTheme.colors.primarySurface,
shape = RoundedCornerShape(4.dp)
) {
Text(tooltip, modifier = Modifier.padding(10.dp), color = MaterialTheme.colors.onPrimary)
}
}
) {
IconButton(onClick) {
Image(
painter = painterResource(resource),
contentDescription = tooltip
)
}
}
}
@Composable
actual fun TopAppBar(
config: Config,
saveAvailable: Boolean,
onSaveAvailable: (Boolean) -> Unit,
onNewConfig: (Config) -> Unit
) {
TopAppBar(
@Composable {
Text("Kotlin publication scripts builder", Modifier.clickable { println(config) })
},
actions = {
createIcon("Open file", "images/open_file.svg") {
openNewConfig(onNewConfig)
}
if (saveAvailable) {
createIcon("Save", "images/save_file.svg") {
saveConfig(config)
}
}
if (saveAvailable) {
createIcon("Export Gradle script", "images/export_gradle.svg") {
exportGradle(config)
}
}
createIcon("Save as", "images/save_as.svg") {
if (saveAs(config)) {
onSaveAvailable(true)
}
}
}
)
}
@@ -6,12 +6,14 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Divider
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -29,15 +31,15 @@ actual fun SwitchWithLabel(
switchEnabled: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
Row(Modifier.padding(0.dp, 8.dp), Arrangement.Start, Alignment.Top) {
Row(Modifier.padding(0.dp, 8.dp).clickable { onCheckedChange(!checked) }, Arrangement.Start, Alignment.Top) {
val switchCreator = @Composable {
Switch(checked, onCheckedChange, Modifier.padding(8.dp, 0.dp), enabled = switchEnabled)
Switch(checked, null, Modifier.padding(8.dp, 0.dp), enabled = switchEnabled)
}
if (placeSwitchAtTheStart) {
switchCreator()
}
Box(Modifier.fillMaxWidth().align(Alignment.CenterVertically).clickable { }) {
CommonText(label,)
Box(Modifier.fillMaxWidth().align(Alignment.CenterVertically)) {
CommonText(label)
}
if (!placeSwitchAtTheStart) {
switchCreator()
@@ -46,11 +48,18 @@ actual fun SwitchWithLabel(
}
@Composable
actual fun CommonTextField(presetText: String, hint: String, onChange: (String) -> Unit) {
actual fun CommonTextField(
presetText: String,
hint: String,
onFocusChanged: (Boolean) -> Unit,
onChange: (String) -> Unit
) {
OutlinedTextField(
presetText,
onChange,
Modifier.fillMaxWidth(),
Modifier.fillMaxWidth().onFocusChanged {
onFocusChanged(it.isFocused)
},
singleLine = true,
label = {
CommonText(hint,)
@@ -69,3 +78,15 @@ actual fun TitleText(text: String) {
text, Modifier.padding(0.dp, 8.dp), fontSize = 18.sp
)
}
@Composable
actual fun <T> ButtonsPanel(data: Iterable<T>, itemDrawer: @Composable (T) -> Unit) {
Row {
data.forEach { itemDrawer(it) }
}
}
@Composable
actual fun DefaultDivider() {
Divider()
}
@@ -2,33 +2,29 @@ package dev.inmo.kmppscriptbuilder.core.utils
import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.core.ui.utils.FileFilter
import dev.inmo.kmppscriptbuilder.core.utils.serialFormat
import dev.inmo.micro_utils.common.MPPFile
import java.io.File
import javax.swing.JFileChooser
private const val appExtension = "kpsb"
fun MPPFile.text() = readText()
private var lastFile: File? = null
internal var lastFile: MPPFile? = null
fun loadConfigFile(file: File): Config {
fun loadConfigFile(file: MPPFile): Config {
lastFile = file
return serialFormat.decodeFromString(Config.serializer(), file.readText())
return serialFormat.decodeFromString(Config.serializer(), file.text())
}
actual fun MPPFile.text() = readText()
actual fun loadConfig(): Config? {
actual fun openNewConfig(onParsed: (Config) -> Unit) {
val fc = JFileChooser(lastFile ?.parent)
fc.addChoosableFileFilter(FileFilter("Kotlin Publication Scripts Builder", Regex(".*\\.$appExtension")))
fc.addChoosableFileFilter(FileFilter("JSON", Regex(".*\\.json")))
return when (fc.showOpenDialog(null)) {
when (fc.showOpenDialog(null)) {
JFileChooser.APPROVE_OPTION -> {
val file = fc.selectedFile
lastFile = file
return serialFormat.decodeFromString(Config.serializer(), fc.selectedFile.readText())
onParsed(serialFormat.decodeFromString(Config.serializer(), fc.selectedFile.readText()))
}
else -> null
}
}