Skip to content

Commit

Permalink
feat: add full-width space card entity support
Browse files Browse the repository at this point in the history
  • Loading branch information
d1snin committed Sep 5, 2023
1 parent f92a66e commit 209e6e8
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ package dev.d1s.beam.commons.contententity
public data object SpaceContentEntityTypeDefinition : ContentEntityTypeDefinition(name = "space") {

val identifier: ContentEntityParameterDefinition = parameter("identifier", required = true, translatable = true)

val fullWidth: ContentEntityParameterDefinition = parameter("full_width")
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ internal object ButtonLinkContentEntityValidator :
private val textBoundary = 1..30

override fun ValidationBuilder<ContentEntity>.validate() {
requireCorrectBoundary(definition.text, textBoundary, stringLengthMode = true)
requireCorrectUrl(definition.url)
val validator = this@ButtonLinkContentEntityValidator

requireCorrectBoundary(validator, definition.text, textBoundary, stringLengthMode = true)
requireCorrectUrl(validator, definition.url)
requireCorrectStyle()
requireCorrectWidth(definition.width)
requireCorrectHeight(definition.height)
requireCorrectWidth(validator, definition.width)
requireCorrectHeight(validator, definition.height)
}

private fun ValidationBuilder<ContentEntity>.requireCorrectStyle() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,53 +19,100 @@ package dev.d1s.beam.commons.validation
import dev.d1s.beam.commons.contententity.ContentEntity
import dev.d1s.beam.commons.contententity.ContentEntityParameterDefinition
import dev.d1s.beam.commons.contententity.get
import dev.d1s.beam.commons.validation.ButtonLinkContentEntityValidator.addTypedConstraint
import io.konform.validation.ValidationBuilder

private val widthBoundary = 1..100
private val heightBoundary = 1..1200

internal fun ValidationBuilder<ContentEntity>.requireCorrectWidth(parameterDefinition: ContentEntityParameterDefinition) {
requireCorrectInt(parameterDefinition)
requireCorrectBoundary(parameterDefinition, widthBoundary)
internal fun ValidationBuilder<ContentEntity>.requireCorrectWidth(
validator: ContentEntityValidator<*>,
parameterDefinition: ContentEntityParameterDefinition
) {
requireCorrectInt(validator, parameterDefinition)
requireCorrectBoundary(validator, parameterDefinition, widthBoundary)
}

internal fun ValidationBuilder<ContentEntity>.requireCorrectHeight(parameterDefinition: ContentEntityParameterDefinition) {
requireCorrectInt(parameterDefinition)
requireCorrectBoundary(parameterDefinition, heightBoundary)
internal fun ValidationBuilder<ContentEntity>.requireCorrectHeight(
validator: ContentEntityValidator<*>,
parameterDefinition: ContentEntityParameterDefinition
) {
requireCorrectInt(validator, parameterDefinition)
requireCorrectBoundary(validator, parameterDefinition, heightBoundary)
}

internal fun ValidationBuilder<ContentEntity>.requireCorrectUrl(parameterDefinition: ContentEntityParameterDefinition) =
addTypedConstraint("parameter '${parameterDefinition.name}' must be a correct url") { entity ->
entity.parameters[parameterDefinition]?.let { url ->
return@addTypedConstraint isUrl(url)
}
internal fun ValidationBuilder<ContentEntity>.requireCorrectUrl(
validator: ContentEntityValidator<*>,
parameterDefinition: ContentEntityParameterDefinition
) {
with(validator) {
addTypedConstraint("parameter '${parameterDefinition.name}' must be a correct url") { entity ->
entity.parameters[parameterDefinition]?.let { url ->
return@addTypedConstraint isUrl(url)
}

true
true
}
}
}

internal fun ValidationBuilder<ContentEntity>.requireCorrectInt(parameterDefinition: ContentEntityParameterDefinition) {
addTypedConstraint("parameter '${parameterDefinition.name}' is not an integer") { entity ->
entity.parameters[parameterDefinition]?.let {
it.toIntOrNull() != null
} ?: true
internal fun ValidationBuilder<ContentEntity>.requireCorrectInt(
validator: ContentEntityValidator<*>,
parameterDefinition: ContentEntityParameterDefinition
) {
with(validator) {
addTypedConstraint("parameter '${parameterDefinition.name}' is not an integer") { entity ->
entity.parameters[parameterDefinition]?.let {
it.toIntOrNull() != null
} ?: true
}
}
}

internal fun ValidationBuilder<ContentEntity>.requireCorrectBoundary(
validator: ContentEntityValidator<*>,
parameterDefinition: ContentEntityParameterDefinition,
boundary: IntRange,
stringLengthMode: Boolean = false
) {
addTypedConstraint("parameter '${parameterDefinition.name}' is not within its boundary $boundary") { entity ->
entity.parameters[parameterDefinition]?.let {
val value = if (stringLengthMode) {
it.length
} else {
it.toIntOrNull()
with(validator) {
addTypedConstraint("parameter '${parameterDefinition.name}' is not within its boundary $boundary") { entity ->
entity.parameters[parameterDefinition]?.let {
val value = if (stringLengthMode) {
it.length
} else {
it.toIntOrNull()
}

value in boundary
} ?: true
}
}
}

internal fun ValidationBuilder<ContentEntity>.requireNotBlankText(
validator: ContentEntityValidator<*>,
parameterDefinition: ContentEntityParameterDefinition
) {
with(validator) {
addTypedConstraint("parameter '${parameterDefinition.name}' must not be blank") { entity ->
val value = entity.parameters[parameterDefinition]

value?.isBlank() != true
}
}
}

internal fun ValidationBuilder<ContentEntity>.requireCorrectBoolean(
validator: ContentEntityValidator<*>,
parameterDefinition: ContentEntityParameterDefinition
) {
with(validator) {
addTypedConstraint("parameter '${parameterDefinition.name} must be 'true' or 'false'") { entity ->
entity.parameters[parameterDefinition]?.let {
it.toBooleanStrictOrNull() ?: return@addTypedConstraint false
}

value in boundary
} ?: true
true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ package dev.d1s.beam.commons.validation

import dev.d1s.beam.commons.contententity.ContentEntity
import dev.d1s.beam.commons.contententity.SpaceContentEntityTypeDefinition
import dev.d1s.beam.commons.contententity.get
import io.konform.validation.ValidationBuilder

internal object SpaceContentEntityValidator :
ContentEntityValidator<SpaceContentEntityTypeDefinition>(SpaceContentEntityTypeDefinition) {

override fun ValidationBuilder<ContentEntity>.validate() {
addTypedConstraint("parameter '${definition.identifier.name}' is blank") { entity ->
entity.parameters[definition.identifier]?.isBlank() != true
}
val validator = this@SpaceContentEntityValidator

requireNotBlankText(validator, definition.identifier)
requireCorrectBoolean(validator, definition.fullWidth)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,25 @@ internal object TextContentEntityValidator :
ContentEntityValidator<TextContentEntityTypeDefinition>(TextContentEntityTypeDefinition) {

override fun ValidationBuilder<ContentEntity>.validate() {
requireNotBlankValue()
val validator = this@TextContentEntityValidator

requireBoolean(definition.bold)
requireBoolean(definition.italic)
requireBoolean(definition.underline)
requireBoolean(definition.strikethrough)
requireBoolean(definition.monospace)
requireBoolean(definition.paragraph)
requireBoolean(definition.secondary)
requireNotBlankText(validator, definition.value)

requireCorrectBoolean(validator, definition.bold)
requireCorrectBoolean(validator, definition.italic)
requireCorrectBoolean(validator, definition.underline)
requireCorrectBoolean(validator, definition.strikethrough)
requireCorrectBoolean(validator, definition.monospace)
requireCorrectBoolean(validator, definition.paragraph)
requireCorrectBoolean(validator, definition.secondary)

requireHeading()

requireCorrectUrl(definition.url)
requireCorrectUrl(validator, definition.url)

requireNoCollision(definition.heading, definition.paragraph)
}

private fun ValidationBuilder<ContentEntity>.requireNotBlankValue() {
addTypedConstraint("parameter '${definition.value.name}' must not be blank") { entity ->
val value = entity.parameters[definition.value]

value?.isBlank() != true
}
}

private fun ValidationBuilder<ContentEntity>.requireBoolean(parameterDefinition: ContentEntityParameterDefinition) {
addTypedConstraint("parameter '${parameterDefinition.name}' must be 'true' or 'false'") { entity ->
entity.parameters[parameterDefinition]?.let {
it.toBooleanStrictOrNull() ?: return@addTypedConstraint false
}

true
}
}

private fun ValidationBuilder<ContentEntity>.requireHeading() {
val headings = TextContentEntityTypeDefinition.Heading.entries.joinToString(", ") { it.key }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ internal object VoidContentEntityValidator :
ContentEntityValidator<VoidContentEntityTypeDefinition>(VoidContentEntityTypeDefinition) {

override fun ValidationBuilder<ContentEntity>.validate() {
requireCorrectHeight(definition.height)
requireCorrectHeight(this@VoidContentEntityValidator, definition.height)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import dev.d1s.beam.ui.theme.setSecondaryText
import dev.d1s.beam.ui.util.*
import dev.d1s.exkt.kvision.component.Component
import dev.d1s.exkt.kvision.component.Effect
import io.kvision.core.*
import io.kvision.core.JustifyContent
import io.kvision.core.TextDecoration
import io.kvision.core.TextDecorationLine
import io.kvision.core.onEvent
import io.kvision.html.*
import io.kvision.panel.SimplePanel
import io.kvision.panel.vPanel
Expand Down Expand Up @@ -51,7 +54,12 @@ class SpaceCardComponent : Component<SpaceCardComponent.Config>(::Config), KoinC
}

private fun SimplePanel.renderCard() {
val className = "p-${config.cardPaddingLevel.value} ps-${config.cardStartPaddingLevel.value}"
val className =
"p-${config.cardPaddingLevel.value} ps-${config.cardStartPaddingLevel.value}" + if (config.cardFullWidth.value) {
" w-100"
} else {
""
}

this@renderCard.renderCard(className, usePageBackground = true) {
renderContent()
Expand Down Expand Up @@ -169,5 +177,7 @@ class SpaceCardComponent : Component<SpaceCardComponent.Config>(::Config), KoinC
val iconWidth = atomic(30.px)

val enableHeading = atomic(false)

val cardFullWidth = atomic(false)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class SpaceListingComponent : Component<Unit>(), KoinComponent {
private fun SimplePanel.renderSpaceRow(spaces: List<Space>, block: SimplePanel.() -> Unit) {
val lgCols = if (spaces.size == 1) 1 else 2

div(className = "row row-cols-1 row-cols-lg-$lgCols g-3") {
div(className = "row row-cols-1 row-cols-lg-$lgCols g-3 mt-0") {
block()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class ButtonLinkContentEntityRenderer : ContentEntityRenderer, KoinComponent {
private val ContentEntity.width get() = parameters[definition.width]

override fun SimplePanel.render(sequence: ContentEntities, block: Block) {
sequence.split { entities, sized ->
sequence.split(condition = { it.width != null }) { entities, sized ->
renderButtons(entities, block, sized)
}
}
Expand Down Expand Up @@ -102,32 +102,4 @@ class ButtonLinkContentEntityRenderer : ContentEntityRenderer, KoinComponent {
render()
}
}

private fun ContentEntities.split(block: (ContentEntities, Boolean) -> Unit) {
val entities = this
val buffer = mutableListOf<ContentEntity>()

fun ContentEntity.sized() = width != null

var bufferSized = entities.first().sized()

fun executeBuffer() {
block(buffer, bufferSized)
buffer.clear()
}

entities.forEach { entity ->
val entitySized = entity.sized()

if ((bufferSized && entitySized) || (!bufferSized && !entitySized)) {
buffer.add(entity)
} else {
executeBuffer()
bufferSized = entitySized
buffer.add(entity)
}
}

executeBuffer()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2023 Mikhail Titov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dev.d1s.beam.ui.contententity

import dev.d1s.beam.commons.contententity.ContentEntities
import dev.d1s.beam.commons.contententity.ContentEntity

fun ContentEntities.split(condition: (ContentEntity) -> Boolean, block: (ContentEntities, Boolean) -> Unit) {
val entities = this
val buffer = mutableListOf<ContentEntity>()

fun ContentEntity.matches() = condition(this)

var bufferMatches = entities.first().matches()

fun executeBuffer() {
block(buffer, bufferMatches)
buffer.clear()
}

entities.forEach { entity ->
val entityMatches = entity.matches()

if ((bufferMatches && entityMatches) || (!bufferMatches && !entityMatches)) {
buffer.add(entity)
} else {
executeBuffer()
bufferMatches = entityMatches
buffer.add(entity)
}
}

executeBuffer()
}
Loading

0 comments on commit 209e6e8

Please sign in to comment.