1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-04 21:25:24 +00:00

Add simple container element

This commit is contained in:
Jérémie Pardou 2025-03-12 11:49:12 +01:00
parent ecf96b5cc8
commit 1d0481abf9
14 changed files with 226 additions and 6 deletions
backend/src/baserow/contrib/builder
changelog/entries/unreleased/feature
enterprise/web-frontend/modules/baserow_enterprise/components
web-frontend/modules

View file

@ -186,6 +186,7 @@ class BuilderConfig(AppConfig):
MenuElementType,
RecordSelectorElementType,
RepeatElementType,
SimpleContainerElementType,
TableElementType,
TextElementType,
)
@ -209,6 +210,7 @@ class BuilderConfig(AppConfig):
element_type_registry.register(HeaderElementType())
element_type_registry.register(FooterElementType())
element_type_registry.register(MenuElementType())
element_type_registry.register(SimpleContainerElementType())
from .domains.domain_types import CustomDomainType, SubDomainType
from .domains.registries import domain_type_registry

View file

@ -61,6 +61,7 @@ from baserow.contrib.builder.elements.models import (
NavigationElementMixin,
RecordSelectorElement,
RepeatElement,
SimpleContainerElement,
TableElement,
TextElement,
VerticalAlignments,
@ -278,6 +279,17 @@ class FormContainerElementType(ContainerElementTypeMixin, ElementType):
]
class SimpleContainerElementType(ContainerElementTypeMixin, ElementType):
type = "simple_container"
model_class = SimpleContainerElement
class SerializedDict(ContainerElementTypeMixin.SerializedDict):
pass
def get_pytest_params(self, pytest_data_fixture) -> Dict[str, Any]:
return {}
class TableElementType(CollectionElementWithFieldsTypeMixin, ElementType):
type = "table"
model_class = TableElement

View file

@ -1066,3 +1066,9 @@ class MenuElement(Element):
)
menu_items = models.ManyToManyField(MenuItemElement)
class SimpleContainerElement(ContainerElement):
"""
A simple container to group elements
"""

View file

@ -0,0 +1,36 @@
# Generated by Django 5.0.9 on 2025-03-08 13:26
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"builder",
"0053_buttonthemeconfigblock_button_active_background_color_and_more",
),
]
operations = [
migrations.CreateModel(
name="SimpleContainerElement",
fields=[
(
"element_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="builder.element",
),
),
],
options={
"abstract": False,
},
bases=("builder.element",),
),
]

View file

@ -0,0 +1,8 @@
{
"type": "feature",
"message": "Added a container element to group multiple elements.",
"domain": "builder",
"issue_number": 3458,
"bullet_points": [],
"created_at": "2025-03-08"
}

View file

@ -84,6 +84,11 @@ import { useVuelidate } from '@vuelidate/core'
export default {
name: 'EnterpriseSettings',
components: { UserFilesModal },
setup() {
return {
v$: useVuelidate({ $lazy: true }),
}
},
computed: {
IMAGE_FILE_TYPES() {
return IMAGE_FILE_TYPES
@ -95,11 +100,6 @@ export default {
settings: 'settings/get',
}),
},
setup() {
return {
v$: useVuelidate({ $lazy: true }),
}
},
methods: {
async updateSettings(values) {
this.v$.$touch()

View file

@ -0,0 +1,5 @@
<svg width="72" height="48" viewBox="0 0 72 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1.25" y="1.25" width="69.5" height="45.5" rx="4.75" fill="white"/>
<rect x="1.25" y="1.25" width="69.5" height="45.5" rx="4.75" stroke="#E6E6E7" stroke-width="1.5"/>
<rect x="8.75" y="8.75" width="54.5" height="30.5" rx="3.25" fill="#F7F7F7" stroke="#E6E6E7" stroke-width="1.5" stroke-dasharray="4 4"/>
</svg>

After

(image error) Size: 415 B

View file

@ -0,0 +1,66 @@
<template>
<div>
<template
v-if="
mode === 'editing' &&
children.length === 0 &&
$hasPermission('builder.page.create_element', currentPage, workspace.id)
"
>
<AddElementZone @add-element="showAddElementModal"></AddElementZone>
<AddElementModal
ref="addElementModal"
:page="elementPage"
></AddElementModal>
</template>
<template v-else>
<template v-for="child in children">
<ElementPreview
v-if="mode === 'editing'"
:key="child.id"
:element="child"
@move="$emit('move', $event)"
/>
<PageElement
v-else
:key="`${child.id}else`"
:element="child"
:mode="mode"
/>
</template>
</template>
</div>
</template>
<script>
import AddElementZone from '@baserow/modules/builder/components/elements/AddElementZone'
import containerElement from '@baserow/modules/builder/mixins/containerElement'
import AddElementModal from '@baserow/modules/builder/components/elements/AddElementModal'
import ElementPreview from '@baserow/modules/builder/components/elements/ElementPreview'
import PageElement from '@baserow/modules/builder/components/page/PageElement'
export default {
name: 'SimpleContainerElement',
components: {
PageElement,
ElementPreview,
AddElementModal,
AddElementZone,
},
mixins: [containerElement],
props: {
element: {
type: Object,
required: true,
},
},
methods: {
showAddElementModal() {
this.$refs.addElementModal.show({
placeInContainer: null,
parentElementId: this.element.id,
})
},
},
}
</script>

View file

@ -0,0 +1,22 @@
<template>
<form @submit.prevent @keydown.enter.prevent>
<p>{{ $t('simpleContainerElementForm.noConfigurationOptions') }}</p>
</form>
</template>
<script>
import elementForm from '@baserow/modules/builder/mixins/elementForm'
export default {
name: 'SimpleContainerElementForm',
mixins: [elementForm],
data() {
return {
values: {
styles: {},
},
allowedValues: ['styles'],
}
},
}
</script>

View file

@ -30,6 +30,7 @@
</Dropdown>
</div>
<ButtonIcon
tag="a"
class="filters__remove page-settings-query-params__remove"
icon="iconoir-bin"
@click="deleteQueryParam(index)"

View file

@ -37,6 +37,8 @@ import RuntimeFormulaContext from '@baserow/modules/core/runtimeFormulaContext'
import { resolveFormula } from '@baserow/modules/core/formula'
import FormContainerElement from '@baserow/modules/builder/components/elements/components/FormContainerElement.vue'
import FormContainerElementForm from '@baserow/modules/builder/components/elements/components/forms/general/FormContainerElementForm.vue'
import SimpleContainerElement from '@baserow/modules/builder/components/elements/components/SimpleContainerElement.vue'
import SimpleContainerElementForm from '@baserow/modules/builder/components/elements/components/forms/general/SimpleContainerElementForm.vue'
import ChoiceElement from '@baserow/modules/builder/components/elements/components/ChoiceElement.vue'
import ChoiceElementForm from '@baserow/modules/builder/components/elements/components/forms/general/ChoiceElementForm.vue'
import CheckboxElement from '@baserow/modules/builder/components/elements/components/CheckboxElement.vue'
@ -856,6 +858,58 @@ export class ColumnElementType extends ContainerElementTypeMixin(ElementType) {
}
}
export class SimpleContainerElementType extends ContainerElementTypeMixin(
ElementType
) {
static getType() {
return 'simple_container'
}
category() {
return 'layoutElement'
}
get name() {
return this.app.i18n.t('elementType.simpleContainer')
}
get description() {
return this.app.i18n.t('elementType.simpleContainerDescription')
}
get iconClass() {
return 'iconoir-square'
}
get component() {
return SimpleContainerElement
}
get generalFormComponent() {
return SimpleContainerElementForm
}
getDefaultValues(page, values) {
const superValues = super.getDefaultValues(page, values)
return {
...superValues,
style_padding_left: 0,
style_padding_right: 0,
style_padding_top: 0,
style_padding_bottom: 0,
}
}
getDefaultChildValues(page, values) {
// Unlike other container we don't want to affect the child padding.
return {}
}
getElementPlaces(element) {
return [null]
}
}
export class TableElementType extends CollectionElementTypeMixin(ElementType) {
static getType() {
return 'table'

View file

@ -125,7 +125,9 @@
"notAllowedInsideSameType": "This element is not allowed in a container of the same type",
"notAllowedLocation": "This element is not allowed at this location",
"menu": "Menu",
"menuDescription": "Menu element"
"menuDescription": "Menu element",
"simpleContainer": "Container",
"simpleContainerDescription": "A container for other elements"
},
"addElementButton": {
"label": "Element"
@ -232,6 +234,9 @@
"eventDescription": "To configure actions for this button, open the Events tab of this element.",
"noMenuItemsMessage": "Click 'Add' to add your first menu item."
},
"simpleContainerElementForm": {
"noConfigurationOptions": "The container element does not have any configuration options."
},
"imageElement": {
"missingValue": "Missing alt text...",
"emptyValue": "Empty alt text..."

View file

@ -45,6 +45,7 @@ import {
HeaderElementType,
FooterElementType,
MenuElementType,
SimpleContainerElementType,
} from '@baserow/modules/builder/elementTypes'
import {
DesktopDeviceType,
@ -218,6 +219,7 @@ export default (context) => {
app.$registry.register('element', new LinkElementType(context))
app.$registry.register('element', new ButtonElementType(context))
app.$registry.register('element', new TableElementType(context))
app.$registry.register('element', new SimpleContainerElementType(context))
app.$registry.register('element', new ColumnElementType(context))
app.$registry.register('element', new HeaderElementType(context))
app.$registry.register('element', new FooterElementType(context))

View file

@ -13,6 +13,7 @@ $baserow-builder-icons: (
'header',
'heading',
'iframe',
'simple_container',
'positioned_container',
'menu',
'image',