diff --git a/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/contententity/SpaceContentEntityTypeDefinition.kt b/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/contententity/SpaceContentEntityTypeDefinition.kt index 4b46af90..fbaa649e 100644 --- a/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/contententity/SpaceContentEntityTypeDefinition.kt +++ b/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/contententity/SpaceContentEntityTypeDefinition.kt @@ -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") } \ No newline at end of file diff --git a/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/ButtonLinkContentEntityValidator.kt b/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/ButtonLinkContentEntityValidator.kt index 5b4baca5..7ed9808c 100644 --- a/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/ButtonLinkContentEntityValidator.kt +++ b/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/ButtonLinkContentEntityValidator.kt @@ -27,11 +27,13 @@ internal object ButtonLinkContentEntityValidator : private val textBoundary = 1..30 override fun ValidationBuilder.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.requireCorrectStyle() { diff --git a/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/ContentEntityParameterChecks.kt b/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/ContentEntityParameterChecks.kt index e4cc0dc1..2faac490 100644 --- a/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/ContentEntityParameterChecks.kt +++ b/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/ContentEntityParameterChecks.kt @@ -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.requireCorrectWidth(parameterDefinition: ContentEntityParameterDefinition) { - requireCorrectInt(parameterDefinition) - requireCorrectBoundary(parameterDefinition, widthBoundary) +internal fun ValidationBuilder.requireCorrectWidth( + validator: ContentEntityValidator<*>, + parameterDefinition: ContentEntityParameterDefinition +) { + requireCorrectInt(validator, parameterDefinition) + requireCorrectBoundary(validator, parameterDefinition, widthBoundary) } -internal fun ValidationBuilder.requireCorrectHeight(parameterDefinition: ContentEntityParameterDefinition) { - requireCorrectInt(parameterDefinition) - requireCorrectBoundary(parameterDefinition, heightBoundary) +internal fun ValidationBuilder.requireCorrectHeight( + validator: ContentEntityValidator<*>, + parameterDefinition: ContentEntityParameterDefinition +) { + requireCorrectInt(validator, parameterDefinition) + requireCorrectBoundary(validator, parameterDefinition, heightBoundary) } -internal fun ValidationBuilder.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.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.requireCorrectInt(parameterDefinition: ContentEntityParameterDefinition) { - addTypedConstraint("parameter '${parameterDefinition.name}' is not an integer") { entity -> - entity.parameters[parameterDefinition]?.let { - it.toIntOrNull() != null - } ?: true +internal fun ValidationBuilder.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.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.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.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 + } } } \ No newline at end of file diff --git a/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/SpaceContentEntityValidator.kt b/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/SpaceContentEntityValidator.kt index da0dfcaa..ea42e69f 100644 --- a/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/SpaceContentEntityValidator.kt +++ b/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/SpaceContentEntityValidator.kt @@ -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) { override fun ValidationBuilder.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) } } \ No newline at end of file diff --git a/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/TextContentEntityValidator.kt b/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/TextContentEntityValidator.kt index 30efc85f..3765393f 100644 --- a/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/TextContentEntityValidator.kt +++ b/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/TextContentEntityValidator.kt @@ -26,41 +26,25 @@ internal object TextContentEntityValidator : ContentEntityValidator(TextContentEntityTypeDefinition) { override fun ValidationBuilder.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.requireNotBlankValue() { - addTypedConstraint("parameter '${definition.value.name}' must not be blank") { entity -> - val value = entity.parameters[definition.value] - - value?.isBlank() != true - } - } - - private fun ValidationBuilder.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.requireHeading() { val headings = TextContentEntityTypeDefinition.Heading.entries.joinToString(", ") { it.key } diff --git a/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/VoidContentEntityValidator.kt b/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/VoidContentEntityValidator.kt index 222dc436..1f951042 100644 --- a/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/VoidContentEntityValidator.kt +++ b/beam-common/src/commonMain/kotlin/dev/d1s/beam/commons/validation/VoidContentEntityValidator.kt @@ -24,6 +24,6 @@ internal object VoidContentEntityValidator : ContentEntityValidator(VoidContentEntityTypeDefinition) { override fun ValidationBuilder.validate() { - requireCorrectHeight(definition.height) + requireCorrectHeight(this@VoidContentEntityValidator, definition.height) } } \ No newline at end of file diff --git a/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/component/SpaceCardComponent.kt b/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/component/SpaceCardComponent.kt index 8136343d..5a5445b4 100644 --- a/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/component/SpaceCardComponent.kt +++ b/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/component/SpaceCardComponent.kt @@ -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 @@ -51,7 +54,12 @@ class SpaceCardComponent : Component(::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() @@ -169,5 +177,7 @@ class SpaceCardComponent : Component(::Config), KoinC val iconWidth = atomic(30.px) val enableHeading = atomic(false) + + val cardFullWidth = atomic(false) } } \ No newline at end of file diff --git a/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/component/SpaceListingComponent.kt b/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/component/SpaceListingComponent.kt index 05589fc1..b203d28a 100644 --- a/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/component/SpaceListingComponent.kt +++ b/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/component/SpaceListingComponent.kt @@ -138,7 +138,7 @@ class SpaceListingComponent : Component(), KoinComponent { private fun SimplePanel.renderSpaceRow(spaces: List, 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() } } diff --git a/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/contententity/ButtonLinkContentEntityRenderer.kt b/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/contententity/ButtonLinkContentEntityRenderer.kt index 4ba2c651..08034032 100644 --- a/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/contententity/ButtonLinkContentEntityRenderer.kt +++ b/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/contententity/ButtonLinkContentEntityRenderer.kt @@ -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) } } @@ -102,32 +102,4 @@ class ButtonLinkContentEntityRenderer : ContentEntityRenderer, KoinComponent { render() } } - - private fun ContentEntities.split(block: (ContentEntities, Boolean) -> Unit) { - val entities = this - val buffer = mutableListOf() - - 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() - } } \ No newline at end of file diff --git a/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/contententity/SequenceSplitter.kt b/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/contententity/SequenceSplitter.kt new file mode 100644 index 00000000..05123acd --- /dev/null +++ b/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/contententity/SequenceSplitter.kt @@ -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() + + 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() +} \ No newline at end of file diff --git a/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/contententity/SpaceContentEntityRenderer.kt b/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/contententity/SpaceContentEntityRenderer.kt index 54730d4d..41eb3952 100644 --- a/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/contententity/SpaceContentEntityRenderer.kt +++ b/beam-ui/src/jsMain/kotlin/dev/d1s/beam/ui/contententity/SpaceContentEntityRenderer.kt @@ -44,28 +44,41 @@ class SpaceContentEntityRenderer : ContentEntityRenderer, KoinComponent { private val renderingScope = CoroutineScope(Dispatchers.Main) + private val ContentEntity.fullWidth get() = parameters[definition.fullWidth]?.toBooleanStrict() ?: false + override fun SimplePanel.render(sequence: ContentEntities, block: Block) { val lgCols = if (block.size >= BlockSize.LARGE) 2 else 1 - renderRow(lgCols) { - renderSequence(sequence, block) + sequence.split(condition = { it.fullWidth }) { entities, fullWidth -> + println("spaceContentEntity: entities: $entities fullWidth: $fullWidth") + + fun SimplePanel.renderSequence() { + renderSequence(entities, block, fullWidth) + } + + if (fullWidth) { + renderSequence() + } else { + renderRow(lgCols) { + renderSequence() + separateContentEntities(sequence, block) + } + } } } private fun SimplePanel.renderRow(lgCols: Int, block: SimplePanel.() -> Unit) { - div(className = "w-100 row row-cols-1 row-cols-lg-$lgCols g-3") { + div(className = "w-100 row row-cols-1 row-cols-lg-$lgCols") { block() } } - private fun SimplePanel.renderSequence(sequence: ContentEntities, block: Block) { + private fun SimplePanel.renderSequence(sequence: ContentEntities, block: Block, fullWidth: Boolean) { sequence.forEach { entity -> asyncDiv { - renderSpaceCard(entity) + renderSpaceCard(entity, block, fullWidth) } } - - separateContentEntities(sequence, block) } private fun SimplePanel.asyncDiv(block: suspend SimplePanel.() -> Unit) { @@ -76,7 +89,7 @@ class SpaceContentEntityRenderer : ContentEntityRenderer, KoinComponent { } } - private suspend fun SimplePanel.renderSpaceCard(entity: ContentEntity) { + private suspend fun SimplePanel.renderSpaceCard(entity: ContentEntity, block: Block, fullWidth: Boolean) { val spaceIdentifier = entity.parameters[definition.identifier] requireNotNull(spaceIdentifier) @@ -84,8 +97,13 @@ class SpaceContentEntityRenderer : ContentEntityRenderer, KoinComponent { val spaceCard = get>(Qualifier.SpaceCardComponent) + if (fullWidth) { + separateContentEntity(entity, block) + } + render(spaceCard) { this.space.value = space + this.cardFullWidth.value = fullWidth } } } \ No newline at end of file