From 4965d347143ec2772460d52c7d2be28e169701ae Mon Sep 17 00:00:00 2001 From: Tsering Paljor <tsering@baserow.io> Date: Wed, 26 Mar 2025 12:16:38 +0400 Subject: [PATCH 1/3] Rebase, fix validation, fix icon. --- .../src/baserow/contrib/builder/constants.py | 5 + ...onfigblock_image_border_radius_and_more.py | 62 +++++++ .../baserow/contrib/builder/theme/models.py | 23 ++- .../builder/test_builder_application_type.py | 4 +- ...e_elements_border_radius_using_pixels.json | 8 + .../components/BorderRadiusSelector.vue | 39 +++++ .../theme/ImageThemeConfigBlock.vue | 152 +++++++++++++++--- web-frontend/modules/builder/enums.js | 5 + web-frontend/modules/builder/locales/en.json | 10 +- .../modules/builder/themeConfigBlockTypes.js | 39 ++++- 10 files changed, 316 insertions(+), 31 deletions(-) create mode 100644 backend/src/baserow/contrib/builder/migrations/0056_remove_imagethemeconfigblock_image_border_radius_and_more.py create mode 100644 changelog/entries/unreleased/feature/3360_add_ability_to_set_image_elements_border_radius_using_pixels.json create mode 100644 web-frontend/modules/builder/components/BorderRadiusSelector.vue diff --git a/backend/src/baserow/contrib/builder/constants.py b/backend/src/baserow/contrib/builder/constants.py index 9a80888b6..397d2254d 100644 --- a/backend/src/baserow/contrib/builder/constants.py +++ b/backend/src/baserow/contrib/builder/constants.py @@ -42,3 +42,8 @@ class FontWeights(models.TextChoices): HEAVY = "heavy" BLACK = "black" EXTRA_BLACK = "extra-black" + + +class BorderRadius(models.TextChoices): + PERCENT = "percent" + PIXEL = "pixel" diff --git a/backend/src/baserow/contrib/builder/migrations/0056_remove_imagethemeconfigblock_image_border_radius_and_more.py b/backend/src/baserow/contrib/builder/migrations/0056_remove_imagethemeconfigblock_image_border_radius_and_more.py new file mode 100644 index 000000000..54f14d34f --- /dev/null +++ b/backend/src/baserow/contrib/builder/migrations/0056_remove_imagethemeconfigblock_image_border_radius_and_more.py @@ -0,0 +1,62 @@ +# Generated by Django 5.0.13 on 2025-03-26 08:12 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("builder", "0055_linkthemeconfigblock_link_active_text_decoration_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="imagethemeconfigblock", + name="image_border_radius", + ), + migrations.AddField( + model_name="imagethemeconfigblock", + name="image_border_radius_percent", + field=models.SmallIntegerField( + db_default=0, + default=0, + help_text="The border radius percentage for this image element.", + validators=[ + django.core.validators.MinValueValidator( + 0, message="Value cannot be less than 0." + ), + django.core.validators.MaxValueValidator( + 50, message="Value cannot be greater than 50." + ), + ], + ), + ), + migrations.AddField( + model_name="imagethemeconfigblock", + name="image_border_radius_pixel", + field=models.SmallIntegerField( + db_default=0, + default=0, + help_text="The border radius pixels for this image element.", + validators=[ + django.core.validators.MinValueValidator( + 0, message="Value cannot be less than 0." + ), + django.core.validators.MaxValueValidator( + 100, message="Value cannot be greater than 100." + ), + ], + ), + ), + migrations.AddField( + model_name="imagethemeconfigblock", + name="image_border_radius_type", + field=models.CharField( + choices=[("percent", "Percent"), ("pixel", "Pixel")], + db_default="pixel", + default="pixel", + help_text="The border radius type for this image element.", + max_length=7, + ), + ), + ] diff --git a/backend/src/baserow/contrib/builder/theme/models.py b/backend/src/baserow/contrib/builder/theme/models.py index 441e56224..d8f818d31 100644 --- a/backend/src/baserow/contrib/builder/theme/models.py +++ b/backend/src/baserow/contrib/builder/theme/models.py @@ -5,6 +5,7 @@ from baserow.contrib.builder.constants import ( BACKGROUND_IMAGE_MODES, COLOR_FIELD_MAX_LENGTH, WIDTHS, + BorderRadius, FontWeights, HorizontalAlignments, ) @@ -411,8 +412,26 @@ class ImageThemeConfigBlock(ThemeConfigBlock): ], ) - image_border_radius = models.SmallIntegerField( - help_text="The border radius for this image element.", + image_border_radius_type = models.CharField( + help_text="The border radius type for this image element.", + choices=BorderRadius.choices, + max_length=7, + default=BorderRadius.PIXEL, + db_default=BorderRadius.PIXEL, + ) + + image_border_radius_percent = models.SmallIntegerField( + help_text="The border radius percentage for this image element.", + validators=[ + MinValueValidator(0, message="Value cannot be less than 0."), + MaxValueValidator(50, message="Value cannot be greater than 50."), + ], + default=0, + db_default=0, + ) + + image_border_radius_pixel = models.SmallIntegerField( + help_text="The border radius pixels for this image element.", validators=[ MinValueValidator(0, message="Value cannot be less than 0."), MaxValueValidator(100, message="Value cannot be greater than 100."), diff --git a/backend/tests/baserow/contrib/builder/test_builder_application_type.py b/backend/tests/baserow/contrib/builder/test_builder_application_type.py index a44af0cd4..247b179f6 100644 --- a/backend/tests/baserow/contrib/builder/test_builder_application_type.py +++ b/backend/tests/baserow/contrib/builder/test_builder_application_type.py @@ -683,7 +683,9 @@ def test_builder_application_export(data_fixture): "link_default_text_decoration": [True, False, False, False], "link_hover_text_decoration": [True, False, False, False], "image_alignment": "left", - "image_border_radius": 0, + "image_border_radius_percent": 0, + "image_border_radius_pixel": 0, + "image_border_radius_type": "pixel", "image_max_width": 100, "image_max_height": None, "image_constraint": "contain", diff --git a/changelog/entries/unreleased/feature/3360_add_ability_to_set_image_elements_border_radius_using_pixels.json b/changelog/entries/unreleased/feature/3360_add_ability_to_set_image_elements_border_radius_using_pixels.json new file mode 100644 index 000000000..377774f88 --- /dev/null +++ b/changelog/entries/unreleased/feature/3360_add_ability_to_set_image_elements_border_radius_using_pixels.json @@ -0,0 +1,8 @@ +{ + "type": "feature", + "message": "Add ability to set Image element's border radius using pixels or percentage.", + "domain": "builder", + "issue_number": 3360, + "bullet_points": [], + "created_at": "2025-03-26" +} \ No newline at end of file diff --git a/web-frontend/modules/builder/components/BorderRadiusSelector.vue b/web-frontend/modules/builder/components/BorderRadiusSelector.vue new file mode 100644 index 000000000..daa2ba90a --- /dev/null +++ b/web-frontend/modules/builder/components/BorderRadiusSelector.vue @@ -0,0 +1,39 @@ +<template> + <RadioGroup + :model-value="value" + :options="borderRadiusOptions" + type="button" + @input="$emit('input', $event)" + /> +</template> + +<script> +import { BORDER_RADIUS_TYPES } from '@baserow/modules/builder/enums' + +export default { + name: 'BorderRadiusSelector', + props: { + value: { + type: String, + required: false, + default: null, + }, + }, + computed: { + borderRadiusOptions() { + return [ + { + title: this.$t('borderRadiusSelector.pixel'), + value: BORDER_RADIUS_TYPES.PIXEL, + icon: 'iconoir-cursor-pointer', + }, + { + title: this.$t('borderRadiusSelector.percent'), + value: BORDER_RADIUS_TYPES.PERCENT, + icon: 'iconoir-percentage', + }, + ] + }, + }, +} +</script> \ No newline at end of file diff --git a/web-frontend/modules/builder/components/theme/ImageThemeConfigBlock.vue b/web-frontend/modules/builder/components/theme/ImageThemeConfigBlock.vue index 235cb0f19..4d9801285 100644 --- a/web-frontend/modules/builder/components/theme/ImageThemeConfigBlock.vue +++ b/web-frontend/modules/builder/components/theme/ImageThemeConfigBlock.vue @@ -121,25 +121,84 @@ </FormGroup> <FormGroup + horizontal-narrow + small-label + required + :label="$t('imageThemeConfigBlock.imageBorderRadiusType')" + class="margin-bottom-2" + > + <BorderRadiusSelector v-model="values.image_border_radius_type" /> + + <template #after-input> + <ResetButton + v-model="values.image_border_radius_type" + :default-value="theme?.image_border_radius_type" + /> + </template> + </FormGroup> + + <FormGroup + v-if="showBorderRadiusPercent" horizontal-narrow small-label required class="margin-bottom-2" :label="$t('imageThemeConfigBlock.imageBorderRadiusLabel')" - :error="fieldHasErrors('image_border_radius')" + :error="fieldHasErrors('image_border_radius_percent')" > <FormInput - v-model="values.image_border_radius" + v-model="v$.values.image_border_radius_percent.$model" :default-value-when-empty=" - defaultValuesWhenEmpty[`image_border_radius`] + defaultValuesWhenEmpty[`image_border_radius_percent`] " - :error="fieldHasErrors('image_border_radius')" + :error="fieldHasErrors('image_border_radius_percent')" type="number" - :min="0" - :max="100" + :min="defaultValuesWhenEmpty.image_border_radius_percent.min" + :max="defaultValuesWhenEmpty.image_border_radius_percent.max" remove-number-input-controls :placeholder=" - $t('imageThemeConfigBlock.imageBorderRadiusPlaceholder') + $t('imageThemeConfigBlock.imageBorderRadiusPercentPlaceholder') + " + :to-value="(value) => (value ? parseInt(value) : null)" + > + <template #suffix> + <i class="iconoir-percentage"></i> + </template> + </FormInput> + + <template #after-input> + <ResetButton + v-model="values.image_border_radius_percent" + :default-value="theme?.image_border_radius_percent" + /> + </template> + + <template #error> + {{ v$.values.image_border_radius_percent.$errors[0].$message }} + </template> + </FormGroup> + + <FormGroup + v-if="showBorderRadiusPixel" + horizontal-narrow + small-label + required + class="margin-bottom-2" + :label="$t('imageThemeConfigBlock.imageBorderRadiusLabel')" + :error="fieldHasErrors('image_border_radius_pixel')" + > + <FormInput + v-model="v$.values.image_border_radius_pixel.$model" + :default-value-when-empty=" + defaultValuesWhenEmpty[`image_border_radius_pixel`] + " + :error="fieldHasErrors('image_border_radius_pixel')" + type="number" + :min="defaultValuesWhenEmpty.image_border_radius_pixel.min" + :max="defaultValuesWhenEmpty.image_border_radius_pixel.max" + remove-number-input-controls + :placeholder=" + $t('imageThemeConfigBlock.imageBorderRadiusPixelPlaceholder') " :to-value="(value) => (value ? parseInt(value) : null)" > @@ -148,13 +207,13 @@ <template #after-input> <ResetButton - v-model="values.image_border_radius" - :default-value="theme?.image_border_radius" + v-model="values.image_border_radius_pixel" + :default-value="theme?.image_border_radius_pixel" /> </template> <template #error> - {{ v$.values.image_border_radius.$errors[0].$message }} + {{ v$.values.image_border_radius_pixel.$errors[0].$message }} </template> </FormGroup> </template> @@ -171,7 +230,11 @@ import themeConfigBlock from '@baserow/modules/builder/mixins/themeConfigBlock' import ThemeConfigBlockSection from '@baserow/modules/builder/components/theme/ThemeConfigBlockSection' import ResetButton from '@baserow/modules/builder/components/theme/ResetButton' import HorizontalAlignmentsSelector from '@baserow/modules/builder/components/HorizontalAlignmentsSelector' -import { IMAGE_SOURCE_TYPES } from '@baserow/modules/builder/enums' +import BorderRadiusSelector from '@baserow/modules/builder/components/BorderRadiusSelector.vue' +import { + IMAGE_SOURCE_TYPES, + BORDER_RADIUS_TYPES, +} from '@baserow/modules/builder/enums' import { integer, maxValue, @@ -189,7 +252,11 @@ const minMax = { min: 5, max: 3000, }, - image_border_radius: { + image_border_radius_percent: { + min: 0, + max: 50, + }, + image_border_radius_pixel: { min: 0, max: 100, }, @@ -198,6 +265,7 @@ const minMax = { export default { name: 'ImageThemeConfigBlock', components: { + BorderRadiusSelector, ThemeConfigBlockSection, ResetButton, HorizontalAlignmentsSelector, @@ -213,23 +281,50 @@ export default { image_max_width: this.theme?.image_max_width, image_max_height: this.theme?.image_max_height, image_constraint: this.theme?.image_constraint, - image_border_radius: this.theme?.image_border_radius, + image_border_radius_type: this.theme?.image_border_radius_type, + image_border_radius_pixel: this.theme?.image_border_radius_pixel, + image_border_radius_percent: this.theme?.image_border_radius_percent, }, allowedValues: [ 'image_alignment', 'image_max_width', 'image_max_height', 'image_constraint', - 'image_border_radius', + 'image_border_radius_pixel', + 'image_border_radius_percent', + 'image_border_radius_type', ], defaultValuesWhenEmpty: { image_min_width: minMax.image_width.min, image_min_height: minMax.image_height.min, - image_border_radius: minMax.image_border_radius.min, + image_border_radius_pixel: minMax.image_border_radius_pixel.min, + image_border_radius_percent: minMax.image_border_radius_percent.min, }, } }, computed: { + borderRadiusTypes() { + return [ + { + label: this.$t('imageThemeConfigBlock.imageBorderRadiusTypePixel'), + value: BORDER_RADIUS_TYPES.PIXEL, + }, + { + label: this.$t('imageThemeConfigBlock.imageBorderRadiusTypePercent'), + value: BORDER_RADIUS_TYPES.PERCENT, + }, + ] + }, + showBorderRadiusPixel() { + return ( + this.values.image_border_radius_type === BORDER_RADIUS_TYPES.PIXEL + ) + }, + showBorderRadiusPercent() { + return ( + this.values.image_border_radius_type === BORDER_RADIUS_TYPES.PERCENT + ) + }, imageMaxHeight: { get() { return this.values.image_max_height @@ -338,19 +433,34 @@ export default { }, image_constraint: {}, image_alignment: {}, - image_border_radius: { + image_border_radius_percent: { integer: helpers.withMessage(this.$t('error.integerField'), integer), minValue: helpers.withMessage( this.$t('error.minValueField', { - min: minMax.image_border_radius.min, + min: minMax.image_border_radius_percent.min, }), - minValue(minMax.image_border_radius.min) + minValue(minMax.image_border_radius_percent.min) ), maxValue: helpers.withMessage( - this.$t('error.minValueField', { - min: minMax.image_border_radius.max, + this.$t('error.maxValueField', { + max: minMax.image_border_radius_percent.max, }), - minValue(minMax.image_border_radius.min) + maxValue(minMax.image_border_radius_percent.max) + ), + }, + image_border_radius_pixel: { + integer: helpers.withMessage(this.$t('error.integerField'), integer), + minValue: helpers.withMessage( + this.$t('error.minValueField', { + min: minMax.image_border_radius_pixel.min, + }), + minValue(minMax.image_border_radius_pixel.min) + ), + maxValue: helpers.withMessage( + this.$t('error.maxValueField', { + max: minMax.image_border_radius_pixel.max, + }), + maxValue(minMax.image_border_radius_pixel.max) ), }, }, diff --git a/web-frontend/modules/builder/enums.js b/web-frontend/modules/builder/enums.js index 2becef7dc..51b791d6f 100644 --- a/web-frontend/modules/builder/enums.js +++ b/web-frontend/modules/builder/enums.js @@ -40,6 +40,11 @@ export const ALLOWED_LINK_PROTOCOLS = [ 'tel:', ] +export const BORDER_RADIUS_TYPES = { + PIXEL: 'pixel', + PERCENT: 'percent', +} + export const TEXT_FORMAT_TYPES = { PLAIN: 'plain', MARKDOWN: 'markdown', diff --git a/web-frontend/modules/builder/locales/en.json b/web-frontend/modules/builder/locales/en.json index 8b3a4c30c..519574089 100644 --- a/web-frontend/modules/builder/locales/en.json +++ b/web-frontend/modules/builder/locales/en.json @@ -400,6 +400,10 @@ "alignmentCenter": "Center", "alignmentRight": "Right" }, + "borderRadiusSelector": { + "percent": "Percent", + "pixel": "Pixel" + }, "verticalAlignmentSelector": { "alignmentTop": "Top", "alignmentCenter": "Middle", @@ -637,7 +641,11 @@ "imageConstraintContain": "Contain", "imageConstraintContainDisabled": "Unavailable with a max height.", "imageBorderRadiusLabel": "Border radius", - "imageBorderRadiusPlaceholder": "Enter the image border radius." + "imageBorderRadiusPercentPlaceholder": "Enter the image border radius percentage.", + "imageBorderRadiusPixelPlaceholder": "Enter the image border radius pixels.", + "imageBorderRadiusType": "Border radius type", + "imageBorderRadiusTypePercent": "Pixel", + "imageBorderRadiusTypePixel": "Percent" }, "tableThemeConfigBlock": { "borderColor": "Border color", diff --git a/web-frontend/modules/builder/themeConfigBlockTypes.js b/web-frontend/modules/builder/themeConfigBlockTypes.js index dc420d01c..16881b38a 100644 --- a/web-frontend/modules/builder/themeConfigBlockTypes.js +++ b/web-frontend/modules/builder/themeConfigBlockTypes.js @@ -14,6 +14,7 @@ import { colorContrast, } from '@baserow/modules/core/utils/colors' import { + BORDER_RADIUS_TYPES, WIDTHS_NEW, HORIZONTAL_ALIGNMENTS, BACKGROUND_MODES, @@ -580,10 +581,20 @@ export class ImageThemeConfigBlockType extends ThemeConfigBlockType { 'image_constraint', baseTheme?.image_constraint ) - const imageBorderRadius = get( + const imageBorderRadiusPixel = get( theme, - 'image_border_radius', - baseTheme?.image_border_radius + 'image_border_radius_pixel', + baseTheme?.image_border_radius_pixel + ) + const imageBorderRadiusPercent = get( + theme, + 'image_border_radius_percent', + baseTheme?.image_border_radius_percent + ) + const imageBorderRadiusType = get( + theme, + 'image_border_radius_type', + baseTheme?.image_border_radius_type ) if (Object.prototype.hasOwnProperty.call(theme, 'image_max_width')) { @@ -621,9 +632,25 @@ export class ImageThemeConfigBlockType extends ThemeConfigBlockType { } } - if (Object.prototype.hasOwnProperty.call(theme, 'image_border_radius')) { - if (imageBorderRadius) { - style.style['--image-border-radius'] = `${imageBorderRadius}px` + if ( + Object.prototype.hasOwnProperty.call( + theme, + 'image_border_radius_percent' + ) && + imageBorderRadiusType === BORDER_RADIUS_TYPES.PERCENT + ) { + if (imageBorderRadiusPercent) { + style.style['--image-border-radius'] = `${imageBorderRadiusPercent}%` + } + } else if ( + Object.prototype.hasOwnProperty.call( + theme, + 'image_border_radius_pixel' + ) && + imageBorderRadiusType === BORDER_RADIUS_TYPES.PIXEL + ) { + if (imageBorderRadiusPixel) { + style.style['--image-border-radius'] = `${imageBorderRadiusPixel}px` } } From 6f1135191aa092dc35b46796fd83b604dab06cdd Mon Sep 17 00:00:00 2001 From: Tsering Paljor <tsering@baserow.io> Date: Wed, 26 Mar 2025 12:18:08 +0400 Subject: [PATCH 2/3] Lint fix --- .../builder/components/BorderRadiusSelector.vue | 2 +- .../components/theme/ImageThemeConfigBlock.vue | 14 ++++++-------- web-frontend/modules/builder/enums.js | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/web-frontend/modules/builder/components/BorderRadiusSelector.vue b/web-frontend/modules/builder/components/BorderRadiusSelector.vue index daa2ba90a..407146a49 100644 --- a/web-frontend/modules/builder/components/BorderRadiusSelector.vue +++ b/web-frontend/modules/builder/components/BorderRadiusSelector.vue @@ -36,4 +36,4 @@ export default { }, }, } -</script> \ No newline at end of file +</script> diff --git a/web-frontend/modules/builder/components/theme/ImageThemeConfigBlock.vue b/web-frontend/modules/builder/components/theme/ImageThemeConfigBlock.vue index 4d9801285..3621c8157 100644 --- a/web-frontend/modules/builder/components/theme/ImageThemeConfigBlock.vue +++ b/web-frontend/modules/builder/components/theme/ImageThemeConfigBlock.vue @@ -291,14 +291,14 @@ export default { 'image_max_height', 'image_constraint', 'image_border_radius_pixel', - 'image_border_radius_percent', + 'image_border_radius_percent', 'image_border_radius_type', ], defaultValuesWhenEmpty: { image_min_width: minMax.image_width.min, image_min_height: minMax.image_height.min, image_border_radius_pixel: minMax.image_border_radius_pixel.min, - image_border_radius_percent: minMax.image_border_radius_percent.min, + image_border_radius_percent: minMax.image_border_radius_percent.min, }, } }, @@ -308,23 +308,21 @@ export default { { label: this.$t('imageThemeConfigBlock.imageBorderRadiusTypePixel'), value: BORDER_RADIUS_TYPES.PIXEL, - }, + }, { label: this.$t('imageThemeConfigBlock.imageBorderRadiusTypePercent'), value: BORDER_RADIUS_TYPES.PERCENT, - }, + }, ] }, showBorderRadiusPixel() { - return ( - this.values.image_border_radius_type === BORDER_RADIUS_TYPES.PIXEL - ) + return this.values.image_border_radius_type === BORDER_RADIUS_TYPES.PIXEL }, showBorderRadiusPercent() { return ( this.values.image_border_radius_type === BORDER_RADIUS_TYPES.PERCENT ) - }, + }, imageMaxHeight: { get() { return this.values.image_max_height diff --git a/web-frontend/modules/builder/enums.js b/web-frontend/modules/builder/enums.js index 51b791d6f..9730ee8db 100644 --- a/web-frontend/modules/builder/enums.js +++ b/web-frontend/modules/builder/enums.js @@ -42,7 +42,7 @@ export const ALLOWED_LINK_PROTOCOLS = [ export const BORDER_RADIUS_TYPES = { PIXEL: 'pixel', - PERCENT: 'percent', + PERCENT: 'percent', } export const TEXT_FORMAT_TYPES = { From 3bf63a4e761a0688f8ceab1713ffd1316b27eda3 Mon Sep 17 00:00:00 2001 From: Tsering Paljor <tsering@baserow.io> Date: Thu, 27 Mar 2025 15:04:34 +0400 Subject: [PATCH 3/3] Use strings rather than icons for Border radius selector --- .../builder/components/BorderRadiusSelector.vue | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/web-frontend/modules/builder/components/BorderRadiusSelector.vue b/web-frontend/modules/builder/components/BorderRadiusSelector.vue index 407146a49..a6dbf27a7 100644 --- a/web-frontend/modules/builder/components/BorderRadiusSelector.vue +++ b/web-frontend/modules/builder/components/BorderRadiusSelector.vue @@ -1,7 +1,7 @@ <template> <RadioGroup :model-value="value" - :options="borderRadiusOptions" + :options="options" type="button" @input="$emit('input', $event)" /> @@ -20,17 +20,15 @@ export default { }, }, computed: { - borderRadiusOptions() { + options() { return [ { - title: this.$t('borderRadiusSelector.pixel'), + label: this.$t('borderRadiusSelector.pixel'), value: BORDER_RADIUS_TYPES.PIXEL, - icon: 'iconoir-cursor-pointer', }, { - title: this.$t('borderRadiusSelector.percent'), + label: this.$t('borderRadiusSelector.percent'), value: BORDER_RADIUS_TYPES.PERCENT, - icon: 'iconoir-percentage', }, ] },