diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/Draft.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/Draft.kt index 6758d6c..8d759d8 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/Draft.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/Draft.kt @@ -4,57 +4,77 @@ import com.lightningkite.reactive.context.ReactiveContext import com.lightningkite.reactive.context.awaitOnce /** - * A mutable reactive value that supports draft editing and publishing. Essentially, this provides an input buffer for a [MutableReactive]. + * A mutable reactive value that supports draft editing and publishing. Essentially, + * this represents an input buffer for a "published" [MutableReactive]. * - * This class wraps a [MutableReactive] value and provides a buffer layer using [MutableRemember]. - * Changes can be made to the draft without affecting the published value until explicitly published. - * - * - The draft value is calculated and updated reactively, but can be manually set. - * - The draft is lazy: if there are no listeners, it will not calculate a value. - * - Listeners are only notified if the draft value actually changes. - * - Use [publish] to commit draft changes to the published value, or [cancel] to discard changes and reset the draft. + * A [Draft] copies its [published] value until you set a new value in the draft. After + * you set a new value, the draft keeps this change separate from the published value. + * The draft will keep your change until you call [publish] (to save it) or [cancel] + * (to discard it and revert to the published value). * * Example: * ```kotlin * val published = Signal(0) * val draft = Draft(published) * - * draft.set(42) // changes the draft, not the published value + * draft.value = 42 // changes the draft, not the published value * draft.publish() // commits the draft to published. published now holds the value '42' * - * draft.set(43) // draft now holds '43', while published holds '42' + * draft.value = 43 // draft now holds '43', while published holds '42' * draft.cancel() // discards changes and resets the draft. draft now reads '42' again. * ``` - * - * @param T The type of value being edited and published. - * @property published The underlying published value. - * @property draft The mutable draft value. - * @property changesMade A reactive value indicating if the draft differs from the published value. - * - * - * @see MutableRemember */ -class Draft private constructor( - val published: MutableReactive, - private val draft: MutableRemember -): ReactiveWithMutableValue by draft { +interface Draft : ReactiveWithMutableValue { + /** + * The current saved value that this [Draft] is buffering. + * + * NOTE: Manually setting values for [published] will not by-default update values in the draft buffer. + * */ + val published: MutableReactive + + /** + * Saves all changes made to this [Draft] to the published [MutableReactive] + * */ + suspend fun publish(): T + + /** + * Discards all changes made to this [Draft] and reverts back to the [published] state + * */ + fun cancel() + + /** + * Reads `true` if there are any differences between the [published] value and the value stored in the draft buffer. + * */ + val changesMade: Reactive +} + +private class BaseDraft private constructor( + override val published: MutableReactive, + val buffer: MutableRemember +): Draft, ReactiveWithMutableValue by buffer { constructor(published: MutableReactive) : this(published, MutableRemember(stopListeningWhenOverridden = false) { published() }) - constructor(initialValue: ReactiveContext.() -> T) : this( - MutableRemember( - useLastWhileLoading = true, - initialValue = initialValue - ) - ) - constructor(initialValue: T) : this(Signal(initialValue)) - - val changesMade = remember { draft() != published() } - - suspend fun publish(): T { - published.set(draft.awaitOnce()) - draft.reset() + + override val changesMade = remember { buffer() != published() } + + override suspend fun publish(): T { + published.set(buffer.awaitOnce()) + buffer.reset() return awaitOnce() } - fun cancel() { draft.reset() } + override fun cancel() { buffer.reset() } +} + +/** + * Creates a [Draft] using the specified [MutableReactive] as the published value. + * */ +fun Draft(published: MutableReactive): Draft = BaseDraft(published) - override suspend fun set(value: T) { draft.valueSet(value) } -} \ No newline at end of file +/** + * Creates a [Draft] where the published value is the provided [initialValue] + * */ +fun Draft(initialValue: T): Draft = BaseDraft(Signal(initialValue)) + +/** + * Creates a [Draft] where the published value is calculated based off the provided [initialValue] calculation. + * */ +fun Draft(initialValue: ReactiveContext.() -> T): Draft = BaseDraft(MutableRemember(useLastWhileLoading = true, initialValue = initialValue)) \ No newline at end of file diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/helpers.kt b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/helpers.kt index a683f68..7dc8c37 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/helpers.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/helpers.kt @@ -37,7 +37,6 @@ var MutableValue.value: T get() = throw IllegalStateException("Attempted to retrieve value for set-only property") @JvmName("setValue2") set(value) { - println("Setting outer value: $value") valueSet(value) } diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/ValidatedDraft.kt b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/ValidatedDraft.kt new file mode 100644 index 0000000..8be40bc --- /dev/null +++ b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/ValidatedDraft.kt @@ -0,0 +1,20 @@ +package com.lightningkite.reactive.lensing.validation + +import com.lightningkite.reactive.core.Draft + +/** + * A [Draft] with a validation tree. Useful for buffering and validating user input at the same time. + * + * @see Draft + * @see MutableValidated + * */ +interface ValidatedDraft : Draft, MutableValidated + +private class RootValidatedDraft(val draft: Draft) : ValidatedDraft, Draft by draft { + override val node: IssueNode = IssueNode(null) + + override fun lens(get: (T) -> L, set: (L) -> T): MutableValidated = ValidatedSetLens(this, get, set) + override fun lens(get: (T) -> L, modify: (T, L) -> T): MutableValidated = ValidatedModifyLens(this, get, modify) +} + +fun Draft.validated(): ValidatedDraft = RootValidatedDraft(this) \ No newline at end of file