Skip to content

Commit a737219

Browse files
committed
feat: json integration
1 parent 2fb065c commit a737219

File tree

14 files changed

+160
-13
lines changed

14 files changed

+160
-13
lines changed

README.md

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
- [x] Multiplatform: Android, Desktop
2121
- [x] State-aware: changes in the tree will trigger recomposition
2222
- [x] Unlimited levels
23-
- [x] [File system integration](#file-system)
23+
- [x] [File System integration](#file-system-integration)
24+
- [x] [JSON integration](#json-integration)
2425
- [x] [Built-in DSL](#dsl)
2526
- [x] [Expandable](#expanding--collapsing)
2627
- [x] [Selectable](#selecting)
@@ -65,10 +66,9 @@ Output:
6566

6667
**Take a look at the [sample app](https://github.com/adrielcafe/bonsai/blob/main/sample/src/main/java/cafe/adriel/bonsai/sample/SampleActivity.kt) for a working example.**
6768

68-
### File System
69-
Bonsai is integrated with file system, you should import `bonsai-file-system` module to use it.
69+
### File System integration
70+
Import `cafe.adriel.bonsai:bonsai-file-system` module to use it.
7071

71-
Instead of manually create the nodes, call `fileSystemNodes()` to generate for you based on a root path.
7272
```kotlin
7373
val tree = rememberTree<Path>(
7474
nodes = fileSystemNodes(
@@ -81,7 +81,7 @@ val tree = rememberTree<Path>(
8181

8282
Bonsai(
8383
tree = tree,
84-
// Custom style to show file and directory icons
84+
// Custom style
8585
style = FileSystemBonsaiStyle()
8686
)
8787
```
@@ -90,6 +90,26 @@ Output:
9090

9191
<img width=300 src="https://user-images.githubusercontent.com/2512298/163184371-a5a38003-44d9-4daa-8f41-6ee3914611f1.png">
9292

93+
### JSON integration
94+
Import `cafe.adriel.bonsai:bonsai-json` module to use it.
95+
96+
```kotlin
97+
val tree = rememberTree<Path>(
98+
// Sample JSON from https://rickandmortyapi.com/api/character
99+
nodes = jsonNodes(json)
100+
)
101+
102+
Bonsai(
103+
tree = tree,
104+
// Custom style
105+
style = JsonBonsaiStyle()
106+
)
107+
```
108+
109+
Output:
110+
111+
<img width=350 src="https://user-images.githubusercontent.com/2512298/163498419-f555d3df-e75d-4e88-9d87-7f29b14e1214.png">
112+
93113
### DSL
94114
Looking for a simpler and less verbose way to create a tree? Here's a handy DSL for you.
95115
```kotlin
@@ -210,6 +230,7 @@ Add the desired dependencies to your module's `build.gradle`:
210230
```gradle
211231
implementation "cafe.adriel.bonsai:bonsai-core:${latest-version}"
212232
implementation "cafe.adriel.bonsai:bonsai-file-system:${latest-version}"
233+
implementation "cafe.adriel.bonsai:bonsai-json:${latest-version}"
213234
```
214235

215236
Current version: ![Maven metadata URL](https://img.shields.io/maven-metadata/v?color=blue&metadataUrl=https://s01.oss.sonatype.org/service/local/repo_groups/public/content/cafe/adriel/bonsai/bonsai-core/maven-metadata.xml)

bonsai-core/src/commonMain/kotlin/cafe/adriel/bonsai/core/Bonsai.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,14 @@ public data class BonsaiStyle<T>(
6161
public val nodeCollapsedColorFilter: ColorFilter? = null,
6262
public val nodeExpandedIcon: NodeIcon<T> = nodeCollapsedIcon,
6363
public val nodeExpandedColorFilter: ColorFilter? = nodeCollapsedColorFilter,
64-
public val nodeNameStartPadding: Dp = 4.dp,
64+
public val nodeNameStartPadding: Dp = 0.dp,
6565
public val nodeNameTextStyle: TextStyle = DefaultNodeTextStyle
6666
) {
6767

6868
public companion object {
6969
public val DefaultNodeTextStyle: TextStyle = TextStyle(
7070
fontWeight = FontWeight.Medium,
71-
fontSize = 14.sp,
72-
letterSpacing = 0.1.sp
71+
fontSize = 12.sp
7372
)
7473
}
7574
}

bonsai-file-system/build.gradle.kts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ kotlin {
1212
val commonMain by getting {
1313
dependencies {
1414
api(projects.bonsaiCore)
15-
implementation(libs.okio)
16-
compileOnly(compose.runtime)
15+
api(libs.okio)
1716
compileOnly(compose.foundation)
1817
compileOnly(compose.ui)
1918
compileOnly(compose.materialIconsExtended)

bonsai-file-system/src/commonMain/kotlin/cafe/adriel/bonsai/filesystem/FileSystemNode.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.compose.material.icons.outlined.Folder
55
import androidx.compose.material.icons.outlined.FolderOpen
66
import androidx.compose.material.icons.outlined.InsertDriveFile
77
import androidx.compose.ui.graphics.vector.rememberVectorPainter
8+
import androidx.compose.ui.unit.dp
89
import cafe.adriel.bonsai.core.BonsaiStyle
910
import cafe.adriel.bonsai.core.node.BranchNode
1011
import cafe.adriel.bonsai.core.node.Node
@@ -19,6 +20,7 @@ internal data class FileSystemNodeScope(
1920

2021
public fun FileSystemBonsaiStyle(): BonsaiStyle<Path> =
2122
BonsaiStyle(
23+
nodeNameStartPadding = 4.dp,
2224
nodeCollapsedIcon = { node ->
2325
rememberVectorPainter(
2426
if (node is BranchNode) Icons.Outlined.Folder

bonsai-json/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

bonsai-json/build.gradle.kts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
plugins {
2+
kotlin("multiplatform")
3+
kotlin("plugin.serialization")
4+
id("com.android.library")
5+
id("org.jetbrains.compose")
6+
id("com.vanniktech.maven.publish")
7+
}
8+
9+
kotlinMultiplatform()
10+
11+
kotlin {
12+
sourceSets {
13+
val commonMain by getting {
14+
dependencies {
15+
api(projects.bonsaiCore)
16+
api(libs.serialization)
17+
compileOnly(compose.foundation)
18+
compileOnly(compose.ui)
19+
}
20+
}
21+
}
22+
}

bonsai-json/consumer-rules.pro

Whitespace-only changes.

bonsai-json/gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
POM_NAME=BonsaiJSON
2+
POM_ARTIFACT_ID=bonsai-json
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest package="cafe.adriel.bonsai.json"/>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package cafe.adriel.bonsai.json
2+
3+
import androidx.compose.ui.text.font.FontFamily
4+
import cafe.adriel.bonsai.core.BonsaiStyle
5+
import cafe.adriel.bonsai.core.node.Node
6+
import cafe.adriel.bonsai.core.node.SimpleBranchNode
7+
import cafe.adriel.bonsai.core.node.SimpleLeafNode
8+
import kotlinx.serialization.json.Json
9+
import kotlinx.serialization.json.JsonArray
10+
import kotlinx.serialization.json.JsonElement
11+
import kotlinx.serialization.json.JsonNull
12+
import kotlinx.serialization.json.JsonObject
13+
import kotlinx.serialization.json.JsonPrimitive
14+
import kotlinx.serialization.json.contentOrNull
15+
16+
public fun JsonBonsaiStyle(): BonsaiStyle<JsonElement> =
17+
BonsaiStyle(
18+
nodeNameTextStyle = BonsaiStyle.DefaultNodeTextStyle.copy(
19+
fontFamily = FontFamily.Monospace
20+
)
21+
)
22+
23+
public fun jsonNodes(
24+
json: String
25+
): List<Node<JsonElement>> =
26+
jsonNodes(
27+
key = "",
28+
jsonElement = Json.Default.parseToJsonElement(json),
29+
parent = null
30+
)
31+
32+
private fun jsonNodes(
33+
key: String,
34+
jsonElement: JsonElement,
35+
parent: Node<JsonElement>?
36+
): List<Node<JsonElement>> =
37+
listOf(
38+
when (jsonElement) {
39+
is JsonNull -> JsonPrimitiveNode(key, jsonElement, parent)
40+
is JsonPrimitive -> JsonPrimitiveNode(key, jsonElement, parent)
41+
is JsonObject -> JsonObjectNode(key, jsonElement, parent)
42+
is JsonArray -> JsonArrayNode(key, jsonElement, parent)
43+
}
44+
)
45+
46+
private fun JsonPrimitiveNode(
47+
key: String,
48+
jsonPrimitive: JsonPrimitive,
49+
parent: Node<JsonElement>?
50+
) =
51+
SimpleLeafNode(
52+
content = jsonPrimitive,
53+
name = "${getFormattedKey(key)}${getFormattedValue(jsonPrimitive)}",
54+
parent = parent
55+
)
56+
57+
private fun JsonObjectNode(
58+
key: String,
59+
jsonObject: JsonObject,
60+
parent: Node<JsonElement>?
61+
) =
62+
SimpleBranchNode(
63+
content = jsonObject,
64+
name = "${getFormattedKey(key)}{object}",
65+
parent = parent,
66+
children = { node ->
67+
jsonObject.entries.flatMap { (name, jsonElement) ->
68+
jsonNodes(name, jsonElement, node)
69+
}
70+
}
71+
)
72+
73+
private fun JsonArrayNode(
74+
key: String,
75+
jsonArray: JsonArray,
76+
parent: Node<JsonElement>?
77+
) =
78+
SimpleBranchNode(
79+
content = jsonArray,
80+
name = "${getFormattedKey(key)}[array]",
81+
parent = parent,
82+
children = { node ->
83+
jsonArray.flatMapIndexed { index, jsonElement ->
84+
jsonNodes(index.toString(), jsonElement, node)
85+
}
86+
}
87+
)
88+
89+
private fun getFormattedKey(key: String) =
90+
if (key.isBlank()) ""
91+
else "$key: "
92+
93+
private fun getFormattedValue(jsonPrimitive: JsonPrimitive) =
94+
if (jsonPrimitive.isString) "\"${jsonPrimitive.contentOrNull}\""
95+
else jsonPrimitive.contentOrNull

0 commit comments

Comments
 (0)