{
65 | text(
66 | text = "Count: ${it.state.value}",
67 | style = TextStyle(color = Color.Hex("#FF0000"))
68 | )
69 | }
70 |
--------------------------------------------------------------------------------
/componentbox/src/commonMain/kotlin/com/dropbox/componentbox/SpanStyle.kt:
--------------------------------------------------------------------------------
1 | package com.dropbox.componentbox
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Represents a style to be applied to a span of text.
7 | * @property color The text color to be applied to the span, or null if not specified.
8 | * @property backgroundColor The background color to be applied to the span, or null if not specified.
9 | * @property fontStyle The font style to be applied to the span, or null if not specified.
10 | * @property fontWeight The font weight to be applied to the span, or null if not specified.
11 | * @property fontSize The font size to be applied to the span, or null if not specified.
12 | * @property letterSpacing The letter spacing to be applied to the span, or null if not specified.
13 | * @property textDecoration The text decoration to be applied to the span, or null if not specified.
14 | */
15 | @Serializable
16 | data class SpanStyle(
17 | val color: Color? = null,
18 | val backgroundColor: Color? = null,
19 | val fontStyle: FontStyle? = null,
20 | val fontWeight: FontWeight? = null,
21 | val fontSize: TextUnit? = null,
22 | val letterSpacing: TextUnit? = null,
23 | val textDecoration: TextDecoration? = null
24 | ) {
25 | companion object {
26 | fun from(style: SpanStyle? = null): SpanStyleBuilder = SpanStyleBuilder(
27 | style
28 | ?: SpanStyle()
29 | )
30 | }
31 |
32 | @Serializable
33 | class SpanStyleBuilder(private var style: SpanStyle) {
34 | fun color(color: Color) {
35 | style = style.copy(color = color)
36 | }
37 |
38 | fun backgroundColor(color: Color) {
39 | style = style.copy(backgroundColor = color)
40 | }
41 |
42 | fun fontStyle(fontStyle: FontStyle) {
43 | style = style.copy(fontStyle = fontStyle)
44 | }
45 |
46 | fun fontWeight(fontWeight: FontWeight) {
47 | style = style.copy(fontWeight = fontWeight)
48 | }
49 |
50 | fun fontSize(fontSize: TextUnit) {
51 | style = style.copy(fontSize = fontSize)
52 | }
53 |
54 | fun letterSpacing(letterSpacing: TextUnit) {
55 | style = style.copy(letterSpacing = letterSpacing)
56 | }
57 |
58 | fun textDecoration(textDecoration: TextDecoration) {
59 | style = style.copy(textDecoration = textDecoration)
60 | }
61 |
62 | fun build() = style
63 | }
64 | }
--------------------------------------------------------------------------------
/componentbox/src/commonMain/kotlin/com/dropbox/componentbox/AnnotatedStringElement.kt:
--------------------------------------------------------------------------------
1 | package com.dropbox.componentbox
2 |
3 | import androidx.compose.runtime.Composable
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * Represents an element of an annotated string, which can be either a Text or a Span.
8 | */
9 | @Serializable
10 | sealed interface AnnotatedStringElement {
11 | /**
12 | * Represents a piece of text with optional style information and a soft break flag.
13 | * @property text The text content.
14 | * @property style The style of the text, or null if not specified.
15 | * @property softBreak Whether the text should be followed by a soft break.
16 | */
17 | @Serializable
18 | data class Text(
19 | val text: String,
20 | val style: TextStyle? = null,
21 | val softBreak: Boolean = false
22 | ) : AnnotatedStringElement
23 |
24 |
25 | /**
26 | * Represents a span of text with a style applied to it.
27 | * @property start The start index of the span in the annotated string.
28 | * @property end The end index of the span in the annotated string.
29 | * @property style The style applied to the span.
30 | */
31 | @Serializable
32 | data class Span(
33 | val start: Int,
34 | val end: Int,
35 | val style: SpanStyle
36 | ) : AnnotatedStringElement
37 |
38 | /**
39 | * Represents the inline content added to an [AnnotatedString].
40 | * Used to store the content ID and associated [InlineTextContent].
41 | */
42 |
43 | data class InlineContent(
44 | val id: String,
45 | val content: InlineTextContent
46 | ) : AnnotatedStringElement
47 | }
48 |
49 | /**
50 | * Used to define the inline content that will be displayed within a [Text] component.
51 | * @param placeholder A [Placeholder] object that defines the size and vertical alignment of the space reserved for the inline content.
52 | * @param content A composable function that represents the inline content to be displayed within the reserved space.
53 | */
54 |
55 | data class InlineTextContent(
56 | val placeholder: Placeholder,
57 | val content: @Composable () -> Unit
58 | )
59 |
60 |
61 | /**
62 | * Used to reserve space for inline content within a [Text] component.
63 | * @param width The width of the placeholder.
64 | * @param height The height of the placeholder.
65 | * @param verticalAlign A [PlaceholderVerticalAlign] enum value that specifies how the placeholder should be vertically aligned with respect to the surrounding text.
66 | */
67 | @Serializable
68 | data class Placeholder(
69 | val width: TextUnit.Sp,
70 | val height: TextUnit.Sp,
71 | val verticalAlign: PlaceholderVerticalAlign
72 | )
73 |
74 | /**
75 | * Used to define the vertical alignment of a [Placeholder] with respect to the surrounding text.
76 | */
77 | enum class PlaceholderVerticalAlign {
78 | TextTop,
79 | TextCenter,
80 | TextBottom,
81 | AboveBaseline,
82 | BelowBaseline
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 |
4 |
5 | Dropbox Open Source Code of Conduct
6 | Dropbox believes that an inclusive development environment fosters greater technical achievement. To encourage a diverse group of contributors we've adopted this code of conduct.
7 |
8 |
9 |
10 | ## The Code
11 | The list of ground rules are by no means exhaustive, instead they should be used as a guide when participating in or contributing to an open source project maintained by Dropbox.
12 |
13 | - **Be friendly and patient.**
14 | - **Be welcoming.** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
15 | - **Be considerate.** Your work will be used by other people, and you in turn will depend on the work of others. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language.
16 | - **Be respectful.** We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It's important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the Dropbox open source community should be respectful when dealing with one another.
17 | - **Be careful in the words that you choose.** We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to:
18 | - Violent threats or language directed against another person.
19 | - Discriminatory jokes and language.
20 | - Posting sexually explicit or violent material.
21 | - Posting (or threatening to post) other people's personally identifying information ("doxing").
22 | - Personal insults, especially those using racist or sexist terms.
23 | - Unwelcome sexual attention.
24 | - Advocating for, or encouraging, any of the above behavior.
25 | - Repeated harassment of others. In general, if someone asks you to stop, then stop.
26 |
27 | ## Reporting Issues
28 | If you believe someone is violating the code of conduct, we ask that you [report it to us](https://goo.gl/forms/sCD2o33YQW4NInhr2). All reports will be handled with discretion. If you believe anyone is in physical danger, please notify appropriate law enforcement first. After evaluating the report we will make a decision of how to respond, which can include asking (or requiring) anyone who violates this policy to leave the Dropbox open source community.
29 |
30 | ## Attribution
31 | This code of conduct is based on the [Open Code of Conduct](http://todogroup.org/opencodeofconduct/) and the [Django Code of Conduct](https://www.djangoproject.com/conduct/).
32 |
--------------------------------------------------------------------------------
/componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Modifier.kt:
--------------------------------------------------------------------------------
1 | package com.dropbox.componentbox
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Represents a modifier to be applied to a UI element.
7 | * @property background The background color to be applied to the UI element.
8 | * @property border The border to be applied to the UI element.
9 | * @property padding The padding values to be applied to the UI element.
10 | * @property size The size of the UI element.
11 | * @property shape The shape of the UI element.
12 | * @property alpha The alpha value to be applied to the UI element.
13 | * @property rotation The rotation angle to be applied to the UI element.
14 | * @property scaleX The horizontal scale factor to be applied to the UI element.
15 | * @property scaleY The vertical scale factor to be applied to the UI element.
16 | * @property offset The offset values to be applied to the UI element.
17 | * @property zIndex The z-index value to be applied to the UI element.
18 | * @property clip Whether clipping should be applied to the UI element.
19 | */
20 | @Serializable
21 | data class Modifier(
22 | val background: Color? = null,
23 | val border: Border? = null,
24 | val padding: PaddingValues? = null,
25 | val size: Size? = null,
26 | val shape: Shape? = null,
27 | val alpha: Float = 1f,
28 | val rotation: Float = 0f,
29 | val scaleX: Float = 1f,
30 | val scaleY: Float = 1f,
31 | val offset: Offset? = null,
32 | val zIndex: Float = 0f,
33 | val clip: Boolean = true
34 | )
35 |
36 | /**
37 | * Represents a border to be applied to a UI element.
38 | * @property width The width of the border.
39 | * @property color The color of the border.
40 | * @property shape The shape of the border.
41 | */
42 | @Serializable
43 | data class Border(
44 | val width: Dp,
45 | val color: Color,
46 | val shape: Shape
47 | )
48 |
49 |
50 | /**
51 | * Represents padding values to be applied to a UI element.
52 | * @property start The start padding value.
53 | * @property top The top padding value.
54 | * @property end The end padding value.
55 | * @property bottom The bottom padding value.
56 | */
57 | @Serializable
58 | data class PaddingValues(
59 | val start: Dp,
60 | val top: Dp,
61 | val end: Dp,
62 | val bottom: Dp
63 | )
64 |
65 |
66 | /**
67 | * Represents the size of a UI element.
68 | * @property width The width of the UI element.
69 | * @property height The height of the UI element.
70 | */
71 | @Serializable
72 | data class Size(
73 | val width: Dp,
74 | val height: Dp
75 | )
76 |
77 | /**
78 | * Represents a dimension value in density-independent pixels.
79 | * @property value The value of the dimension in density-independent pixels.
80 | */
81 | @Serializable
82 | data class Dp(val value: Float) {
83 | operator fun plus(other: Dp): Dp = Dp(value + other.value)
84 | operator fun minus(other: Dp): Dp = Dp(value - other.value)
85 | operator fun times(scalar: Float): Dp = Dp(value * scalar)
86 | operator fun div(scalar: Float): Dp = Dp(value / scalar)
87 | }
88 |
89 | val Float.dp: Dp
90 | get() = Dp(this)
91 |
92 | val Int.dp: Dp
93 | get() = Dp(this.toFloat())
94 |
95 |
96 | /**
97 | * Represents the offset of a UI element in density-independent pixels.
98 | * @property x The horizontal offset value.
99 | * @property y The vertical offset value.
100 | */
101 | @Serializable
102 | data class Offset(
103 | val x: Dp = 0.dp,
104 | val y: Dp = 0.dp
105 | )
--------------------------------------------------------------------------------
/componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Button.kt:
--------------------------------------------------------------------------------
1 | package com.dropbox.componentbox
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * The base class for a button.
8 | * @property modifier The modifier to be applied to the button.
9 | * @property enabled Whether the button should be enabled or disabled.
10 | */
11 | @Serializable
12 | sealed class Button : Component {
13 |
14 | abstract val modifier: Modifier
15 | abstract val enabled: Boolean
16 | abstract val onClick: Action?
17 |
18 | /**
19 | * A button with a contained style.
20 | *
21 | * @property backgroundColor The background color of the button.
22 | * @property contentColor The content color of the button.
23 | * @property elevation The elevation of the button.
24 | * @property shape The shape of the button.
25 | */
26 | @Serializable
27 | sealed class Contained : Button() {
28 | abstract val backgroundColor: Color?
29 | abstract val contentColor: Color?
30 | abstract val elevation: Dp?
31 | abstract val shape: Shape?
32 | abstract val children: MutableList
33 |
34 | /**
35 | * A button with a contained style and executable event handler.
36 | * @property onClick The callback function to be called when the button is clicked.
37 | */
38 | data class Dynamic(
39 | override val modifier: Modifier = Modifier(),
40 | override val enabled: Boolean = true,
41 | override val onClick: Action.Lambda? = null,
42 | override val backgroundColor: Color? = null,
43 | override val contentColor: Color? = null,
44 | override val elevation: Dp? = null,
45 | override val shape: Shape? = null,
46 | override val children: MutableList = mutableListOf()
47 | ) : Contained() {
48 | fun child(component: Component) {
49 | children.add(component)
50 | }
51 | }
52 |
53 | /**
54 | * A button with a contained style and semantically identifiable event handler.
55 | * @property onClick The action to be performed when the button is clicked.
56 | */
57 | @Serializable
58 | data class Static(
59 | override val modifier: Modifier = Modifier(),
60 | override val enabled: Boolean = true,
61 | override val onClick: Action.Semantic? = null,
62 | override val backgroundColor: Color? = null,
63 | override val contentColor: Color? = null,
64 | override val elevation: Dp? = null,
65 | override val shape: Shape? = null,
66 | override val children: MutableList = mutableListOf()
67 | ) : Contained() {
68 | fun child(component: Component) {
69 | children.add(component)
70 | }
71 | }
72 | }
73 |
74 |
75 | /**
76 | * A button with a text style.
77 | *
78 | * @property contentColor: Color
79 | */
80 | @Serializable
81 | sealed class Text : Button() {
82 | abstract val text: String
83 | abstract val contentColor: Color?
84 |
85 | data class Dynamic(
86 | override val modifier: Modifier = Modifier(),
87 | override val text: String,
88 | override val contentColor: Color? = null,
89 | override val enabled: Boolean = true,
90 | override val onClick: Action.Lambda? = null
91 | ) : Text()
92 |
93 | @Serializable
94 | data class Static(
95 | override val modifier: Modifier = Modifier(),
96 | override val text: String,
97 | override val contentColor: Color? = null,
98 | override val enabled: Boolean = true,
99 | override val onClick: Action.Semantic? = null
100 | ) : Text()
101 | }
102 | }
--------------------------------------------------------------------------------
/componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Component.kt:
--------------------------------------------------------------------------------
1 | package com.dropbox.componentbox
2 |
3 | import androidx.compose.runtime.Composable
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | sealed interface Component
8 |
9 | fun annotatedString(
10 | elements: MutableList = mutableListOf()
11 | ): Component = AnnotatedString(elements)
12 |
13 | fun box(
14 | modifier: Modifier = Modifier(),
15 | events: Events.Semantic? = null,
16 | children: Box.() -> Unit
17 | ): Component = Box.Static(modifier, events)
18 |
19 | @Composable
20 | fun Box(
21 | modifier: Modifier = Modifier(),
22 | events: Events.Lambda? = null,
23 | children: @Composable Box.() -> Unit
24 | ): Component = Box.Dynamic(modifier, events)
25 |
26 | fun column(
27 | modifier: Modifier = Modifier(),
28 | events: Events.Semantic? = null,
29 | verticalArrangement: Arrangement.Vertical? = null,
30 | horizontalAlignment: Alignment.Horizontal? = null,
31 | children: Column.Static.() -> Unit
32 | ): Component = Column.Static(modifier, events, verticalArrangement, horizontalAlignment)
33 |
34 | @Composable
35 | fun Column(
36 | modifier: Modifier = Modifier(),
37 | events: Events.Lambda? = null,
38 | verticalArrangement: Arrangement.Vertical? = null,
39 | horizontalAlignment: Alignment.Horizontal? = null,
40 | children: @Composable Column.Dynamic.() -> Unit
41 | ): Component = Column.Dynamic(modifier, events, verticalArrangement, horizontalAlignment)
42 |
43 |
44 | fun containedButton(
45 | modifier: Modifier = Modifier(),
46 | enabled: Boolean = true,
47 | onClick: Action.Semantic? = null,
48 | backgroundColor: Color? = null,
49 | contentColor: Color? = null,
50 | elevation: Dp? = null,
51 | shape: Shape,
52 | children: Button.Contained.Static.() -> Unit
53 | ): Component = Button.Contained.Static(
54 | modifier,
55 | enabled,
56 | onClick,
57 | backgroundColor,
58 | contentColor,
59 | elevation,
60 | shape
61 | )
62 |
63 |
64 | @Composable
65 | fun ContainedButton(
66 | modifier: Modifier = Modifier(),
67 | enabled: Boolean = true,
68 | onClick: Action.Lambda? = null,
69 | backgroundColor: Color? = null,
70 | contentColor: Color? = null,
71 | elevation: Dp? = null,
72 | shape: Shape? = null,
73 | children: @Composable Button.Contained.Dynamic.() -> Unit
74 | ): Component = Button.Contained.Dynamic(
75 | modifier,
76 | enabled,
77 | onClick,
78 | backgroundColor,
79 | contentColor,
80 | elevation,
81 | shape
82 | )
83 |
84 | @Composable
85 | fun LazyColumn(
86 | modifier: Modifier = Modifier(),
87 | events: Events.Lambda? = null,
88 | verticalArrangement: Arrangement.Vertical? = null,
89 | horizontalAlignment: Alignment.Horizontal? = null,
90 | contentPaddingValues: PaddingValues? = null,
91 | children: @Composable LazyColumn.Dynamic.() -> Unit
92 | ): Component = LazyColumn.Dynamic(
93 | modifier,
94 | events,
95 | verticalArrangement,
96 | horizontalAlignment,
97 | contentPaddingValues
98 | )
99 |
100 |
101 | fun lazyColumn(
102 | modifier: Modifier = Modifier(),
103 | events: Events.Semantic? = null,
104 | verticalArrangement: Arrangement.Vertical? = null,
105 | horizontalAlignment: Alignment.Horizontal? = null,
106 | contentPaddingValues: PaddingValues? = null,
107 | children: LazyColumn.Static.() -> Unit
108 | ): Component = LazyColumn.Static(
109 | modifier,
110 | events,
111 | verticalArrangement,
112 | horizontalAlignment,
113 | contentPaddingValues
114 | )
115 |
116 | fun text(
117 | text: String,
118 | color: Color? = null,
119 | style: TextStyle? = null
120 | ): Component = Text(text, color, style)
121 |
122 |
123 | fun textButton(
124 | modifier: Modifier = Modifier(),
125 | text: String,
126 | contentColor: Color? = null,
127 | enabled: Boolean = false,
128 | onClick: Action.Semantic? = null,
129 | ): Component = Button.Text.Static(modifier, text, contentColor, enabled, onClick)
130 |
131 | @Composable
132 | fun TextButton(
133 | modifier: Modifier = Modifier(),
134 | text: String,
135 | contentColor: Color? = null,
136 | enabled: Boolean = false,
137 | onClick: Action.Lambda? = null,
138 | ): Component = Button.Text.Dynamic(modifier, text, contentColor, enabled, onClick)
139 |
--------------------------------------------------------------------------------
/componentbox-gradle-plugin/src/main/kotlin/com/dropbox/componentbox/plugin/ComponentBoxPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.dropbox.componentbox.plugin
2 |
3 | import app.cash.zipline.gradle.ZiplineCompileTask
4 | import app.cash.zipline.gradle.ZiplinePlugin
5 | import com.dropbox.componentbox.SerializableComponentBox
6 | import com.dropbox.componentbox.Tree
7 | import kotlinx.serialization.encodeToString
8 | import kotlinx.serialization.json.Json
9 | import org.gradle.api.Plugin
10 | import org.gradle.api.Project
11 | import org.gradle.internal.impldep.org.objectweb.asm.Opcodes
12 | import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
13 | import org.objectweb.asm.AnnotationVisitor
14 | import java.io.FileInputStream
15 | import java.net.URL
16 | import java.net.URLClassLoader
17 |
18 | class ComponentBoxPlugin : Plugin {
19 | private val ziplinePlugin = ZiplinePlugin()
20 |
21 | override fun apply(target: Project) {
22 | target.pluginManager.apply("com.dropbox.componentbox.plugin")
23 | ziplinePlugin.apply(target)
24 | createZiplineExecutionTask(target)
25 | createComponentBoxJsonTask(target)
26 | }
27 |
28 | private fun createZiplineExecutionTask(target: Project) {
29 | val ziplineExecutionTask = target.tasks.register("componentBoxZipline") {
30 | it.group = "Component Box"
31 | it.description = "Execute all Zipline tasks"
32 | }
33 |
34 | target.tasks.withType(ZiplineCompileTask::class.java) { ziplineCompileTask ->
35 | ziplineExecutionTask.configure {
36 | it.dependsOn(ziplineCompileTask)
37 | }
38 | }
39 | }
40 |
41 | private fun createComponentBoxJsonTask(project: Project) {
42 | val compileTaskName = "compileKotlinMetadata"
43 | val compileTask = project.tasks.named(compileTaskName, KotlinCompile::class.java)
44 |
45 | val json = Json {
46 | prettyPrint = true
47 | }
48 |
49 | project.tasks.register("componentBoxJson") {
50 |
51 | it.dependsOn(compileTask)
52 | it.group = "Component Box"
53 | it.description =
54 | "Generate JSON from Component Box exports"
55 |
56 | it.doLast {
57 |
58 | val jvmMainOutputDir = project.buildDir.resolve("classes/kotlin/jvm/main")
59 |
60 | val files =
61 | jvmMainOutputDir.walk().flatMap { file -> listOf(file, file.parentFile) }
62 | .filter { file -> file.isFile && file.name.endsWith(".class") }
63 | .toList()
64 |
65 |
66 | val urls = files.filter {
67 | it.isFile && it.name.endsWith(".class")
68 | }.map {
69 | it.toURI().toURL()
70 | }
71 | .toTypedArray()
72 |
73 | val classpath = arrayOf(jvmMainOutputDir.toURI().toURL()) + urls
74 | val urlClassLoader = URLClassLoader(classpath, javaClass.classLoader)
75 |
76 | val annotatedClasses = mutableListOf>()
77 |
78 |
79 | urls.forEach { url ->
80 | try {
81 | val classReader = org.objectweb.asm.ClassReader(FileInputStream(url.path))
82 | val myAnnotationChecker = MyAnnotationChecker()
83 |
84 | classReader.accept(myAnnotationChecker, 0)
85 |
86 | val className = classReader.className
87 |
88 | if (myAnnotationChecker.hasAnnotation) {
89 | annotatedClasses.add(Pair(url, className))
90 | }
91 | } catch (error: Throwable) {
92 | println(error)
93 | }
94 | }
95 |
96 | annotatedClasses.forEach { (url, className) ->
97 | val name = className.replace("/", ".")
98 |
99 | try {
100 |
101 | val clazz = urlClassLoader.loadClass(name)
102 | val method = clazz.methods.firstOrNull { method ->
103 | method.isAnnotationPresent(SerializableComponentBox::class.java)
104 | }
105 |
106 | if (method != null) {
107 | val instance = clazz.newInstance()
108 | val result = method.invoke(instance) as? Tree.Static
109 |
110 | if (result != null) {
111 | val serializedResult = json.encodeToString(result)
112 | val path = "/json/${className}/${method.name}.json"
113 | val outputFile =
114 | project.file(project.projectDir.path + "/componentbox" + path)
115 |
116 | if (!outputFile.parentFile.exists()) {
117 | outputFile.parentFile.mkdirs()
118 | }
119 |
120 | if (outputFile.exists()) {
121 | outputFile.writeText(serializedResult)
122 | } else {
123 | outputFile.createNewFile()
124 | outputFile.writeText(serializedResult)
125 | }
126 |
127 | }
128 | }
129 | } catch (error: Throwable) {
130 | println(error)
131 | }
132 | }
133 | }
134 | }
135 | }
136 | }
137 |
138 |
139 | class MyAnnotationChecker : org.objectweb.asm.ClassVisitor(Opcodes.ASM9) {
140 | var hasAnnotation = false
141 | override fun visitAnnotation(
142 | descriptor: String?,
143 | visible: Boolean
144 | ): AnnotationVisitor {
145 |
146 | if (descriptor?.contains("Lcom/dropbox/componentbox/ComponentBoxExport;") == true) {
147 | hasAnnotation = true
148 | }
149 |
150 | return super.visitAnnotation(descriptor, visible) ?: FallbackAnnotationVisitor()
151 | }
152 | }
153 |
154 | class FallbackAnnotationVisitor : AnnotationVisitor(Opcodes.ASM9) {
155 | var hasAnnotation = false
156 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Component Box
4 |
5 | > [!NOTE]
6 | >
7 | > **No longer active!**
8 | >
9 | > After internal experimentation and discussion, we've decided to move forward with other options.
10 |
11 | ### Sample
12 |
13 | #### Model (server)
14 |
15 | ```kotlin
16 | class Counter : ComposableModel(0) {
17 | private fun increment() {
18 | withState {
19 | setState(state.value + 1)
20 | }
21 | }
22 |
23 | private fun decrement() {
24 | withState {
25 | setState(state.value - 1)
26 | }
27 | }
28 |
29 | override fun on(event: CounterEvent) = when (event) {
30 | Increment -> increment()
31 | Decrement -> decrement()
32 | }
33 | }
34 | ```
35 |
36 | #### UI Representation (server)
37 |
38 | ##### Static
39 |
40 | ###### [Tree](componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Tree.kt)
41 |
42 | ```kotlin
43 | @SerializableComponentBox
44 | fun main() = componentBox {
45 | tree {
46 | lazyColumn(
47 | verticalArrangement = Arrangement.SpaceEvenly(2.dp),
48 | horizontalAlignment = Alignment.Start
49 | ) {
50 | child(header)
51 | child(count)
52 | child(incrementButton)
53 | child(decrementButton)
54 | }
55 | }
56 | }
57 |
58 | val header = text(
59 | text = "Component Box Counter",
60 | style = TextStyle(fontWeight = FontWeight.ExtraBold)
61 | )
62 |
63 | val count = text(
64 | text = "Count: \${COUNTER_STATE}",
65 | style = TextStyle(color = Color.Hex("#FF0000"))
66 | )
67 |
68 | val incrementButton = textButton(
69 | text = "+1",
70 | onClick = semantic { Increment }
71 | )
72 |
73 | val decrementButton = textButton(
74 | text = "-1",
75 | onClick = semantic { Decrement }
76 | )
77 | ```
78 |
79 | ##### Dynamic
80 |
81 | ###### [Graph](componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Graph.kt)
82 |
83 | ```kotlin
84 | @Composable
85 | @ComponentBoxExport
86 | fun main() = statefulComponentBoxGraph(init = null) {
87 | Graph(start = CounterOnboardingFlow.value) {
88 | componentBox(CounterLoginScreen.value, LoginScreen())
89 | componentBox(CounterOnboardingFlow.value, OnboardingFlow())
90 | componentBox(CounterScreen.Home.value, HomeScreen())
91 | }
92 | }
93 | ```
94 |
95 | ###### [Forest](componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Forest.kt)
96 |
97 | ```kotlin
98 | @Composable
99 | fun LoginScreen() = Forest {
100 | tree("heading", Tree { LoginHeading() })
101 | tree("button", Tree { LoginButton() })
102 | }
103 | ```
104 |
105 | ###### [Trail](componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Trail.kt)
106 |
107 | ```kotlin
108 | @Composable
109 | fun OnboardingFlow() = Trail {
110 | node(WelcomeScreen())
111 | node(FeatureDiscoveryScreen())
112 | node(HomeScreen())
113 | }
114 | ```
115 |
116 | ###### [Tree](componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Tree.kt)
117 |
118 | ```kotlin
119 | @Composable
120 | fun HomeScreen() = ComponentBox {
121 | Tree {
122 | LazyColumn(
123 | verticalArrangement = Arrangement.SpaceEvenly(2.dp),
124 | horizontalAlignment = Alignment.Start
125 | ) {
126 | child(header)
127 | child(Count())
128 | child(IncrementButton())
129 | child(DecrementButton())
130 | }
131 | }
132 | }
133 |
134 | @Composable
135 | fun IncrementButton() = StatefulComposable {
136 | ContainedButton(onClick = lambda { it.on(Increment) }) {
137 | text(text = "+1")
138 | }
139 | }
140 |
141 | @Composable
142 | fun DecrementButton() = StatefulComposable {
143 | ContainedButton(onClick = lambda { it.on(Decrement) }) {
144 | text(text = "-1")
145 | }
146 | }
147 |
148 | @Composable
149 | fun Count() = StatefulComposable {
150 | text(
151 | text = "Count: ${it.state.value}",
152 | style = TextStyle(color = Color.Hex("#FF0000"))
153 | )
154 | }
155 |
156 |
157 | ```
158 |
159 | #### Binaries (server)
160 |
161 | ```shell
162 | ./gradlew componentBoxJs
163 | ```
164 |
165 | ```shell
166 | ./gradlew componentBoxJson
167 | ```
168 |
169 | #### Jetpack Compose (Android)
170 |
171 | ##### Activity
172 |
173 | ```kotlin
174 | class ComponentBoxActivity : ComponentActivity() {
175 | private val scope = CoroutineScope(Dispatchers.Default)
176 | private val service = ComponentBoxService(scope)
177 | private val componentBox = service.componentBox
178 | private val render = RenderingEngine()
179 |
180 | override fun onStart() {
181 | super.onStart()
182 | service.launch(MANIFEST_URL)
183 | }
184 |
185 | override fun onCreate(savedInstanceState: Bundle?) {
186 | super.onCreate(savedInstanceState)
187 |
188 | setContent {
189 | val root = componentBox.collectAsState()
190 | render {
191 | root.value
192 | }
193 | }
194 | }
195 | }
196 | ```
197 |
198 | ##### Composable
199 |
200 | ```kotlin
201 | @Composable
202 | fun ComponentBoxView(componentBox: StateFlow, render: RenderingEngine) {
203 | val root = componentBox.collectAsState()
204 | render {
205 | root.value
206 | }
207 | }
208 | ```
209 |
210 | #### React (web)
211 |
212 | ```js
213 | export default function ComponentBoxView(props: {manifestUrl: string}) {
214 | const [root, setRoot] = useState(null);
215 | const service = new ComponentBoxService();
216 | const render = new RenderingEngine();
217 |
218 | useEffect(() => {
219 | async function launch(manifestUrl: string): Tree {
220 | const componentBox = await service.launch(manifestUrl)
221 | setRoot(componentBox.root)
222 | }
223 |
224 | launch(props.manifestUrl)
225 |
226 | }, [props.manifestUrl]);
227 |
228 | return render(root)
229 | }
230 | ```
231 |
232 | #### SwiftUI (iOS)
233 |
234 | ```swift
235 | struct ComponentBoxView: View {
236 | @StateObject private var service = ComponentBoxService()
237 | @State private var root: Component?
238 |
239 | var body: some View {
240 | render {
241 | root
242 | }
243 | .onAppear {
244 | service.launch(from: MANIFEST_URL) { result in
245 | switch result {
246 | case .success(let componentBox):
247 | DispatchQueue.main.async {
248 | self.root = componentBox.root
249 | }
250 | }
251 | }
252 | }
253 | }
254 | ```
255 |
256 | ## Snapshots
257 |
258 | Snapshots are available
259 | in [Sonatype's snapshots repository](https://s01.oss.sonatype.org/content/repositories/snapshots/com/dropbox/componentbox/).
260 |
261 | ## License
262 |
263 | ```text
264 | Copyright (c) 2023 Dropbox, Inc.
265 |
266 | Licensed under the Apache License, Version 2.0 (the "License");
267 | you may not use this file except in compliance with the License.
268 | You may obtain a copy of the License at
269 |
270 | http://www.apache.org/licenses/LICENSE-2.0
271 |
272 | Unless required by applicable law or agreed to in writing, software
273 | distributed under the License is distributed on an "AS IS" BASIS,
274 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
275 | See the License for the specific language governing permissions and
276 | limitations under the License.
277 | ```
278 |
279 | ## Acknowledgments
280 | | [
](https://github.com/cashapp) | Thanks to our friends at [Cash App](http://github.com/cashapp) for [Zipline](http://github.com/cashapp/zipline) |
281 | |:-:|---|
282 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
84 |
85 | APP_NAME="Gradle"
86 | APP_BASE_NAME=${0##*/}
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | MAX_FD=$( ulimit -H -n ) ||
147 | warn "Could not query maximum file descriptor limit"
148 | esac
149 | case $MAX_FD in #(
150 | '' | soft) :;; #(
151 | *)
152 | ulimit -n "$MAX_FD" ||
153 | warn "Could not set maximum file descriptor limit to $MAX_FD"
154 | esac
155 | fi
156 |
157 | # Collect all arguments for the java command, stacking in reverse order:
158 | # * args from the command line
159 | # * the main class name
160 | # * -classpath
161 | # * -D...appname settings
162 | # * --module-path (only if needed)
163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
164 |
165 | # For Cygwin or MSYS, switch paths to Windows format before running java
166 | if "$cygwin" || "$msys" ; then
167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
169 |
170 | JAVACMD=$( cygpath --unix "$JAVACMD" )
171 |
172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
173 | for arg do
174 | if
175 | case $arg in #(
176 | -*) false ;; # don't mess with options #(
177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
178 | [ -e "$t" ] ;; #(
179 | *) false ;;
180 | esac
181 | then
182 | arg=$( cygpath --path --ignore --mixed "$arg" )
183 | fi
184 | # Roll the args list around exactly as many times as the number of
185 | # args, so each arg winds up back in the position where it started, but
186 | # possibly modified.
187 | #
188 | # NB: a `for` loop captures its iteration list before it begins, so
189 | # changing the positional parameters here affects neither the number of
190 | # iterations, nor the values presented in `arg`.
191 | shift # remove old arg
192 | set -- "$@" "$arg" # push replacement arg
193 | done
194 | fi
195 |
196 | # Collect all arguments for the java command;
197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
198 | # shell script including quotes and variable substitutions, so put them in
199 | # double quotes to make sure that they get re-expanded; and
200 | # * put everything else in single quotes, so that it's not re-expanded.
201 |
202 | set -- \
203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
204 | -classpath "$CLASSPATH" \
205 | org.gradle.wrapper.GradleWrapperMain \
206 | "$@"
207 |
208 | # Use "xargs" to parse quoted args.
209 | #
210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
211 | #
212 | # In Bash we could simply go:
213 | #
214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
215 | # set -- "${ARGS[@]}" "$@"
216 | #
217 | # but POSIX shell has neither arrays nor command substitution, so instead we
218 | # post-process each arg (as a line of input to sed) to backslash-escape any
219 | # character that might be a shell metacharacter, then use eval to reverse
220 | # that process (while maintaining the separation between arguments), and wrap
221 | # the whole thing up as a single "set" statement.
222 | #
223 | # This will of course break if any of these variables contains a newline or
224 | # an unmatched quote.
225 | #
226 |
227 | eval "set -- $(
228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
229 | xargs -n1 |
230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
231 | tr '\n' ' '
232 | )" '"$@"'
233 |
234 | exec "$JAVACMD" "$@"
235 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
--------------------------------------------------------------------------------