mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-10 23:50:12 +00:00
Add simple container element
This commit is contained in:
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
builder
core/assets/scss/components/builder
|
@ -186,6 +186,7 @@ class BuilderConfig(AppConfig):
|
||||||
MenuElementType,
|
MenuElementType,
|
||||||
RecordSelectorElementType,
|
RecordSelectorElementType,
|
||||||
RepeatElementType,
|
RepeatElementType,
|
||||||
|
SimpleContainerElementType,
|
||||||
TableElementType,
|
TableElementType,
|
||||||
TextElementType,
|
TextElementType,
|
||||||
)
|
)
|
||||||
|
@ -209,6 +210,7 @@ class BuilderConfig(AppConfig):
|
||||||
element_type_registry.register(HeaderElementType())
|
element_type_registry.register(HeaderElementType())
|
||||||
element_type_registry.register(FooterElementType())
|
element_type_registry.register(FooterElementType())
|
||||||
element_type_registry.register(MenuElementType())
|
element_type_registry.register(MenuElementType())
|
||||||
|
element_type_registry.register(SimpleContainerElementType())
|
||||||
|
|
||||||
from .domains.domain_types import CustomDomainType, SubDomainType
|
from .domains.domain_types import CustomDomainType, SubDomainType
|
||||||
from .domains.registries import domain_type_registry
|
from .domains.registries import domain_type_registry
|
||||||
|
|
|
@ -61,6 +61,7 @@ from baserow.contrib.builder.elements.models import (
|
||||||
NavigationElementMixin,
|
NavigationElementMixin,
|
||||||
RecordSelectorElement,
|
RecordSelectorElement,
|
||||||
RepeatElement,
|
RepeatElement,
|
||||||
|
SimpleContainerElement,
|
||||||
TableElement,
|
TableElement,
|
||||||
TextElement,
|
TextElement,
|
||||||
VerticalAlignments,
|
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):
|
class TableElementType(CollectionElementWithFieldsTypeMixin, ElementType):
|
||||||
type = "table"
|
type = "table"
|
||||||
model_class = TableElement
|
model_class = TableElement
|
||||||
|
|
|
@ -1066,3 +1066,9 @@ class MenuElement(Element):
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_items = models.ManyToManyField(MenuItemElement)
|
menu_items = models.ManyToManyField(MenuItemElement)
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleContainerElement(ContainerElement):
|
||||||
|
"""
|
||||||
|
A simple container to group elements
|
||||||
|
"""
|
||||||
|
|
|
@ -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",),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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"
|
||||||
|
}
|
|
@ -84,6 +84,11 @@ import { useVuelidate } from '@vuelidate/core'
|
||||||
export default {
|
export default {
|
||||||
name: 'EnterpriseSettings',
|
name: 'EnterpriseSettings',
|
||||||
components: { UserFilesModal },
|
components: { UserFilesModal },
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
v$: useVuelidate({ $lazy: true }),
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
IMAGE_FILE_TYPES() {
|
IMAGE_FILE_TYPES() {
|
||||||
return IMAGE_FILE_TYPES
|
return IMAGE_FILE_TYPES
|
||||||
|
@ -95,11 +100,6 @@ export default {
|
||||||
settings: 'settings/get',
|
settings: 'settings/get',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
v$: useVuelidate({ $lazy: true }),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
async updateSettings(values) {
|
async updateSettings(values) {
|
||||||
this.v$.$touch()
|
this.v$.$touch()
|
||||||
|
|
|
@ -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 |
|
@ -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>
|
|
@ -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>
|
|
@ -30,6 +30,7 @@
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
<ButtonIcon
|
<ButtonIcon
|
||||||
|
tag="a"
|
||||||
class="filters__remove page-settings-query-params__remove"
|
class="filters__remove page-settings-query-params__remove"
|
||||||
icon="iconoir-bin"
|
icon="iconoir-bin"
|
||||||
@click="deleteQueryParam(index)"
|
@click="deleteQueryParam(index)"
|
||||||
|
|
|
@ -37,6 +37,8 @@ import RuntimeFormulaContext from '@baserow/modules/core/runtimeFormulaContext'
|
||||||
import { resolveFormula } from '@baserow/modules/core/formula'
|
import { resolveFormula } from '@baserow/modules/core/formula'
|
||||||
import FormContainerElement from '@baserow/modules/builder/components/elements/components/FormContainerElement.vue'
|
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 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 ChoiceElement from '@baserow/modules/builder/components/elements/components/ChoiceElement.vue'
|
||||||
import ChoiceElementForm from '@baserow/modules/builder/components/elements/components/forms/general/ChoiceElementForm.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'
|
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) {
|
export class TableElementType extends CollectionElementTypeMixin(ElementType) {
|
||||||
static getType() {
|
static getType() {
|
||||||
return 'table'
|
return 'table'
|
||||||
|
|
|
@ -125,7 +125,9 @@
|
||||||
"notAllowedInsideSameType": "This element is not allowed in a container of the same type",
|
"notAllowedInsideSameType": "This element is not allowed in a container of the same type",
|
||||||
"notAllowedLocation": "This element is not allowed at this location",
|
"notAllowedLocation": "This element is not allowed at this location",
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"menuDescription": "Menu element"
|
"menuDescription": "Menu element",
|
||||||
|
"simpleContainer": "Container",
|
||||||
|
"simpleContainerDescription": "A container for other elements"
|
||||||
},
|
},
|
||||||
"addElementButton": {
|
"addElementButton": {
|
||||||
"label": "Element"
|
"label": "Element"
|
||||||
|
@ -232,6 +234,9 @@
|
||||||
"eventDescription": "To configure actions for this button, open the Events tab of this element.",
|
"eventDescription": "To configure actions for this button, open the Events tab of this element.",
|
||||||
"noMenuItemsMessage": "Click 'Add' to add your first menu item."
|
"noMenuItemsMessage": "Click 'Add' to add your first menu item."
|
||||||
},
|
},
|
||||||
|
"simpleContainerElementForm": {
|
||||||
|
"noConfigurationOptions": "The container element does not have any configuration options."
|
||||||
|
},
|
||||||
"imageElement": {
|
"imageElement": {
|
||||||
"missingValue": "Missing alt text...",
|
"missingValue": "Missing alt text...",
|
||||||
"emptyValue": "Empty alt text..."
|
"emptyValue": "Empty alt text..."
|
||||||
|
|
|
@ -45,6 +45,7 @@ import {
|
||||||
HeaderElementType,
|
HeaderElementType,
|
||||||
FooterElementType,
|
FooterElementType,
|
||||||
MenuElementType,
|
MenuElementType,
|
||||||
|
SimpleContainerElementType,
|
||||||
} from '@baserow/modules/builder/elementTypes'
|
} from '@baserow/modules/builder/elementTypes'
|
||||||
import {
|
import {
|
||||||
DesktopDeviceType,
|
DesktopDeviceType,
|
||||||
|
@ -218,6 +219,7 @@ export default (context) => {
|
||||||
app.$registry.register('element', new LinkElementType(context))
|
app.$registry.register('element', new LinkElementType(context))
|
||||||
app.$registry.register('element', new ButtonElementType(context))
|
app.$registry.register('element', new ButtonElementType(context))
|
||||||
app.$registry.register('element', new TableElementType(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 ColumnElementType(context))
|
||||||
app.$registry.register('element', new HeaderElementType(context))
|
app.$registry.register('element', new HeaderElementType(context))
|
||||||
app.$registry.register('element', new FooterElementType(context))
|
app.$registry.register('element', new FooterElementType(context))
|
||||||
|
|
|
@ -13,6 +13,7 @@ $baserow-builder-icons: (
|
||||||
'header',
|
'header',
|
||||||
'heading',
|
'heading',
|
||||||
'iframe',
|
'iframe',
|
||||||
|
'simple_container',
|
||||||
'positioned_container',
|
'positioned_container',
|
||||||
'menu',
|
'menu',
|
||||||
'image',
|
'image',
|
||||||
|
|
Loading…
Add table
Reference in a new issue