This repository has been archived by the owner on Jun 3, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* incomplete * important integration tests are passing * replaces class UINode with boolean polymorphic * remove dependent * Implemented lazy initialization for the tree * forEach working * tests are passing * docs * detekt + new test * small change to events * fixes tests
- Loading branch information
1 parent
ad15fac
commit 08c71d3
Showing
147 changed files
with
3,630 additions
and
3,808 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.zup.nimbus.core | ||
|
||
import com.zup.nimbus.core.dependency.CommonDependency | ||
import com.zup.nimbus.core.tree.ServerDrivenAction | ||
import com.zup.nimbus.core.tree.ServerDrivenEvent | ||
|
||
interface ActionEvent { | ||
/** | ||
* The action of this event. Use this object to find the name of the action, its properties and metadata. | ||
*/ | ||
val action: ServerDrivenAction | ||
/** | ||
* The scope of the event that triggered this ActionEvent. | ||
*/ | ||
val scope: ServerDrivenEvent | ||
} | ||
|
||
/** | ||
* All information needed for an action to execute. Represents the trigger event of an action. | ||
*/ | ||
class ActionTriggeredEvent( | ||
override val action: ServerDrivenAction, | ||
override val scope: ServerDrivenEvent, | ||
/** | ||
* Every event can update the current state of the application based on the dependency graph. This set starts empty | ||
* when a ServerDrivenEvent is run. A ServerDrivenEvent is what triggers ActionEvents. Use this to tell the | ||
* ServerDrivenEvent (parent) which dependencies might need to propagate its changes to its dependents after it | ||
* finishes running. "Might" because it will still check if the dependency has really changed since the last time its | ||
* dependents were updated. | ||
*/ | ||
val dependencies: MutableSet<CommonDependency>, | ||
): ActionEvent | ||
|
||
/** | ||
* All information needed for an action to initialize. Represents the initialization event of an action. | ||
*/ | ||
class ActionInitializedEvent( | ||
override val action: ServerDrivenAction, | ||
override val scope: ServerDrivenEvent, | ||
): ActionEvent |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,98 +1,62 @@ | ||
package com.zup.nimbus.core | ||
|
||
import com.zup.nimbus.core.action.getCoreActions | ||
import com.zup.nimbus.core.action.getRenderHandlersForCoreActions | ||
import com.zup.nimbus.core.component.getCoreComponents | ||
import com.zup.nimbus.core.expression.parser.ExpressionParser | ||
import com.zup.nimbus.core.ui.UILibraryManager | ||
import com.zup.nimbus.core.log.DefaultLogger | ||
import com.zup.nimbus.core.network.DefaultHttpClient | ||
import com.zup.nimbus.core.network.DefaultUrlBuilder | ||
import com.zup.nimbus.core.network.DefaultViewClient | ||
import com.zup.nimbus.core.operations.getDefaultOperations | ||
import com.zup.nimbus.core.render.ServerDrivenView | ||
import com.zup.nimbus.core.scope.CommonScope | ||
import com.zup.nimbus.core.tree.DefaultIdManager | ||
import com.zup.nimbus.core.tree.MalformedComponentError | ||
import com.zup.nimbus.core.tree.MalformedJsonError | ||
import com.zup.nimbus.core.tree.ObservableState | ||
import com.zup.nimbus.core.tree.RenderNode | ||
|
||
class Nimbus(config: ServerDrivenConfig) { | ||
// From config | ||
val baseUrl = config.baseUrl | ||
private val platform = config.platform | ||
val actions = (getCoreActions() + (config.actions ?: emptyMap())).toMutableMap() | ||
val actionObservers = config.actionObservers?.toMutableList() ?: ArrayList() | ||
val operations = (getDefaultOperations() + (config.operations?.toMutableMap() ?: emptyMap())).toMutableMap() | ||
import com.zup.nimbus.core.tree.dynamic.builder.EventBuilder | ||
import com.zup.nimbus.core.tree.dynamic.builder.NodeBuilder | ||
import com.zup.nimbus.core.ui.coreUILibrary | ||
|
||
/** | ||
* The root scope of a nimbus application. Contains important objects like the logger and the httpClient. | ||
*/ | ||
class Nimbus(config: ServerDrivenConfig): CommonScope( | ||
parent = null, | ||
states = (config.states ?: emptyList()) + ServerDrivenState("global", null), | ||
) { | ||
/** | ||
* Manages UI elements like actions, components and operations. | ||
*/ | ||
val uiLibraryManager = UILibraryManager(config.coreUILibrary ?: coreUILibrary, config.ui) | ||
/** | ||
* Logger of this instance of Nimbus. | ||
*/ | ||
val logger = config.logger ?: DefaultLogger() | ||
val urlBuilder = config.urlBuilder ?: DefaultUrlBuilder(baseUrl) | ||
/** | ||
* Responsible for making every network interaction within this nimbus instance. | ||
*/ | ||
val httpClient = config.httpClient ?: DefaultHttpClient() | ||
/** | ||
* Responsible for retrieving Server Driven Screens from the backend. Uses the httpClient. | ||
*/ | ||
val viewClient = config.viewClient?.let { it(this) } ?: DefaultViewClient(this) | ||
/** | ||
* Logic for building urls. Uses the baseUrl. | ||
*/ | ||
val urlBuilder = config.urlBuilder?.let { it(config.baseUrl) } ?: DefaultUrlBuilder(config.baseUrl) | ||
/** | ||
* Logic for generating unique ids for components when one hasn't been defined in the json. | ||
*/ | ||
val idManager = config.idManager ?: DefaultIdManager() | ||
val viewClient = config.viewClient ?: DefaultViewClient(httpClient, urlBuilder, idManager, logger, platform) | ||
|
||
/** | ||
* Core components. These don't correspond to real UI components and never reach the UI layer. These components are | ||
* used to manipulate the structure of the UI tree and exists only in the core lib. | ||
* | ||
* The structural components are replaced by their result before being rendered by the UI layer. | ||
* | ||
* Examples: if (companions: then, else); switch (companions: case, default); foreach. | ||
* The platform currently using the Nimbus. | ||
*/ | ||
internal val structuralComponents = getCoreComponents() | ||
|
||
// Other | ||
val globalState = ObservableState("global", null) | ||
|
||
val platform = config.platform | ||
/** | ||
* Functions to run once an action goes through the rendering process for the first time. | ||
* This is currently used only for performing pre-fetches in navigation actions. | ||
* A tool for parsing strings using the Nimbus Expression Language | ||
*/ | ||
internal val onActionRendered: Map<String, ActionHandler> = getRenderHandlersForCoreActions() | ||
|
||
val expressionParser = ExpressionParser(this) | ||
/** | ||
* Creates a new ServerDrivenView that uses this Nimbus instance as its dependency manager. | ||
* | ||
* Check the documentation for ServerDrivenView for more details on the parameters. | ||
* | ||
* @param getNavigator a function that returns the ServerDrivenView's navigator. | ||
* @param description a description for the new ServerDrivenView. | ||
* @return the new ServerDrivenView. | ||
* Builds a node tree from a json string or map. | ||
*/ | ||
fun createView(getNavigator: () -> ServerDrivenNavigator, description: String? = null): ServerDrivenView { | ||
return ServerDrivenView(this, getNavigator, description) | ||
} | ||
|
||
val nodeBuilder = NodeBuilder(this) | ||
/** | ||
* Creates a RenderNode from a JSON string using the idManager provided in the config (or the default idManager if | ||
* none has been provided. | ||
* | ||
* @param json the json string to deserialize into a RenderNode. | ||
* @return the resulting RenderNode. | ||
* @throws MalformedJsonError if the string is not a valid json. | ||
* @throws MalformedComponentError if a component in the JSON is malformed. | ||
* Builds an event from a json array. | ||
*/ | ||
@Throws(MalformedJsonError::class, MalformedComponentError::class) | ||
fun createNodeFromJson(json: String): RenderNode { | ||
return RenderNode.fromJsonString(json, idManager) | ||
} | ||
|
||
private fun <T>addAll(target: MutableMap<String, T>, source: Map<String, T>, entity: String) { | ||
source.forEach { | ||
if (target.containsKey(it.key)) { | ||
logger.warn("$entity of name \"${it.key}\" already exists and is going to be replaced. Maybe you should " + | ||
"consider another name.") | ||
} | ||
target[it.key] = it.value | ||
} | ||
} | ||
|
||
fun addActions(newActions: Map<String, ActionHandler>) { | ||
addAll(actions, newActions, "Action") | ||
} | ||
|
||
fun addActionObservers(observers: List<ActionHandler>) { | ||
actionObservers.addAll(observers) | ||
} | ||
|
||
fun addOperations(newOperations: Map<String, OperationHandler>) { | ||
addAll(operations, newOperations, "Operation") | ||
} | ||
val eventBuilder = EventBuilder(this) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
src/commonMain/kotlin/com/zup/nimbus/core/ServerDrivenState.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package com.zup.nimbus.core | ||
|
||
import com.zup.nimbus.core.dependency.CommonDependency | ||
import com.zup.nimbus.core.dependency.DependencyUpdateManager | ||
import com.zup.nimbus.core.utils.deepCopyMutable | ||
import com.zup.nimbus.core.utils.setMapValue | ||
import com.zup.nimbus.core.utils.valueOfPath | ||
|
||
/** | ||
* Represents a state in the scope hierarchy. | ||
* | ||
* Considering the Json tree, a state is represented by the key "state" in a component. | ||
*/ | ||
class ServerDrivenState( | ||
/** | ||
* The id of the state. | ||
*/ | ||
val id: String, | ||
/** | ||
* The value of the state. | ||
* If you need set this value and have this change propagated, consider using "set" instead. | ||
*/ | ||
internal var value: Any?, | ||
): CommonDependency() { | ||
/** | ||
* Gets the current value of this state. Do not use this value as settable. | ||
* | ||
* @see set, to set the new value of this state use the `set` function. | ||
* @return the current value of this state. | ||
*/ | ||
fun get(): Any? { | ||
return value | ||
} | ||
|
||
/** | ||
* If the value is a list or map, but is not mutable, returns a mutable version of it. | ||
* Otherwise, returns the input. | ||
*/ | ||
private fun getMutable(maybeImmutable: Any?): Any? { | ||
if (maybeImmutable is Map<*, *> && maybeImmutable !is MutableMap<*, *>) return maybeImmutable.toMutableMap() | ||
if (maybeImmutable is List<*> && maybeImmutable !is MutableList<*>) return maybeImmutable.toMutableList() | ||
return maybeImmutable | ||
} | ||
|
||
private fun setValueAtPath(newValue: Any?, path: String) { | ||
if (path.isEmpty()) { | ||
value = newValue | ||
} else { | ||
if (value !is MutableMap<*, *>) value = HashMap<String, Any>() | ||
@Suppress("UNCHECKED_CAST") | ||
setMapValue(value as MutableMap<String, Any?>, path, newValue) | ||
} | ||
} | ||
|
||
/** | ||
* Changes the value of this state at the provided path. | ||
* | ||
* @param newValue the new value of "state.value.$path". Must be encodable, i.e. null, string, number, boolean, | ||
* Map<string, encodable> or List<encodable>. | ||
* @param path the path within the state to modify. Example: "" to alter the entire state. "foo.bar" to alter the | ||
* property "bar" of "foo" in the map "state.value". If "path" is not empty and "state.value" is not a mutable map, it | ||
* is converted to one. The path must only contain letters, numbers and underscores separated by dots. | ||
* @param shouldUpdateDependents whether or not to propagate this change to everything that depends on the value of | ||
* this state. This is useful for making multiple changes to states and still have a single UI update. In this case, | ||
* you would pass false to every `set`, but the last one. By default, it will update its dependents. | ||
*/ | ||
fun set(newValue: Any?, path: String, shouldUpdateDependents: Boolean = true) { | ||
val currentValue: Any? = valueOfPath(value, path) | ||
if (currentValue != newValue) { | ||
val mutableValue = getMutable(newValue) | ||
setValueAtPath(mutableValue, path) | ||
hasChanged = true | ||
if (shouldUpdateDependents) DependencyUpdateManager.updateDependentsOf(this) | ||
} | ||
} | ||
|
||
/** | ||
* Shortcut to `set(newValue, "")`, i.e. replaces the entire value of the state with `newValue`. | ||
*/ | ||
fun set(newValue: Any?) { | ||
set(newValue, "") | ||
} | ||
|
||
fun clone(): ServerDrivenState { | ||
return ServerDrivenState(id, deepCopyMutable(value)) | ||
} | ||
} |
Oops, something went wrong.