mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-07 22:35:36 +00:00
Merge branch '2388-3-add-new-styles' into 'develop'
Improve AB styling v3 - Add a lot of new styles Closes #2388 See merge request baserow/baserow!2476
This commit is contained in:
commit
d61ad81a91
77 changed files with 3176 additions and 1072 deletions
backend
src/baserow
tests/baserow/contrib/builder
changelog/entries/unreleased/feature
web-frontend/modules
builder
components
FontFamilySelector.vuePaddingSelector.vuePixelValueSelector.vue
elementTypes.jsenums.jsfontFamilyTypes.jselements
baseComponents
components/forms
page
theme
locales
mixins
plugin.jsthemeConfigBlockTypes.jscore
assets/scss/components
builder
color_input.scsscolor_picker.scsscolor_picker_context.scssform.scssform_input.scssimage_input.scsscomponents
enums.jslocales
mixins
pages
plugins
|
@ -11,12 +11,13 @@ from baserow.api.app_auth_providers.serializers import (
|
|||
)
|
||||
from baserow.api.polymorphic import PolymorphicSerializer
|
||||
from baserow.api.services.serializers import PublicServiceSerializer
|
||||
from baserow.api.user_files.serializers import UserFileSerializer
|
||||
from baserow.api.user_files.serializers import UserFileField, UserFileSerializer
|
||||
from baserow.contrib.builder.api.pages.serializers import PathParamSerializer
|
||||
from baserow.contrib.builder.api.theme.serializers import (
|
||||
CombinedThemeConfigBlocksSerializer,
|
||||
serialize_builder_theme,
|
||||
)
|
||||
from baserow.contrib.builder.api.validators import image_file_validation
|
||||
from baserow.contrib.builder.data_sources.models import DataSource
|
||||
from baserow.contrib.builder.domains.models import Domain
|
||||
from baserow.contrib.builder.domains.registries import domain_type_registry
|
||||
|
@ -98,6 +99,12 @@ class PublicElementSerializer(serializers.ModelSerializer):
|
|||
def get_type(self, instance):
|
||||
return element_type_registry.get_by_model(instance.specific_class).type
|
||||
|
||||
style_background_file = UserFileField(
|
||||
allow_null=True,
|
||||
help_text="The background image file",
|
||||
validators=[image_file_validation],
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Element
|
||||
fields = (
|
||||
|
@ -111,17 +118,23 @@ class PublicElementSerializer(serializers.ModelSerializer):
|
|||
"style_border_top_color",
|
||||
"style_border_top_size",
|
||||
"style_padding_top",
|
||||
"style_margin_top",
|
||||
"style_border_bottom_color",
|
||||
"style_border_bottom_size",
|
||||
"style_padding_bottom",
|
||||
"style_margin_bottom",
|
||||
"style_border_left_color",
|
||||
"style_border_left_size",
|
||||
"style_padding_left",
|
||||
"style_margin_left",
|
||||
"style_border_right_color",
|
||||
"style_border_right_size",
|
||||
"style_padding_right",
|
||||
"style_margin_right",
|
||||
"style_background",
|
||||
"style_background_color",
|
||||
"style_background_file",
|
||||
"style_background_mode",
|
||||
"style_width",
|
||||
"role_type",
|
||||
"roles",
|
||||
|
|
|
@ -7,6 +7,8 @@ from drf_spectacular.utils import extend_schema_field, extend_schema_serializer
|
|||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from baserow.api.user_files.serializers import UserFileField
|
||||
from baserow.contrib.builder.api.validators import image_file_validation
|
||||
from baserow.contrib.builder.api.workflow_actions.serializers import (
|
||||
BuilderWorkflowActionSerializer,
|
||||
)
|
||||
|
@ -42,6 +44,12 @@ class ElementSerializer(serializers.ModelSerializer):
|
|||
def get_type(self, instance):
|
||||
return element_type_registry.get_by_model(instance.specific_class).type
|
||||
|
||||
style_background_file = UserFileField(
|
||||
allow_null=True,
|
||||
help_text="The background image file",
|
||||
validators=[image_file_validation],
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Element
|
||||
fields = (
|
||||
|
@ -56,17 +64,23 @@ class ElementSerializer(serializers.ModelSerializer):
|
|||
"style_border_top_color",
|
||||
"style_border_top_size",
|
||||
"style_padding_top",
|
||||
"style_margin_top",
|
||||
"style_border_bottom_color",
|
||||
"style_border_bottom_size",
|
||||
"style_padding_bottom",
|
||||
"style_margin_bottom",
|
||||
"style_border_left_color",
|
||||
"style_border_left_size",
|
||||
"style_padding_left",
|
||||
"style_margin_left",
|
||||
"style_border_right_color",
|
||||
"style_border_right_size",
|
||||
"style_padding_right",
|
||||
"style_margin_right",
|
||||
"style_background",
|
||||
"style_background_color",
|
||||
"style_background_file",
|
||||
"style_background_mode",
|
||||
"style_width",
|
||||
"role_type",
|
||||
"roles",
|
||||
|
@ -102,6 +116,12 @@ class CreateElementSerializer(serializers.ModelSerializer):
|
|||
"the given id.",
|
||||
)
|
||||
|
||||
style_background_file = UserFileField(
|
||||
allow_null=True,
|
||||
help_text="The background image file",
|
||||
validators=[image_file_validation],
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Element
|
||||
fields = (
|
||||
|
@ -111,28 +131,42 @@ class CreateElementSerializer(serializers.ModelSerializer):
|
|||
"parent_element_id",
|
||||
"place_in_container",
|
||||
"visibility",
|
||||
"styles",
|
||||
"style_border_top_color",
|
||||
"style_border_top_size",
|
||||
"style_padding_top",
|
||||
"style_margin_top",
|
||||
"style_border_bottom_color",
|
||||
"style_border_bottom_size",
|
||||
"style_padding_bottom",
|
||||
"style_margin_bottom",
|
||||
"style_border_left_color",
|
||||
"style_border_left_size",
|
||||
"style_padding_left",
|
||||
"style_margin_left",
|
||||
"style_border_right_color",
|
||||
"style_border_right_size",
|
||||
"style_padding_right",
|
||||
"style_margin_right",
|
||||
"style_background",
|
||||
"style_background_color",
|
||||
"style_background_file",
|
||||
"style_background_mode",
|
||||
"style_width",
|
||||
)
|
||||
extra_kwargs = {
|
||||
"visibility": {"default": Element.VISIBILITY_TYPES.ALL},
|
||||
"styles": {"default": dict},
|
||||
}
|
||||
|
||||
|
||||
class UpdateElementSerializer(serializers.ModelSerializer):
|
||||
style_background_file = UserFileField(
|
||||
allow_null=True,
|
||||
help_text="The background image file",
|
||||
validators=[image_file_validation],
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Element
|
||||
fields = (
|
||||
|
@ -141,17 +175,23 @@ class UpdateElementSerializer(serializers.ModelSerializer):
|
|||
"style_border_top_color",
|
||||
"style_border_top_size",
|
||||
"style_padding_top",
|
||||
"style_margin_top",
|
||||
"style_border_bottom_color",
|
||||
"style_border_bottom_size",
|
||||
"style_padding_bottom",
|
||||
"style_margin_bottom",
|
||||
"style_border_left_color",
|
||||
"style_border_left_size",
|
||||
"style_padding_left",
|
||||
"style_margin_left",
|
||||
"style_border_right_color",
|
||||
"style_border_right_size",
|
||||
"style_padding_right",
|
||||
"style_margin_right",
|
||||
"style_background",
|
||||
"style_background_color",
|
||||
"style_background_file",
|
||||
"style_background_mode",
|
||||
"style_width",
|
||||
"role_type",
|
||||
"roles",
|
||||
|
|
|
@ -5,6 +5,49 @@ from baserow.contrib.builder.models import Builder
|
|||
from baserow.contrib.builder.theme.registries import theme_config_block_registry
|
||||
|
||||
|
||||
class DynamicConfigBlockSerializer(serializers.Serializer):
|
||||
"""
|
||||
Style overrides for this element.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
property_name=None,
|
||||
theme_config_block_type_name=None,
|
||||
serializer_kwargs=None,
|
||||
**kwargs,
|
||||
):
|
||||
if property_name is None:
|
||||
raise ValueError("Missing property_name parameter")
|
||||
if theme_config_block_type_name is None:
|
||||
raise ValueError("Missing theme_block_type parameter")
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if serializer_kwargs is None:
|
||||
serializer_kwargs = {}
|
||||
|
||||
if not isinstance(property_name, list):
|
||||
property_name = [property_name]
|
||||
|
||||
if not isinstance(theme_config_block_type_name, list):
|
||||
theme_config_block_type_name = [theme_config_block_type_name]
|
||||
|
||||
for prop, type_name in zip(property_name, theme_config_block_type_name):
|
||||
theme_config_block_type = theme_config_block_registry.get(type_name)
|
||||
self.fields[prop] = theme_config_block_type.get_serializer_class()(
|
||||
**({"help_text": f"Styles overrides for {prop}"} | serializer_kwargs)
|
||||
)
|
||||
|
||||
# Dynamically create the Meta class with ref name to prevent collision
|
||||
class DynamicMeta:
|
||||
type_names = "".join([p.capitalize() for p in theme_config_block_type_name])
|
||||
ref_name = f"{type_names}ConfigBlockSerializer"
|
||||
|
||||
self.Meta = DynamicMeta
|
||||
|
||||
|
||||
def serialize_builder_theme(builder: Builder) -> dict:
|
||||
"""
|
||||
A helper function that serializes all theme properties of the provided builder.
|
||||
|
@ -35,7 +78,7 @@ def get_combined_theme_config_blocks_serializer_class() -> serializers.Serialize
|
|||
:return: The generated serializer.
|
||||
"""
|
||||
|
||||
if hasattr(get_combined_theme_config_blocks_serializer_class, "cache"):
|
||||
if hasattr(get_combined_theme_config_blocks_serializer_class, "cached_class"):
|
||||
return get_combined_theme_config_blocks_serializer_class.cached_class
|
||||
|
||||
if len(theme_config_block_registry.registry.values()) == 0:
|
||||
|
|
|
@ -56,7 +56,9 @@ class ThemeView(APIView):
|
|||
ApplicationDoesNotExist: ERROR_APPLICATION_DOES_NOT_EXIST,
|
||||
}
|
||||
)
|
||||
@validate_body(CombinedThemeConfigBlocksSerializer, return_validated=True)
|
||||
@validate_body(
|
||||
CombinedThemeConfigBlocksSerializer, return_validated=True, partial=True
|
||||
)
|
||||
def patch(self, request, data: Dict, builder_id: int):
|
||||
builder = BuilderHandler().get_builder(builder_id)
|
||||
|
||||
|
|
|
@ -240,6 +240,7 @@ class BuilderConfig(AppConfig):
|
|||
ColorThemeConfigBlockType,
|
||||
ImageThemeConfigBlockType,
|
||||
LinkThemeConfigBlockType,
|
||||
PageThemeConfigBlockType,
|
||||
TypographyThemeConfigBlockType,
|
||||
)
|
||||
|
||||
|
@ -248,6 +249,7 @@ class BuilderConfig(AppConfig):
|
|||
theme_config_block_registry.register(ButtonThemeConfigBlockType())
|
||||
theme_config_block_registry.register(LinkThemeConfigBlockType())
|
||||
theme_config_block_registry.register(ImageThemeConfigBlockType())
|
||||
theme_config_block_registry.register(PageThemeConfigBlockType())
|
||||
|
||||
from .workflow_actions.registries import builder_workflow_action_type_registry
|
||||
from .workflow_actions.workflow_action_types import (
|
||||
|
|
|
@ -18,3 +18,9 @@ class VerticalAlignments(models.TextChoices):
|
|||
class WIDTHS(models.TextChoices):
|
||||
AUTO = "auto"
|
||||
FULL = "full"
|
||||
|
||||
|
||||
class BACKGROUND_IMAGE_MODES(models.TextChoices):
|
||||
TILE = "tile"
|
||||
FILL = "fill"
|
||||
FIT = "fit"
|
||||
|
|
|
@ -161,6 +161,12 @@ class FormContainerElementType(ContainerElementTypeMixin, ElementType):
|
|||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
from baserow.contrib.builder.api.theme.serializers import (
|
||||
DynamicConfigBlockSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.theme.theme_config_block_types import (
|
||||
ButtonThemeConfigBlockType,
|
||||
)
|
||||
from baserow.core.formula.serializers import FormulaSerializerField
|
||||
|
||||
return {
|
||||
|
@ -184,6 +190,12 @@ class FormContainerElementType(ContainerElementTypeMixin, ElementType):
|
|||
).help_text,
|
||||
required=False,
|
||||
),
|
||||
"styles": DynamicConfigBlockSerializer(
|
||||
required=False,
|
||||
property_name="button",
|
||||
theme_config_block_type_name=ButtonThemeConfigBlockType.type,
|
||||
serializer_kwargs={"required": False},
|
||||
),
|
||||
}
|
||||
|
||||
@property
|
||||
|
@ -232,6 +244,13 @@ class TableElementType(CollectionElementWithFieldsTypeMixin, ElementType):
|
|||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
from baserow.contrib.builder.api.theme.serializers import (
|
||||
DynamicConfigBlockSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.theme.theme_config_block_types import (
|
||||
ButtonThemeConfigBlockType,
|
||||
)
|
||||
|
||||
return {
|
||||
**super().serializer_field_overrides,
|
||||
"orientation": serializers.JSONField(
|
||||
|
@ -239,6 +258,12 @@ class TableElementType(CollectionElementWithFieldsTypeMixin, ElementType):
|
|||
default=get_default_table_orientation,
|
||||
help_text=TableElement._meta.get_field("orientation").help_text,
|
||||
),
|
||||
"styles": DynamicConfigBlockSerializer(
|
||||
required=False,
|
||||
property_name="button",
|
||||
theme_config_block_type_name=ButtonThemeConfigBlockType.type,
|
||||
serializer_kwargs={"required": False},
|
||||
),
|
||||
}
|
||||
|
||||
def get_pytest_params(self, pytest_data_fixture) -> Dict[str, Any]:
|
||||
|
@ -276,6 +301,25 @@ class RepeatElementType(
|
|||
orientation: str
|
||||
items_per_row: dict
|
||||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
from baserow.contrib.builder.api.theme.serializers import (
|
||||
DynamicConfigBlockSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.theme.theme_config_block_types import (
|
||||
ButtonThemeConfigBlockType,
|
||||
)
|
||||
|
||||
return {
|
||||
**super().serializer_field_overrides,
|
||||
"styles": DynamicConfigBlockSerializer(
|
||||
required=False,
|
||||
property_name="button",
|
||||
theme_config_block_type_name=ButtonThemeConfigBlockType.type,
|
||||
serializer_kwargs={"required": False},
|
||||
),
|
||||
}
|
||||
|
||||
def import_context_addition(self, instance, id_mapping):
|
||||
return {"data_source_id": instance.data_source_id}
|
||||
|
||||
|
@ -305,6 +349,12 @@ class HeadingElementType(ElementType):
|
|||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
from baserow.contrib.builder.api.theme.serializers import (
|
||||
DynamicConfigBlockSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.theme.theme_config_block_types import (
|
||||
TypographyThemeConfigBlockType,
|
||||
)
|
||||
from baserow.core.formula.serializers import FormulaSerializerField
|
||||
|
||||
overrides = {
|
||||
|
@ -326,6 +376,12 @@ class HeadingElementType(ElementType):
|
|||
allow_blank=True,
|
||||
help_text="Heading font color.",
|
||||
),
|
||||
"styles": DynamicConfigBlockSerializer(
|
||||
required=False,
|
||||
property_name="typography",
|
||||
theme_config_block_type_name=TypographyThemeConfigBlockType.type,
|
||||
serializer_kwargs={"required": False},
|
||||
),
|
||||
}
|
||||
|
||||
return overrides
|
||||
|
@ -379,6 +435,12 @@ class TextElementType(ElementType):
|
|||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
from baserow.contrib.builder.api.theme.serializers import (
|
||||
DynamicConfigBlockSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.theme.theme_config_block_types import (
|
||||
TypographyThemeConfigBlockType,
|
||||
)
|
||||
from baserow.core.formula.serializers import FormulaSerializerField
|
||||
|
||||
return {
|
||||
|
@ -393,6 +455,12 @@ class TextElementType(ElementType):
|
|||
default=TextElement.TEXT_FORMATS.PLAIN,
|
||||
help_text=TextElement._meta.get_field("format").help_text,
|
||||
),
|
||||
"styles": DynamicConfigBlockSerializer(
|
||||
required=False,
|
||||
property_name="typography",
|
||||
theme_config_block_type_name=TypographyThemeConfigBlockType.type,
|
||||
serializer_kwargs={"required": False},
|
||||
),
|
||||
}
|
||||
|
||||
def deserialize_property(
|
||||
|
@ -636,6 +704,13 @@ class LinkElementType(ElementType):
|
|||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
from baserow.contrib.builder.api.theme.serializers import (
|
||||
DynamicConfigBlockSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.theme.theme_config_block_types import (
|
||||
ButtonThemeConfigBlockType,
|
||||
LinkThemeConfigBlockType,
|
||||
)
|
||||
from baserow.core.formula.serializers import FormulaSerializerField
|
||||
|
||||
overrides = (
|
||||
|
@ -669,8 +744,18 @@ class LinkElementType(ElementType):
|
|||
default="primary",
|
||||
help_text="Button color.",
|
||||
),
|
||||
"styles": DynamicConfigBlockSerializer(
|
||||
required=False,
|
||||
property_name=["button", "link"],
|
||||
theme_config_block_type_name=[
|
||||
ButtonThemeConfigBlockType.type,
|
||||
LinkThemeConfigBlockType.type,
|
||||
],
|
||||
serializer_kwargs={"required": False},
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return overrides
|
||||
|
||||
def get_pytest_params(self, pytest_data_fixture):
|
||||
|
@ -753,6 +838,12 @@ class ImageElementType(ElementType):
|
|||
@property
|
||||
def serializer_field_overrides(self):
|
||||
from baserow.api.user_files.serializers import UserFileSerializer
|
||||
from baserow.contrib.builder.api.theme.serializers import (
|
||||
DynamicConfigBlockSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.theme.theme_config_block_types import (
|
||||
ImageThemeConfigBlockType,
|
||||
)
|
||||
from baserow.core.formula.serializers import FormulaSerializerField
|
||||
|
||||
overrides = {
|
||||
|
@ -769,6 +860,12 @@ class ImageElementType(ElementType):
|
|||
allow_blank=True,
|
||||
default="",
|
||||
),
|
||||
"styles": DynamicConfigBlockSerializer(
|
||||
required=False,
|
||||
property_name="image",
|
||||
theme_config_block_type_name=ImageThemeConfigBlockType.type,
|
||||
serializer_kwargs={"required": False},
|
||||
),
|
||||
}
|
||||
|
||||
overrides.update(super().serializer_field_overrides)
|
||||
|
@ -777,7 +874,13 @@ class ImageElementType(ElementType):
|
|||
@property
|
||||
def request_serializer_field_overrides(self):
|
||||
from baserow.api.user_files.serializers import UserFileField
|
||||
from baserow.contrib.builder.api.theme.serializers import (
|
||||
DynamicConfigBlockSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.api.validators import image_file_validation
|
||||
from baserow.contrib.builder.theme.theme_config_block_types import (
|
||||
ImageThemeConfigBlockType,
|
||||
)
|
||||
|
||||
overrides = {
|
||||
"image_file": UserFileField(
|
||||
|
@ -798,6 +901,12 @@ class ImageElementType(ElementType):
|
|||
default=ImageElement._meta.get_field("style_max_width").default,
|
||||
help_text=ImageElement._meta.get_field("style_max_width").help_text,
|
||||
),
|
||||
"styles": DynamicConfigBlockSerializer(
|
||||
required=False,
|
||||
property_name="image",
|
||||
theme_config_block_type_name=ImageThemeConfigBlockType.type,
|
||||
serializer_kwargs={"required": False},
|
||||
),
|
||||
}
|
||||
if super().request_serializer_field_overrides is not None:
|
||||
overrides.update(super().request_serializer_field_overrides)
|
||||
|
@ -1014,6 +1123,12 @@ class ButtonElementType(ElementType):
|
|||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
from baserow.contrib.builder.api.theme.serializers import (
|
||||
DynamicConfigBlockSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.theme.theme_config_block_types import (
|
||||
ButtonThemeConfigBlockType,
|
||||
)
|
||||
from baserow.core.formula.serializers import FormulaSerializerField
|
||||
|
||||
overrides = {
|
||||
|
@ -1039,6 +1154,12 @@ class ButtonElementType(ElementType):
|
|||
default="primary",
|
||||
help_text="Button color.",
|
||||
),
|
||||
"styles": DynamicConfigBlockSerializer(
|
||||
required=False,
|
||||
property_name="button",
|
||||
theme_config_block_type_name=ButtonThemeConfigBlockType.type,
|
||||
serializer_kwargs={"required": False},
|
||||
),
|
||||
}
|
||||
|
||||
return overrides
|
||||
|
|
|
@ -35,22 +35,28 @@ class ElementHandler:
|
|||
allowed_fields_create = [
|
||||
"parent_element_id",
|
||||
"place_in_container",
|
||||
"styles",
|
||||
"visibility",
|
||||
"styles",
|
||||
"style_border_top_color",
|
||||
"style_border_top_size",
|
||||
"style_padding_top",
|
||||
"style_margin_top",
|
||||
"style_border_bottom_color",
|
||||
"style_border_bottom_size",
|
||||
"style_padding_bottom",
|
||||
"style_margin_bottom",
|
||||
"style_border_left_color",
|
||||
"style_border_left_size",
|
||||
"style_padding_left",
|
||||
"style_margin_left",
|
||||
"style_border_right_color",
|
||||
"style_border_right_size",
|
||||
"style_padding_right",
|
||||
"style_margin_right",
|
||||
"style_background",
|
||||
"style_background_color",
|
||||
"style_background_file",
|
||||
"style_background_mode",
|
||||
"style_width",
|
||||
]
|
||||
|
||||
|
@ -62,17 +68,23 @@ class ElementHandler:
|
|||
"style_border_top_color",
|
||||
"style_border_top_size",
|
||||
"style_padding_top",
|
||||
"style_margin_top",
|
||||
"style_border_bottom_color",
|
||||
"style_border_bottom_size",
|
||||
"style_padding_bottom",
|
||||
"style_margin_bottom",
|
||||
"style_border_left_color",
|
||||
"style_border_left_size",
|
||||
"style_padding_left",
|
||||
"style_margin_left",
|
||||
"style_border_right_color",
|
||||
"style_border_right_size",
|
||||
"style_padding_right",
|
||||
"style_margin_right",
|
||||
"style_background",
|
||||
"style_background_color",
|
||||
"style_background_file",
|
||||
"style_background_mode",
|
||||
"style_width",
|
||||
"role_type",
|
||||
"roles",
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.db import models
|
|||
from django.db.models import SET_NULL, QuerySet
|
||||
|
||||
from baserow.contrib.builder.constants import (
|
||||
BACKGROUND_IMAGE_MODES,
|
||||
WIDTHS,
|
||||
HorizontalAlignments,
|
||||
VerticalAlignments,
|
||||
|
@ -29,10 +30,12 @@ if TYPE_CHECKING:
|
|||
class BackgroundTypes(models.TextChoices):
|
||||
NONE = "none"
|
||||
COLOR = "color"
|
||||
IMAGE = "image"
|
||||
|
||||
|
||||
class WidthTypes(models.TextChoices):
|
||||
FULL = "full"
|
||||
FULL_WIDTH = "full-width"
|
||||
NORMAL = "normal"
|
||||
MEDIUM = "medium"
|
||||
SMALL = "small"
|
||||
|
@ -155,6 +158,11 @@ class Element(
|
|||
style_padding_top = models.PositiveIntegerField(
|
||||
default=10, help_text="Padding size of the top border."
|
||||
)
|
||||
style_margin_top = models.PositiveIntegerField(
|
||||
default=0,
|
||||
help_text="Margin size of the top border.",
|
||||
null=True, # TODO zdm remove me after v1.26
|
||||
)
|
||||
|
||||
style_border_bottom_color = models.CharField(
|
||||
max_length=20,
|
||||
|
@ -168,6 +176,11 @@ class Element(
|
|||
style_padding_bottom = models.PositiveIntegerField(
|
||||
default=10, help_text="Padding size of the bottom border."
|
||||
)
|
||||
style_margin_bottom = models.PositiveIntegerField(
|
||||
default=0,
|
||||
help_text="Margin size of the bottom border.",
|
||||
null=True, # TODO zdm remove me after v1.26
|
||||
)
|
||||
|
||||
style_border_left_color = models.CharField(
|
||||
max_length=20,
|
||||
|
@ -181,6 +194,11 @@ class Element(
|
|||
style_padding_left = models.PositiveIntegerField(
|
||||
default=20, help_text="Padding size of the left border."
|
||||
)
|
||||
style_margin_left = models.PositiveIntegerField(
|
||||
default=0,
|
||||
help_text="Margin size of the left border.",
|
||||
null=True, # TODO zdm remove me after v1.26
|
||||
)
|
||||
|
||||
style_border_right_color = models.CharField(
|
||||
max_length=20,
|
||||
|
@ -194,6 +212,11 @@ class Element(
|
|||
style_padding_right = models.PositiveIntegerField(
|
||||
default=20, help_text="Padding size of the right border."
|
||||
)
|
||||
style_margin_right = models.PositiveIntegerField(
|
||||
default=0,
|
||||
help_text="Margin size of the right border.",
|
||||
null=True, # TODO zdm remove me after v1.26
|
||||
)
|
||||
|
||||
style_background = models.CharField(
|
||||
choices=BackgroundTypes.choices,
|
||||
|
@ -208,6 +231,22 @@ class Element(
|
|||
help_text="The background color if `style_background` is color.",
|
||||
)
|
||||
|
||||
style_background_file = models.ForeignKey(
|
||||
UserFile,
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="element_background_image_file",
|
||||
help_text="An image file uploaded by the user to be used as element background",
|
||||
)
|
||||
|
||||
style_background_mode = models.CharField(
|
||||
help_text="The mode of the background image",
|
||||
choices=BACKGROUND_IMAGE_MODES.choices,
|
||||
max_length=32,
|
||||
default=BACKGROUND_IMAGE_MODES.FILL,
|
||||
null=True, # TODO zdm remove me after v1.26
|
||||
)
|
||||
|
||||
style_width = models.CharField(
|
||||
choices=WidthTypes.choices,
|
||||
default=WidthTypes.NORMAL,
|
||||
|
|
|
@ -18,6 +18,7 @@ from baserow.core.registry import (
|
|||
ModelRegistryMixin,
|
||||
Registry,
|
||||
)
|
||||
from baserow.core.user_files.handler import UserFileHandler
|
||||
from baserow.core.user_sources.constants import DEFAULT_USER_ROLE_PREFIX
|
||||
from baserow.core.user_sources.handler import UserSourceHandler
|
||||
|
||||
|
@ -233,6 +234,14 @@ class ElementType(
|
|||
if prop_name == "order":
|
||||
return str(element.order)
|
||||
|
||||
if prop_name == "style_background_file_id":
|
||||
return UserFileHandler().export_user_file(
|
||||
element.style_background_file,
|
||||
files_zip=files_zip,
|
||||
storage=storage,
|
||||
cache=cache,
|
||||
)
|
||||
|
||||
return super().serialize_property(
|
||||
element, prop_name, files_zip=files_zip, storage=storage, cache=cache
|
||||
)
|
||||
|
@ -268,6 +277,14 @@ class ElementType(
|
|||
value,
|
||||
)
|
||||
|
||||
if prop_name == "style_background_file_id":
|
||||
user_file = UserFileHandler().import_user_file(
|
||||
value, files_zip=files_zip, storage=storage
|
||||
)
|
||||
if user_file:
|
||||
return user_file.id
|
||||
return None
|
||||
|
||||
return value
|
||||
|
||||
@abstractmethod
|
||||
|
|
|
@ -0,0 +1,299 @@
|
|||
# Generated by Django 4.2.13 on 2024-07-02 16:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import baserow.core.fields
|
||||
|
||||
|
||||
def migrate_element_styles(apps, schema_editor):
|
||||
"""
|
||||
Migrates on model element styles into the style property.
|
||||
"""
|
||||
|
||||
Element = apps.get_model("builder", "element")
|
||||
|
||||
# Set default values for element styles
|
||||
Element.objects.all().update(
|
||||
style_margin_left=0,
|
||||
style_margin_right=0,
|
||||
style_margin_top=0,
|
||||
style_margin_bottom=0,
|
||||
style_background_mode="fill",
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("core", "0088_remove_blacklistedtoken_user"),
|
||||
("builder", "0026_add_more_style_properties"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="buttonthemeconfigblock",
|
||||
name="button_border_color",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
default="border",
|
||||
help_text="The border color of buttons",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="buttonthemeconfigblock",
|
||||
name="button_border_radius",
|
||||
field=models.SmallIntegerField(default=4, help_text="Button border radius"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="buttonthemeconfigblock",
|
||||
name="button_border_size",
|
||||
field=models.SmallIntegerField(default=0, help_text="Button border size"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="buttonthemeconfigblock",
|
||||
name="button_font_family",
|
||||
field=models.CharField(default="inter", max_length=250),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="buttonthemeconfigblock",
|
||||
name="button_font_size",
|
||||
field=models.SmallIntegerField(default=13),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="buttonthemeconfigblock",
|
||||
name="button_horizontal_padding",
|
||||
field=models.SmallIntegerField(
|
||||
default=12, help_text="Button horizontal padding"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="buttonthemeconfigblock",
|
||||
name="button_hover_border_color",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
default="border",
|
||||
help_text="The border color of buttons when hovered",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="buttonthemeconfigblock",
|
||||
name="button_hover_text_color",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
default="#ffffffff",
|
||||
help_text="The text color of buttons when hovered",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="buttonthemeconfigblock",
|
||||
name="button_text_color",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
default="#ffffffff",
|
||||
help_text="The text color of buttons",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="buttonthemeconfigblock",
|
||||
name="button_vertical_padding",
|
||||
field=models.SmallIntegerField(
|
||||
default=12, help_text="Button vertical padding"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="colorthemeconfigblock",
|
||||
name="main_error_color",
|
||||
field=models.CharField(default="#FF5A4A", max_length=9),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="colorthemeconfigblock",
|
||||
name="main_success_color",
|
||||
field=models.CharField(default="#12D452", max_length=9),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="colorthemeconfigblock",
|
||||
name="main_warning_color",
|
||||
field=models.CharField(default="#FCC74A", max_length=9),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="element",
|
||||
name="style_background_file",
|
||||
field=models.ForeignKey(
|
||||
help_text="An image file uploaded by the user to be used as element background",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="element_background_image_file",
|
||||
to="core.userfile",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="element",
|
||||
name="style_background_mode",
|
||||
field=models.CharField(
|
||||
choices=[("tile", "Tile"), ("fill", "Fill"), ("fit", "Fit")],
|
||||
default="fill",
|
||||
help_text="The mode of the background image",
|
||||
max_length=32,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="element",
|
||||
name="style_margin_bottom",
|
||||
field=models.PositiveIntegerField(
|
||||
default=0, help_text="Margin size of the bottom border.", null=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="element",
|
||||
name="style_margin_left",
|
||||
field=models.PositiveIntegerField(
|
||||
default=0, help_text="Margin size of the left border.", null=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="element",
|
||||
name="style_margin_right",
|
||||
field=models.PositiveIntegerField(
|
||||
default=0, help_text="Margin size of the right border.", null=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="element",
|
||||
name="style_margin_top",
|
||||
field=models.PositiveIntegerField(
|
||||
default=0, help_text="Margin size of the top border.", null=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="linkthemeconfigblock",
|
||||
name="link_font_family",
|
||||
field=models.CharField(default="inter", max_length=250),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="linkthemeconfigblock",
|
||||
name="link_font_size",
|
||||
field=models.SmallIntegerField(default=13),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="typographythemeconfigblock",
|
||||
name="body_font_family",
|
||||
field=models.CharField(default="inter", max_length=250),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="typographythemeconfigblock",
|
||||
name="heading_1_font_family",
|
||||
field=models.CharField(default="inter", max_length=250),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="typographythemeconfigblock",
|
||||
name="heading_2_font_family",
|
||||
field=models.CharField(default="inter", max_length=250),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="typographythemeconfigblock",
|
||||
name="heading_3_font_family",
|
||||
field=models.CharField(default="inter", max_length=250),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="typographythemeconfigblock",
|
||||
name="heading_4_font_family",
|
||||
field=models.CharField(default="inter", max_length=250),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="typographythemeconfigblock",
|
||||
name="heading_5_font_family",
|
||||
field=models.CharField(default="inter", max_length=250),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="typographythemeconfigblock",
|
||||
name="heading_6_font_family",
|
||||
field=models.CharField(default="inter", max_length=250),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="element",
|
||||
name="style_background",
|
||||
field=models.CharField(
|
||||
choices=[("none", "None"), ("color", "Color"), ("image", "Image")],
|
||||
default="none",
|
||||
help_text="What type of background the element should have.",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="element",
|
||||
name="style_width",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("full", "Full"),
|
||||
("full-width", "Full Width"),
|
||||
("normal", "Normal"),
|
||||
("medium", "Medium"),
|
||||
("small", "Small"),
|
||||
],
|
||||
default="normal",
|
||||
help_text="Indicates the width of the element.",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PageThemeConfigBlock",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"page_background_color",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
default="#ffffffff",
|
||||
help_text="The background color of the page",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
(
|
||||
"page_background_mode",
|
||||
models.CharField(
|
||||
choices=[("tile", "Tile"), ("fill", "Fill"), ("fit", "Fit")],
|
||||
default="tile",
|
||||
help_text="The mode of the background image",
|
||||
max_length=32,
|
||||
),
|
||||
),
|
||||
(
|
||||
"builder",
|
||||
baserow.core.fields.AutoOneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="%(class)s",
|
||||
to="builder.builder",
|
||||
),
|
||||
),
|
||||
(
|
||||
"page_background_file",
|
||||
models.ForeignKey(
|
||||
help_text="An image file uploaded by the user to be used as page background",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="page_background_image_file",
|
||||
to="core.userfile",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
migrate_element_styles, reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
|
@ -1,8 +1,13 @@
|
|||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
|
||||
from baserow.contrib.builder.constants import WIDTHS, HorizontalAlignments
|
||||
from baserow.contrib.builder.constants import (
|
||||
BACKGROUND_IMAGE_MODES,
|
||||
WIDTHS,
|
||||
HorizontalAlignments,
|
||||
)
|
||||
from baserow.core.fields import AutoOneToOneField
|
||||
from baserow.core.user_files.models import UserFile
|
||||
|
||||
|
||||
class ThemeConfigBlock(models.Model):
|
||||
|
@ -33,9 +38,16 @@ class ColorThemeConfigBlock(ThemeConfigBlock):
|
|||
primary_color = models.CharField(max_length=9, default="#5190efff")
|
||||
secondary_color = models.CharField(max_length=9, default="#0eaa42ff")
|
||||
border_color = models.CharField(max_length=9, default="#d7d8d9ff")
|
||||
main_success_color = models.CharField(max_length=9, default="#12D452")
|
||||
main_warning_color = models.CharField(max_length=9, default="#FCC74A")
|
||||
main_error_color = models.CharField(max_length=9, default="#FF5A4A")
|
||||
|
||||
|
||||
class TypographyThemeConfigBlock(ThemeConfigBlock):
|
||||
body_font_family = models.CharField(
|
||||
max_length=250,
|
||||
default="inter",
|
||||
)
|
||||
body_font_size = models.SmallIntegerField(default=14)
|
||||
body_text_color = models.CharField(max_length=9, default="#070810ff")
|
||||
body_text_alignment = models.CharField(
|
||||
|
@ -43,6 +55,10 @@ class TypographyThemeConfigBlock(ThemeConfigBlock):
|
|||
max_length=10,
|
||||
default=HorizontalAlignments.LEFT,
|
||||
)
|
||||
heading_1_font_family = models.CharField(
|
||||
max_length=250,
|
||||
default="inter",
|
||||
)
|
||||
heading_1_font_size = models.SmallIntegerField(default=24)
|
||||
heading_1_text_color = models.CharField(max_length=9, default="#070810ff")
|
||||
heading_1_text_alignment = models.CharField(
|
||||
|
@ -50,6 +66,10 @@ class TypographyThemeConfigBlock(ThemeConfigBlock):
|
|||
max_length=10,
|
||||
default=HorizontalAlignments.LEFT,
|
||||
)
|
||||
heading_2_font_family = models.CharField(
|
||||
max_length=250,
|
||||
default="inter",
|
||||
)
|
||||
heading_2_font_size = models.SmallIntegerField(default=20)
|
||||
heading_2_text_color = models.CharField(max_length=9, default="#070810ff")
|
||||
heading_2_text_alignment = models.CharField(
|
||||
|
@ -57,6 +77,10 @@ class TypographyThemeConfigBlock(ThemeConfigBlock):
|
|||
max_length=10,
|
||||
default=HorizontalAlignments.LEFT,
|
||||
)
|
||||
heading_3_font_family = models.CharField(
|
||||
max_length=250,
|
||||
default="inter",
|
||||
)
|
||||
heading_3_font_size = models.SmallIntegerField(default=16)
|
||||
heading_3_text_color = models.CharField(max_length=9, default="#070810ff")
|
||||
heading_3_text_alignment = models.CharField(
|
||||
|
@ -64,6 +88,10 @@ class TypographyThemeConfigBlock(ThemeConfigBlock):
|
|||
max_length=10,
|
||||
default=HorizontalAlignments.LEFT,
|
||||
)
|
||||
heading_4_font_family = models.CharField(
|
||||
max_length=250,
|
||||
default="inter",
|
||||
)
|
||||
heading_4_font_size = models.SmallIntegerField(default=16)
|
||||
heading_4_text_color = models.CharField(max_length=9, default="#070810ff")
|
||||
heading_4_text_alignment = models.CharField(
|
||||
|
@ -71,6 +99,10 @@ class TypographyThemeConfigBlock(ThemeConfigBlock):
|
|||
max_length=10,
|
||||
default=HorizontalAlignments.LEFT,
|
||||
)
|
||||
heading_5_font_family = models.CharField(
|
||||
max_length=250,
|
||||
default="inter",
|
||||
)
|
||||
heading_5_font_size = models.SmallIntegerField(default=14)
|
||||
heading_5_text_color = models.CharField(max_length=9, default="#070810ff")
|
||||
heading_5_text_alignment = models.CharField(
|
||||
|
@ -78,6 +110,10 @@ class TypographyThemeConfigBlock(ThemeConfigBlock):
|
|||
max_length=10,
|
||||
default=HorizontalAlignments.LEFT,
|
||||
)
|
||||
heading_6_font_family = models.CharField(
|
||||
max_length=250,
|
||||
default="inter",
|
||||
)
|
||||
heading_6_font_size = models.SmallIntegerField(default=14)
|
||||
heading_6_text_color = models.CharField(max_length=9, default="#202128")
|
||||
heading_6_text_alignment = models.CharField(
|
||||
|
@ -88,6 +124,11 @@ class TypographyThemeConfigBlock(ThemeConfigBlock):
|
|||
|
||||
|
||||
class ButtonThemeConfigBlock(ThemeConfigBlock):
|
||||
button_font_family = models.CharField(
|
||||
max_length=250,
|
||||
default="inter",
|
||||
)
|
||||
button_font_size = models.SmallIntegerField(default=13)
|
||||
button_alignment = models.CharField(
|
||||
choices=HorizontalAlignments.choices,
|
||||
max_length=10,
|
||||
|
@ -109,15 +150,56 @@ class ButtonThemeConfigBlock(ThemeConfigBlock):
|
|||
blank=True,
|
||||
help_text="The background color of buttons",
|
||||
)
|
||||
button_text_color = models.CharField(
|
||||
max_length=20,
|
||||
default="#ffffffff",
|
||||
blank=True,
|
||||
help_text="The text color of buttons",
|
||||
)
|
||||
button_border_color = models.CharField(
|
||||
max_length=20,
|
||||
default="border",
|
||||
blank=True,
|
||||
help_text="The border color of buttons",
|
||||
)
|
||||
button_border_size = models.SmallIntegerField(
|
||||
default=0, help_text="Button border size"
|
||||
)
|
||||
button_border_radius = models.SmallIntegerField(
|
||||
default=4, help_text="Button border radius"
|
||||
)
|
||||
button_vertical_padding = models.SmallIntegerField(
|
||||
default=12, help_text="Button vertical padding"
|
||||
)
|
||||
button_horizontal_padding = models.SmallIntegerField(
|
||||
default=12, help_text="Button horizontal padding"
|
||||
)
|
||||
button_hover_background_color = models.CharField(
|
||||
max_length=20,
|
||||
default="#96baf6ff",
|
||||
blank=True,
|
||||
help_text="The background color of buttons when hovered",
|
||||
)
|
||||
button_hover_text_color = models.CharField(
|
||||
max_length=20,
|
||||
default="#ffffffff",
|
||||
blank=True,
|
||||
help_text="The text color of buttons when hovered",
|
||||
)
|
||||
button_hover_border_color = models.CharField(
|
||||
max_length=20,
|
||||
default="border",
|
||||
blank=True,
|
||||
help_text="The border color of buttons when hovered",
|
||||
)
|
||||
|
||||
|
||||
class LinkThemeConfigBlock(ThemeConfigBlock):
|
||||
link_font_family = models.CharField(
|
||||
max_length=250,
|
||||
default="inter",
|
||||
)
|
||||
link_font_size = models.SmallIntegerField(default=13)
|
||||
link_text_alignment = models.CharField(
|
||||
choices=HorizontalAlignments.choices,
|
||||
max_length=10,
|
||||
|
@ -173,3 +255,31 @@ class ImageThemeConfigBlock(ThemeConfigBlock):
|
|||
max_length=32,
|
||||
default=IMAGE_CONSTRAINT_TYPES.CONTAIN,
|
||||
)
|
||||
|
||||
|
||||
class PageThemeConfigBlock(ThemeConfigBlock):
|
||||
"""
|
||||
Theme for pages.
|
||||
"""
|
||||
|
||||
page_background_color = models.CharField(
|
||||
max_length=20,
|
||||
default="#ffffffff",
|
||||
blank=True,
|
||||
help_text="The background color of the page",
|
||||
)
|
||||
|
||||
page_background_file = models.ForeignKey(
|
||||
UserFile,
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="page_background_image_file",
|
||||
help_text="An image file uploaded by the user to be used as page background",
|
||||
)
|
||||
|
||||
page_background_mode = models.CharField(
|
||||
help_text="The mode of the background image",
|
||||
choices=BACKGROUND_IMAGE_MODES.choices,
|
||||
max_length=32,
|
||||
default=BACKGROUND_IMAGE_MODES.TILE,
|
||||
)
|
||||
|
|
|
@ -6,19 +6,18 @@ from django.db.models import QuerySet
|
|||
from baserow.core.registry import (
|
||||
CustomFieldsInstanceMixin,
|
||||
CustomFieldsRegistryMixin,
|
||||
ImportExportMixin,
|
||||
EasyImportExportMixin,
|
||||
Instance,
|
||||
Registry,
|
||||
)
|
||||
from baserow.core.utils import extract_allowed
|
||||
|
||||
from .models import ThemeConfigBlock
|
||||
from .types import ThemeConfigBlockSubClass
|
||||
|
||||
|
||||
class ThemeConfigBlockType(
|
||||
Instance,
|
||||
ImportExportMixin[ThemeConfigBlock],
|
||||
EasyImportExportMixin,
|
||||
CustomFieldsInstanceMixin,
|
||||
ABC,
|
||||
):
|
||||
|
@ -33,6 +32,31 @@ class ThemeConfigBlockType(
|
|||
polymorphic.
|
||||
"""
|
||||
|
||||
parent_property_name = "builder"
|
||||
|
||||
def get_property_names(self):
|
||||
"""
|
||||
We want all properties here to make it easier.
|
||||
"""
|
||||
|
||||
return [
|
||||
f.name
|
||||
for f in self.model_class._meta.get_fields()
|
||||
if f.name not in ["builder", "id"]
|
||||
]
|
||||
|
||||
@property
|
||||
def allowed_fields(self):
|
||||
return [
|
||||
f.name
|
||||
for f in self.model_class._meta.get_fields()
|
||||
if f.name not in ["id", "builder"]
|
||||
]
|
||||
|
||||
@property
|
||||
def serializer_field_names(self):
|
||||
return self.allowed_fields
|
||||
|
||||
@property
|
||||
def related_name_in_builder_model(self) -> str:
|
||||
"""
|
||||
|
@ -42,15 +66,6 @@ class ThemeConfigBlockType(
|
|||
|
||||
return self.model_class.__name__.lower()
|
||||
|
||||
def export_serialized(self, instance):
|
||||
return {field: getattr(instance, field) for field in self.allowed_fields}
|
||||
|
||||
def import_serialized(self, parent, serialized_values, id_mapping):
|
||||
allowed_values = extract_allowed(serialized_values, self.allowed_fields)
|
||||
theme_config_block = self.model_class(builder=parent, **allowed_values)
|
||||
theme_config_block.save()
|
||||
return theme_config_block
|
||||
|
||||
def update_properties(
|
||||
self, builder, **kwargs: dict
|
||||
) -> Type[ThemeConfigBlockSubClass]:
|
||||
|
@ -64,10 +79,14 @@ class ThemeConfigBlockType(
|
|||
|
||||
instance = getattr(builder, self.related_name_in_builder_model)
|
||||
allowed_values = extract_allowed(kwargs, self.allowed_fields)
|
||||
|
||||
for key, value in allowed_values.items():
|
||||
setattr(instance, key, value)
|
||||
|
||||
instance.save()
|
||||
|
||||
setattr(builder, self.related_name_in_builder_model, instance)
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
from baserow.core.user_files.handler import UserFileHandler
|
||||
|
||||
from .models import (
|
||||
ButtonThemeConfigBlock,
|
||||
ColorThemeConfigBlock,
|
||||
ImageThemeConfigBlock,
|
||||
LinkThemeConfigBlock,
|
||||
PageThemeConfigBlock,
|
||||
ThemeConfigBlock,
|
||||
TypographyThemeConfigBlock,
|
||||
)
|
||||
from .registries import ThemeConfigBlockType
|
||||
|
@ -11,67 +15,11 @@ from .registries import ThemeConfigBlockType
|
|||
class ColorThemeConfigBlockType(ThemeConfigBlockType):
|
||||
type = "color"
|
||||
model_class = ColorThemeConfigBlock
|
||||
allowed_fields = [
|
||||
"primary_color",
|
||||
"secondary_color",
|
||||
"border_color",
|
||||
]
|
||||
serializer_field_names = [
|
||||
"primary_color",
|
||||
"secondary_color",
|
||||
"border_color",
|
||||
]
|
||||
|
||||
|
||||
class TypographyThemeConfigBlockType(ThemeConfigBlockType):
|
||||
type = "typography"
|
||||
model_class = TypographyThemeConfigBlock
|
||||
allowed_fields = [
|
||||
"body_font_size",
|
||||
"body_text_color",
|
||||
"body_text_alignment",
|
||||
"heading_1_font_size",
|
||||
"heading_1_text_color",
|
||||
"heading_1_text_alignment",
|
||||
"heading_2_font_size",
|
||||
"heading_2_text_color",
|
||||
"heading_2_text_alignment",
|
||||
"heading_3_font_size",
|
||||
"heading_3_text_color",
|
||||
"heading_3_text_alignment",
|
||||
"heading_4_font_size",
|
||||
"heading_4_text_color",
|
||||
"heading_4_text_alignment",
|
||||
"heading_5_font_size",
|
||||
"heading_5_text_color",
|
||||
"heading_5_text_alignment",
|
||||
"heading_6_font_size",
|
||||
"heading_6_text_color",
|
||||
"heading_6_text_alignment",
|
||||
]
|
||||
serializer_field_names = [
|
||||
"body_font_size",
|
||||
"body_text_color",
|
||||
"body_text_alignment",
|
||||
"heading_1_font_size",
|
||||
"heading_1_text_color",
|
||||
"heading_1_text_alignment",
|
||||
"heading_2_font_size",
|
||||
"heading_2_text_color",
|
||||
"heading_2_text_alignment",
|
||||
"heading_3_font_size",
|
||||
"heading_3_text_color",
|
||||
"heading_3_text_alignment",
|
||||
"heading_4_font_size",
|
||||
"heading_4_text_color",
|
||||
"heading_4_text_alignment",
|
||||
"heading_5_font_size",
|
||||
"heading_5_text_color",
|
||||
"heading_5_text_alignment",
|
||||
"heading_6_font_size",
|
||||
"heading_6_text_color",
|
||||
"heading_6_text_alignment",
|
||||
]
|
||||
|
||||
def import_serialized(self, parent, serialized_values, id_mapping):
|
||||
# Translate from old color property names to new names for compat with templates
|
||||
|
@ -87,49 +35,91 @@ class TypographyThemeConfigBlockType(ThemeConfigBlockType):
|
|||
class ButtonThemeConfigBlockType(ThemeConfigBlockType):
|
||||
type = "button"
|
||||
model_class = ButtonThemeConfigBlock
|
||||
allowed_fields = [
|
||||
"button_background_color",
|
||||
"button_hover_background_color",
|
||||
"button_text_alignment",
|
||||
"button_alignment",
|
||||
"button_width",
|
||||
]
|
||||
serializer_field_names = [
|
||||
"button_background_color",
|
||||
"button_hover_background_color",
|
||||
"button_text_alignment",
|
||||
"button_alignment",
|
||||
"button_width",
|
||||
]
|
||||
|
||||
|
||||
class LinkThemeConfigBlockType(ThemeConfigBlockType):
|
||||
type = "link"
|
||||
model_class = LinkThemeConfigBlock
|
||||
allowed_fields = [
|
||||
"link_text_alignment",
|
||||
"link_text_color",
|
||||
"link_hover_text_color",
|
||||
]
|
||||
serializer_field_names = [
|
||||
"link_text_alignment",
|
||||
"link_text_color",
|
||||
"link_hover_text_color",
|
||||
]
|
||||
|
||||
|
||||
class ImageThemeConfigBlockType(ThemeConfigBlockType):
|
||||
type = "image"
|
||||
model_class = ImageThemeConfigBlock
|
||||
allowed_fields = [
|
||||
"image_alignment",
|
||||
"image_max_width",
|
||||
"image_max_height",
|
||||
"image_constraint",
|
||||
]
|
||||
serializer_field_names = [
|
||||
"image_alignment",
|
||||
"image_max_width",
|
||||
"image_max_height",
|
||||
"image_constraint",
|
||||
]
|
||||
|
||||
|
||||
class PageThemeConfigBlockType(ThemeConfigBlockType):
|
||||
type = "page"
|
||||
model_class = PageThemeConfigBlock
|
||||
|
||||
def get_property_names(self):
|
||||
"""
|
||||
Let's replace the page_background_file property with page_background_file_id.
|
||||
"""
|
||||
|
||||
return [
|
||||
n if n != "page_background_file" else "page_background_file_id"
|
||||
for n in super().get_property_names()
|
||||
]
|
||||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
from baserow.api.user_files.serializers import UserFileField
|
||||
from baserow.contrib.builder.api.validators import image_file_validation
|
||||
|
||||
return {
|
||||
"page_background_file": UserFileField(
|
||||
allow_null=True,
|
||||
required=False,
|
||||
help_text="The image file",
|
||||
validators=[image_file_validation],
|
||||
),
|
||||
}
|
||||
|
||||
def serialize_property(
|
||||
self,
|
||||
theme_config_block: ThemeConfigBlock,
|
||||
prop_name: str,
|
||||
files_zip=None,
|
||||
storage=None,
|
||||
cache=None,
|
||||
):
|
||||
"""
|
||||
You can customize the behavior of the serialization of a property with this
|
||||
hook.
|
||||
"""
|
||||
|
||||
if prop_name == "page_background_file_id":
|
||||
return UserFileHandler().export_user_file(
|
||||
theme_config_block.page_background_file,
|
||||
files_zip=files_zip,
|
||||
storage=storage,
|
||||
cache=cache,
|
||||
)
|
||||
|
||||
return super().serialize_property(
|
||||
theme_config_block,
|
||||
prop_name,
|
||||
files_zip=files_zip,
|
||||
storage=storage,
|
||||
cache=cache,
|
||||
)
|
||||
|
||||
def deserialize_property(
|
||||
self,
|
||||
prop_name: str,
|
||||
value,
|
||||
id_mapping,
|
||||
files_zip=None,
|
||||
storage=None,
|
||||
cache=None,
|
||||
**kwargs,
|
||||
):
|
||||
if prop_name == "page_background_file_id":
|
||||
user_file = UserFileHandler().import_user_file(
|
||||
value, files_zip=files_zip, storage=storage
|
||||
)
|
||||
if user_file:
|
||||
return user_file.id
|
||||
return None
|
||||
|
||||
return value
|
||||
|
|
|
@ -20,17 +20,23 @@ class ElementDict(TypedDict):
|
|||
style_border_top_color: str
|
||||
style_border_top_size: int
|
||||
style_padding_top: int
|
||||
style_margin_top: int
|
||||
style_border_bottom_color: str
|
||||
style_border_bottom_size: int
|
||||
style_padding_bottom: int
|
||||
style_margin_bottom: int
|
||||
style_border_left_color: str
|
||||
style_border_left_size: int
|
||||
style_padding_left: int
|
||||
style_margin_left: int
|
||||
style_border_right_color: str
|
||||
style_border_right_size: int
|
||||
style_padding_right: int
|
||||
style_margin_right: int
|
||||
style_background: str
|
||||
style_background_color: str
|
||||
style_background_file_id: str
|
||||
style_background_mode: str
|
||||
style_width: str
|
||||
|
||||
|
||||
|
|
|
@ -389,8 +389,9 @@ class EasyImportExportMixin(Generic[T], ABC):
|
|||
# The parent property name for the model
|
||||
parent_property_name: str
|
||||
|
||||
# The name of the id mapping used for import process
|
||||
id_mapping_name: str
|
||||
# The name of the id mapping used for import process. Let it None if you don't need
|
||||
# this feature.
|
||||
id_mapping_name: Optional[str] = None
|
||||
|
||||
# The model class to create
|
||||
model_class: Type[T]
|
||||
|
@ -417,6 +418,16 @@ class EasyImportExportMixin(Generic[T], ABC):
|
|||
|
||||
return getattr(instance, prop_name)
|
||||
|
||||
def get_property_names(self):
|
||||
"""
|
||||
Returns a list of properties to export/import for this type. By default it uses
|
||||
the SerializedDict properties.
|
||||
|
||||
:returns: a list of property names belonging to instances of this type.
|
||||
"""
|
||||
|
||||
return self.SerializedDict.__annotations__.keys()
|
||||
|
||||
def export_serialized(
|
||||
self,
|
||||
instance: T,
|
||||
|
@ -432,9 +443,7 @@ class EasyImportExportMixin(Generic[T], ABC):
|
|||
:return: The exported instance as serialized dict.
|
||||
"""
|
||||
|
||||
property_names = self.SerializedDict.__annotations__.keys()
|
||||
|
||||
serialized = self.SerializedDict(
|
||||
serialized = dict(
|
||||
**{
|
||||
key: self.serialize_property(
|
||||
instance,
|
||||
|
@ -443,7 +452,7 @@ class EasyImportExportMixin(Generic[T], ABC):
|
|||
storage=storage,
|
||||
cache=cache,
|
||||
)
|
||||
for key in property_names
|
||||
for key in self.get_property_names()
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -516,11 +525,11 @@ class EasyImportExportMixin(Generic[T], ABC):
|
|||
:return: The created instance.
|
||||
"""
|
||||
|
||||
if self.id_mapping_name not in id_mapping:
|
||||
if self.id_mapping_name and self.id_mapping_name not in id_mapping:
|
||||
id_mapping[self.id_mapping_name] = {}
|
||||
|
||||
deserialized_properties = {}
|
||||
for name in self.SerializedDict.__annotations__.keys():
|
||||
for name in self.get_property_names():
|
||||
if name in serialized_values and name != f"{self.parent_property_name}_id":
|
||||
deserialized_properties[name] = self.deserialize_property(
|
||||
name,
|
||||
|
@ -533,10 +542,11 @@ class EasyImportExportMixin(Generic[T], ABC):
|
|||
)
|
||||
|
||||
# Remove id key
|
||||
originale_instance_id = deserialized_properties.pop("id")
|
||||
originale_instance_id = deserialized_properties.pop("id", 0)
|
||||
|
||||
# Remove type
|
||||
deserialized_properties.pop("type")
|
||||
# Remove type if any
|
||||
if "type" in deserialized_properties:
|
||||
deserialized_properties.pop("type")
|
||||
|
||||
# Add the parent
|
||||
deserialized_properties[self.parent_property_name] = parent
|
||||
|
@ -550,8 +560,11 @@ class EasyImportExportMixin(Generic[T], ABC):
|
|||
**kwargs,
|
||||
)
|
||||
|
||||
# Add the created instance to the mapping
|
||||
id_mapping[self.id_mapping_name][originale_instance_id] = created_instance.id
|
||||
if self.id_mapping_name:
|
||||
# Add the created instance to the mapping
|
||||
id_mapping[self.id_mapping_name][
|
||||
originale_instance_id
|
||||
] = created_instance.id
|
||||
|
||||
return created_instance
|
||||
|
||||
|
|
|
@ -224,6 +224,25 @@ def test_update_element(api_client, data_fixture):
|
|||
assert response.json()["level"] == 3
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_element_styles(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
element1 = data_fixture.create_builder_heading_element(page=page)
|
||||
|
||||
url = reverse("api:builder:element:item", kwargs={"element_id": element1.id})
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{"styles": {"typography": {"heading_1_text_color": "#CCCCCCCC"}}},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response.json()["styles"] == {
|
||||
"typography": {"heading_1_text_color": "#CCCCCCCC"}
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_element_bad_request(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
|
@ -256,6 +275,35 @@ def test_update_element_does_not_exist(api_client, data_fixture):
|
|||
assert response.json()["error"] == "ERROR_ELEMENT_DOES_NOT_EXIST"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_element_bad_style_property(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
element1 = data_fixture.create_builder_heading_element(page=page)
|
||||
|
||||
url = reverse("api:builder:element:item", kwargs={"element_id": element1.id})
|
||||
|
||||
# Bad root property
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{"styles": {"typpography": {"heading_1_text_color": "#CCCCCCCC"}}},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response.json()["styles"] == {}
|
||||
|
||||
# Bad theme property
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{"styles": {"typography": {"heading_25_text_color": "#CCCCCCCC"}}},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response.json()["styles"] == {"typography": {}}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_move_element_empty_payload(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
|
|
|
@ -151,6 +151,11 @@ def test_get_builder_application(api_client, data_fixture):
|
|||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
|
||||
# Check we have the theme but don't want to check every single property
|
||||
assert response_json["theme"]["body_text_color"] == "#070810ff"
|
||||
del response_json["theme"]
|
||||
|
||||
assert response_json == {
|
||||
"favicon_file": UserFileSerializer(application.favicon_file).data,
|
||||
"id": application.id,
|
||||
|
@ -168,44 +173,6 @@ def test_get_builder_application(api_client, data_fixture):
|
|||
"generative_ai_models_enabled": {},
|
||||
},
|
||||
"pages": [],
|
||||
"theme": {
|
||||
"body_text_color": "#070810ff",
|
||||
"body_font_size": 14,
|
||||
"body_text_alignment": "left",
|
||||
"primary_color": "#5190efff",
|
||||
"secondary_color": "#0eaa42ff",
|
||||
"border_color": "#d7d8d9ff",
|
||||
"heading_1_font_size": 24,
|
||||
"heading_1_text_color": "#070810ff",
|
||||
"heading_1_text_alignment": "left",
|
||||
"heading_2_font_size": 20,
|
||||
"heading_2_text_color": "#070810ff",
|
||||
"heading_2_text_alignment": "left",
|
||||
"heading_3_font_size": 16,
|
||||
"heading_3_text_color": "#070810ff",
|
||||
"heading_3_text_alignment": "left",
|
||||
"heading_4_font_size": 16,
|
||||
"heading_4_text_color": "#070810ff",
|
||||
"heading_4_text_alignment": "left",
|
||||
"heading_5_font_size": 14,
|
||||
"heading_5_text_color": "#070810ff",
|
||||
"heading_5_text_alignment": "left",
|
||||
"heading_6_font_size": 14,
|
||||
"heading_6_text_color": "#202128",
|
||||
"heading_6_text_alignment": "left",
|
||||
"button_background_color": "primary",
|
||||
"button_hover_background_color": "#96baf6ff",
|
||||
"button_alignment": "left",
|
||||
"button_text_alignment": "center",
|
||||
"button_width": "auto",
|
||||
"image_alignment": "left",
|
||||
"image_constraint": "contain",
|
||||
"image_max_height": None,
|
||||
"image_max_width": 100,
|
||||
"link_text_alignment": "left",
|
||||
"link_text_color": "primary",
|
||||
"link_hover_text_color": "#96baf6ff",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -230,6 +197,11 @@ def test_list_builder_applications(api_client, data_fixture):
|
|||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
|
||||
# Check we have the theme but don't want to check every single property
|
||||
assert response_json[0]["theme"]["body_text_color"] == "#070810ff"
|
||||
del response_json[0]["theme"]
|
||||
|
||||
assert response_json == [
|
||||
{
|
||||
"favicon_file": UserFileSerializer(application.favicon_file).data,
|
||||
|
@ -248,43 +220,5 @@ def test_list_builder_applications(api_client, data_fixture):
|
|||
"generative_ai_models_enabled": {},
|
||||
},
|
||||
"pages": [],
|
||||
"theme": {
|
||||
"body_text_color": "#070810ff",
|
||||
"body_font_size": 14,
|
||||
"body_text_alignment": "left",
|
||||
"primary_color": "#5190efff",
|
||||
"secondary_color": "#0eaa42ff",
|
||||
"border_color": "#d7d8d9ff",
|
||||
"heading_1_font_size": 24,
|
||||
"heading_1_text_color": "#070810ff",
|
||||
"heading_1_text_alignment": "left",
|
||||
"heading_2_font_size": 20,
|
||||
"heading_2_text_color": "#070810ff",
|
||||
"heading_2_text_alignment": "left",
|
||||
"heading_3_font_size": 16,
|
||||
"heading_3_text_color": "#070810ff",
|
||||
"heading_3_text_alignment": "left",
|
||||
"heading_4_font_size": 16,
|
||||
"heading_4_text_color": "#070810ff",
|
||||
"heading_4_text_alignment": "left",
|
||||
"heading_5_font_size": 14,
|
||||
"heading_5_text_color": "#070810ff",
|
||||
"heading_5_text_alignment": "left",
|
||||
"heading_6_font_size": 14,
|
||||
"heading_6_text_color": "#202128",
|
||||
"heading_6_text_alignment": "left",
|
||||
"button_background_color": "primary",
|
||||
"button_hover_background_color": "#96baf6ff",
|
||||
"button_alignment": "left",
|
||||
"button_text_alignment": "center",
|
||||
"button_width": "auto",
|
||||
"image_alignment": "left",
|
||||
"image_constraint": "contain",
|
||||
"image_max_height": None,
|
||||
"image_max_width": 100,
|
||||
"link_text_alignment": "left",
|
||||
"link_text_color": "primary",
|
||||
"link_hover_text_color": "#96baf6ff",
|
||||
},
|
||||
}
|
||||
]
|
||||
|
|
|
@ -207,22 +207,28 @@ def test_builder_application_export(data_fixture):
|
|||
"place_in_container": None,
|
||||
"visibility": "all",
|
||||
"font_color": "default",
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"styles": {},
|
||||
"style_border_top_color": "border",
|
||||
"style_border_top_size": 0,
|
||||
"style_padding_top": 10,
|
||||
"style_margin_top": 0,
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"style_padding_bottom": 10,
|
||||
"style_margin_bottom": 0,
|
||||
"style_border_left_color": "border",
|
||||
"style_border_left_size": 0,
|
||||
"style_padding_left": 20,
|
||||
"style_margin_left": 0,
|
||||
"style_border_right_color": "border",
|
||||
"style_border_right_size": 0,
|
||||
"style_width": "normal",
|
||||
"style_padding_top": 10,
|
||||
"style_padding_bottom": 10,
|
||||
"style_padding_left": 20,
|
||||
"style_padding_right": 20,
|
||||
"style_margin_right": 0,
|
||||
"style_background": "none",
|
||||
"styles": {},
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_background_file_id": None,
|
||||
"style_background_mode": "fill",
|
||||
"style_width": "normal",
|
||||
"value": element1.value,
|
||||
"level": element1.level,
|
||||
"alignment": "left",
|
||||
|
@ -236,22 +242,28 @@ def test_builder_application_export(data_fixture):
|
|||
"parent_element_id": None,
|
||||
"place_in_container": None,
|
||||
"visibility": "all",
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"styles": {},
|
||||
"style_border_top_color": "border",
|
||||
"style_border_top_size": 0,
|
||||
"style_padding_top": 10,
|
||||
"style_margin_top": 0,
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"style_padding_bottom": 10,
|
||||
"style_margin_bottom": 0,
|
||||
"style_border_left_color": "border",
|
||||
"style_border_left_size": 0,
|
||||
"style_padding_left": 20,
|
||||
"style_margin_left": 0,
|
||||
"style_border_right_color": "border",
|
||||
"style_border_right_size": 0,
|
||||
"style_width": "normal",
|
||||
"style_padding_top": 10,
|
||||
"style_padding_bottom": 10,
|
||||
"style_padding_left": 20,
|
||||
"style_padding_right": 20,
|
||||
"style_margin_right": 0,
|
||||
"style_background": "none",
|
||||
"styles": {},
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_background_file_id": None,
|
||||
"style_background_mode": "fill",
|
||||
"style_width": "normal",
|
||||
"value": element2.value,
|
||||
"alignment": "left",
|
||||
"roles": [],
|
||||
|
@ -264,22 +276,28 @@ def test_builder_application_export(data_fixture):
|
|||
"parent_element_id": None,
|
||||
"place_in_container": None,
|
||||
"visibility": "all",
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"styles": {},
|
||||
"style_border_top_color": "border",
|
||||
"style_border_top_size": 0,
|
||||
"style_padding_top": 10,
|
||||
"style_margin_top": 0,
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"style_padding_bottom": 10,
|
||||
"style_margin_bottom": 0,
|
||||
"style_border_left_color": "border",
|
||||
"style_border_left_size": 0,
|
||||
"style_padding_left": 20,
|
||||
"style_margin_left": 0,
|
||||
"style_border_right_color": "border",
|
||||
"style_border_right_size": 0,
|
||||
"style_width": "normal",
|
||||
"style_padding_top": 10,
|
||||
"style_padding_bottom": 10,
|
||||
"style_padding_left": 20,
|
||||
"style_padding_right": 20,
|
||||
"style_margin_right": 0,
|
||||
"style_background": "none",
|
||||
"styles": {},
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_background_file_id": None,
|
||||
"style_background_mode": "fill",
|
||||
"style_width": "normal",
|
||||
"order": str(element_container.order),
|
||||
"column_amount": 3,
|
||||
"column_gap": 50,
|
||||
|
@ -293,22 +311,28 @@ def test_builder_application_export(data_fixture):
|
|||
"parent_element_id": element_container.id,
|
||||
"place_in_container": "0",
|
||||
"visibility": "all",
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"styles": {},
|
||||
"style_border_top_color": "border",
|
||||
"style_border_top_size": 0,
|
||||
"style_padding_top": 10,
|
||||
"style_margin_top": 0,
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"style_padding_bottom": 10,
|
||||
"style_margin_bottom": 0,
|
||||
"style_border_left_color": "border",
|
||||
"style_border_left_size": 0,
|
||||
"style_padding_left": 20,
|
||||
"style_margin_left": 0,
|
||||
"style_border_right_color": "border",
|
||||
"style_border_right_size": 0,
|
||||
"style_width": "normal",
|
||||
"style_padding_top": 10,
|
||||
"style_padding_bottom": 10,
|
||||
"style_padding_left": 20,
|
||||
"style_padding_right": 20,
|
||||
"style_margin_right": 0,
|
||||
"style_background": "none",
|
||||
"styles": {},
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_background_file_id": None,
|
||||
"style_background_mode": "fill",
|
||||
"style_width": "normal",
|
||||
"order": str(element_inside_container.order),
|
||||
"value": element_inside_container.value,
|
||||
"alignment": "left",
|
||||
|
@ -368,22 +392,28 @@ def test_builder_application_export(data_fixture):
|
|||
"place_in_container": None,
|
||||
"visibility": "all",
|
||||
"font_color": "default",
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"styles": {},
|
||||
"style_border_top_color": "border",
|
||||
"style_border_top_size": 0,
|
||||
"style_padding_top": 10,
|
||||
"style_margin_top": 0,
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"style_padding_bottom": 10,
|
||||
"style_margin_bottom": 0,
|
||||
"style_border_left_color": "border",
|
||||
"style_border_left_size": 0,
|
||||
"style_padding_left": 20,
|
||||
"style_margin_left": 0,
|
||||
"style_border_right_color": "border",
|
||||
"style_border_right_size": 0,
|
||||
"style_width": "normal",
|
||||
"style_padding_top": 10,
|
||||
"style_padding_bottom": 10,
|
||||
"style_padding_left": 20,
|
||||
"style_padding_right": 20,
|
||||
"style_margin_right": 0,
|
||||
"style_background": "none",
|
||||
"styles": {},
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_background_file_id": None,
|
||||
"style_background_mode": "fill",
|
||||
"style_width": "normal",
|
||||
"value": element3.value,
|
||||
"level": element3.level,
|
||||
"alignment": "left",
|
||||
|
@ -406,22 +436,28 @@ def test_builder_application_export(data_fixture):
|
|||
"parent_element_id": None,
|
||||
"place_in_container": None,
|
||||
"visibility": "all",
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"styles": {},
|
||||
"style_border_top_color": "border",
|
||||
"style_border_top_size": 0,
|
||||
"style_padding_top": 10,
|
||||
"style_margin_top": 0,
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"style_padding_bottom": 10,
|
||||
"style_margin_bottom": 0,
|
||||
"style_border_left_color": "border",
|
||||
"style_border_left_size": 0,
|
||||
"style_padding_left": 20,
|
||||
"style_margin_left": 0,
|
||||
"style_border_right_color": "border",
|
||||
"style_border_right_size": 0,
|
||||
"style_width": "normal",
|
||||
"style_padding_top": 10,
|
||||
"style_padding_bottom": 10,
|
||||
"style_padding_left": 20,
|
||||
"style_padding_right": 20,
|
||||
"style_margin_right": 0,
|
||||
"style_background": "none",
|
||||
"styles": {},
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_background_file_id": None,
|
||||
"style_background_mode": "fill",
|
||||
"style_width": "normal",
|
||||
"items_per_page": 42,
|
||||
"data_source_id": element4.data_source.id,
|
||||
"fields": [
|
||||
|
@ -470,42 +506,67 @@ def test_builder_application_export(data_fixture):
|
|||
},
|
||||
],
|
||||
"theme": {
|
||||
"body_text_color": "#070810ff",
|
||||
"body_font_size": 14,
|
||||
"body_text_alignment": "left",
|
||||
"primary_color": "#5190efff",
|
||||
"secondary_color": "#0eaa42ff",
|
||||
"border_color": "#d7d8d9ff",
|
||||
"main_success_color": "#12D452",
|
||||
"main_error_color": "#FF5A4A",
|
||||
"main_warning_color": "#FCC74A",
|
||||
"body_font_family": "inter",
|
||||
"body_font_size": 14,
|
||||
"body_text_color": "#070810ff",
|
||||
"body_text_alignment": "left",
|
||||
"heading_1_font_family": "inter",
|
||||
"heading_1_font_size": 24,
|
||||
"heading_1_text_color": "#070810ff",
|
||||
"heading_1_text_alignment": "left",
|
||||
"heading_2_font_family": "inter",
|
||||
"heading_2_font_size": 20,
|
||||
"heading_2_text_color": "#070810ff",
|
||||
"heading_2_text_alignment": "left",
|
||||
"heading_3_font_family": "inter",
|
||||
"heading_3_font_size": 16,
|
||||
"heading_3_text_color": "#070810ff",
|
||||
"heading_3_text_alignment": "left",
|
||||
"heading_4_font_family": "inter",
|
||||
"heading_4_font_size": 16,
|
||||
"heading_4_text_color": "#070810ff",
|
||||
"heading_4_text_alignment": "left",
|
||||
"heading_5_font_family": "inter",
|
||||
"heading_5_font_size": 14,
|
||||
"heading_5_text_color": "#070810ff",
|
||||
"heading_5_text_alignment": "left",
|
||||
"heading_6_font_family": "inter",
|
||||
"heading_6_font_size": 14,
|
||||
"heading_6_text_color": "#202128",
|
||||
"heading_6_text_alignment": "left",
|
||||
"button_background_color": "primary",
|
||||
"button_hover_background_color": "#96baf6ff",
|
||||
"button_font_family": "inter",
|
||||
"button_font_size": 13,
|
||||
"button_alignment": "left",
|
||||
"button_text_alignment": "center",
|
||||
"button_width": "auto",
|
||||
"image_alignment": "left",
|
||||
"image_constraint": "contain",
|
||||
"image_max_height": None,
|
||||
"image_max_width": 100,
|
||||
"button_background_color": "primary",
|
||||
"button_text_color": "#ffffffff",
|
||||
"button_border_color": "border",
|
||||
"button_border_size": 0,
|
||||
"button_border_radius": 4,
|
||||
"button_vertical_padding": 12,
|
||||
"button_horizontal_padding": 12,
|
||||
"button_hover_background_color": "#96baf6ff",
|
||||
"button_hover_text_color": "#ffffffff",
|
||||
"button_hover_border_color": "border",
|
||||
"link_font_family": "inter",
|
||||
"link_font_size": 13,
|
||||
"link_text_alignment": "left",
|
||||
"link_text_color": "primary",
|
||||
"link_hover_text_color": "#96baf6ff",
|
||||
"image_alignment": "left",
|
||||
"image_max_width": 100,
|
||||
"image_max_height": None,
|
||||
"image_constraint": "contain",
|
||||
"page_background_color": "#ffffffff",
|
||||
"page_background_file_id": None,
|
||||
"page_background_mode": "tile",
|
||||
},
|
||||
"id": builder.id,
|
||||
"name": builder.name,
|
||||
|
@ -514,6 +575,433 @@ def test_builder_application_export(data_fixture):
|
|||
"favicon_file": None,
|
||||
}
|
||||
|
||||
test = {
|
||||
"pages": [
|
||||
{
|
||||
"id": 3,
|
||||
"name": "search",
|
||||
"order": 1,
|
||||
"path": "search",
|
||||
"path_params": {},
|
||||
"elements": [
|
||||
{
|
||||
"id": 13,
|
||||
"order": "1.00000000000000000000",
|
||||
"type": "heading",
|
||||
"parent_element_id": None,
|
||||
"place_in_container": None,
|
||||
"visibility": "all",
|
||||
"role_type": "allow_all",
|
||||
"roles": [],
|
||||
"styles": {},
|
||||
"style_border_top_color": "border",
|
||||
"style_border_top_size": 0,
|
||||
"style_padding_top": 10,
|
||||
"style_margin_top": 0,
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"style_padding_bottom": 10,
|
||||
"style_margin_bottom": 0,
|
||||
"style_border_left_color": "border",
|
||||
"style_border_left_size": 0,
|
||||
"style_padding_left": 20,
|
||||
"style_margin_left": 0,
|
||||
"style_border_right_color": "border",
|
||||
"style_border_right_size": 0,
|
||||
"style_padding_right": 20,
|
||||
"style_margin_right": 0,
|
||||
"style_background": "none",
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_background_file_id": None,
|
||||
"style_background_mode": "fill",
|
||||
"style_width": "normal",
|
||||
"value": "foo",
|
||||
"font_color": "default",
|
||||
"level": 2,
|
||||
"alignment": "left",
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"order": "2.00000000000000000000",
|
||||
"type": "text",
|
||||
"parent_element_id": None,
|
||||
"place_in_container": None,
|
||||
"visibility": "all",
|
||||
"role_type": "allow_all",
|
||||
"roles": [],
|
||||
"styles": {},
|
||||
"style_border_top_color": "border",
|
||||
"style_border_top_size": 0,
|
||||
"style_padding_top": 10,
|
||||
"style_margin_top": 0,
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"style_padding_bottom": 10,
|
||||
"style_margin_bottom": 0,
|
||||
"style_border_left_color": "border",
|
||||
"style_border_left_size": 0,
|
||||
"style_padding_left": 20,
|
||||
"style_margin_left": 0,
|
||||
"style_border_right_color": "border",
|
||||
"style_border_right_size": 0,
|
||||
"style_padding_right": 20,
|
||||
"style_margin_right": 0,
|
||||
"style_background": "none",
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_background_file_id": None,
|
||||
"style_background_mode": "fill",
|
||||
"style_width": "normal",
|
||||
"value": "",
|
||||
"alignment": "left",
|
||||
"format": "plain",
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"order": "3.00000000000000000000",
|
||||
"type": "column",
|
||||
"parent_element_id": None,
|
||||
"place_in_container": None,
|
||||
"visibility": "all",
|
||||
"role_type": "allow_all",
|
||||
"roles": [],
|
||||
"styles": {},
|
||||
"style_border_top_color": "border",
|
||||
"style_border_top_size": 0,
|
||||
"style_padding_top": 10,
|
||||
"style_margin_top": 0,
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"style_padding_bottom": 10,
|
||||
"style_margin_bottom": 0,
|
||||
"style_border_left_color": "border",
|
||||
"style_border_left_size": 0,
|
||||
"style_padding_left": 20,
|
||||
"style_margin_left": 0,
|
||||
"style_border_right_color": "border",
|
||||
"style_border_right_size": 0,
|
||||
"style_padding_right": 20,
|
||||
"style_margin_right": 0,
|
||||
"style_background": "none",
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_background_file_id": None,
|
||||
"style_background_mode": "fill",
|
||||
"style_width": "normal",
|
||||
"column_amount": 3,
|
||||
"column_gap": 50,
|
||||
"alignment": "top",
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"order": "4.00000000000000000000",
|
||||
"type": "text",
|
||||
"parent_element_id": 16,
|
||||
"place_in_container": "0",
|
||||
"visibility": "all",
|
||||
"role_type": "allow_all",
|
||||
"roles": [],
|
||||
"styles": {},
|
||||
"style_border_top_color": "border",
|
||||
"style_border_top_size": 0,
|
||||
"style_padding_top": 10,
|
||||
"style_margin_top": 0,
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"style_padding_bottom": 10,
|
||||
"style_margin_bottom": 0,
|
||||
"style_border_left_color": "border",
|
||||
"style_border_left_size": 0,
|
||||
"style_padding_left": 20,
|
||||
"style_margin_left": 0,
|
||||
"style_border_right_color": "border",
|
||||
"style_border_right_size": 0,
|
||||
"style_padding_right": 20,
|
||||
"style_margin_right": 0,
|
||||
"style_background": "none",
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_background_file_id": None,
|
||||
"style_background_mode": "fill",
|
||||
"style_width": "normal",
|
||||
"value": "",
|
||||
"alignment": "left",
|
||||
"format": "plain",
|
||||
},
|
||||
],
|
||||
"data_sources": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "source 1",
|
||||
"order": "1.00000000000000000000",
|
||||
"service": {
|
||||
"id": 1,
|
||||
"integration_id": 2,
|
||||
"type": "local_baserow_get_row",
|
||||
"table_id": None,
|
||||
"view_id": None,
|
||||
"filter_type": "AND",
|
||||
"filters": [],
|
||||
"row_id": "",
|
||||
"search_query": "",
|
||||
},
|
||||
}
|
||||
],
|
||||
"workflow_actions": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "notification",
|
||||
"order": 0,
|
||||
"page_id": 3,
|
||||
"element_id": 13,
|
||||
"event": "click",
|
||||
"title": "there",
|
||||
"description": "hello",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "index",
|
||||
"order": 2,
|
||||
"path": "index",
|
||||
"path_params": {},
|
||||
"elements": [
|
||||
{
|
||||
"id": 15,
|
||||
"order": "1.00000000000000000000",
|
||||
"type": "heading",
|
||||
"parent_element_id": None,
|
||||
"place_in_container": None,
|
||||
"visibility": "all",
|
||||
"role_type": "allow_all",
|
||||
"roles": [],
|
||||
"styles": {},
|
||||
"style_border_top_color": "border",
|
||||
"style_border_top_size": 0,
|
||||
"style_padding_top": 10,
|
||||
"style_margin_top": 0,
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"style_padding_bottom": 10,
|
||||
"style_margin_bottom": 0,
|
||||
"style_border_left_color": "border",
|
||||
"style_border_left_size": 0,
|
||||
"style_padding_left": 20,
|
||||
"style_margin_left": 0,
|
||||
"style_border_right_color": "border",
|
||||
"style_border_right_size": 0,
|
||||
"style_padding_right": 20,
|
||||
"style_margin_right": 0,
|
||||
"style_background": "none",
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_background_file_id": None,
|
||||
"style_background_mode": "fill",
|
||||
"style_width": "normal",
|
||||
"value": "",
|
||||
"font_color": "default",
|
||||
"level": 1,
|
||||
"alignment": "left",
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"order": "2.00000000000000000000",
|
||||
"type": "table",
|
||||
"parent_element_id": None,
|
||||
"place_in_container": None,
|
||||
"visibility": "all",
|
||||
"role_type": "allow_all",
|
||||
"roles": [],
|
||||
"styles": {},
|
||||
"style_border_top_color": "border",
|
||||
"style_border_top_size": 0,
|
||||
"style_padding_top": 10,
|
||||
"style_margin_top": 0,
|
||||
"style_border_bottom_color": "border",
|
||||
"style_border_bottom_size": 0,
|
||||
"style_padding_bottom": 10,
|
||||
"style_margin_bottom": 0,
|
||||
"style_border_left_color": "border",
|
||||
"style_border_left_size": 0,
|
||||
"style_padding_left": 20,
|
||||
"style_margin_left": 0,
|
||||
"style_border_right_color": "border",
|
||||
"style_border_right_size": 0,
|
||||
"style_padding_right": 20,
|
||||
"style_margin_right": 0,
|
||||
"style_background": "none",
|
||||
"style_background_color": "#ffffffff",
|
||||
"style_background_file_id": None,
|
||||
"style_background_mode": "fill",
|
||||
"style_width": "normal",
|
||||
"data_source_id": 3,
|
||||
"items_per_page": 42,
|
||||
"button_load_more_label": "",
|
||||
"fields": [
|
||||
{
|
||||
"uid": "447cbec7-c422-42eb-bd50-204b53453330",
|
||||
"name": "Field 1",
|
||||
"type": "text",
|
||||
"config": {"value": "get('test1')"},
|
||||
},
|
||||
{
|
||||
"uid": "44446a1c-841f-47ba-b1df-e902cc50c6ed",
|
||||
"name": "Field 2",
|
||||
"type": "text",
|
||||
"config": {"value": "get('test2')"},
|
||||
},
|
||||
{
|
||||
"uid": "960aef1f-a894-4003-8cf2-36da3b9c798b",
|
||||
"name": "Field 3",
|
||||
"type": "text",
|
||||
"config": {"value": "get('test3')"},
|
||||
},
|
||||
],
|
||||
"button_color": "primary",
|
||||
"orientation": {
|
||||
"tablet": "horizontal",
|
||||
"desktop": "horizontal",
|
||||
"smartphone": "horizontal",
|
||||
},
|
||||
},
|
||||
],
|
||||
"data_sources": [
|
||||
{
|
||||
"id": 2,
|
||||
"name": "source 2",
|
||||
"order": "1.00000000000000000000",
|
||||
"service": {
|
||||
"id": 2,
|
||||
"integration_id": 2,
|
||||
"type": "local_baserow_get_row",
|
||||
"table_id": None,
|
||||
"view_id": None,
|
||||
"filter_type": "AND",
|
||||
"filters": [],
|
||||
"row_id": "",
|
||||
"search_query": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "source 3",
|
||||
"order": "2.00000000000000000000",
|
||||
"service": {
|
||||
"id": 3,
|
||||
"integration_id": 2,
|
||||
"type": "local_baserow_list_rows",
|
||||
"table_id": None,
|
||||
"view_id": None,
|
||||
"search_query": "",
|
||||
"filter_type": "AND",
|
||||
"filters": [],
|
||||
"sortings": [],
|
||||
},
|
||||
},
|
||||
],
|
||||
"workflow_actions": [],
|
||||
},
|
||||
],
|
||||
"integrations": [
|
||||
{
|
||||
"id": 2,
|
||||
"name": "test",
|
||||
"order": "1.00000000000000000000",
|
||||
"type": "local_baserow",
|
||||
"authorized_user": "jennifer92@example.net",
|
||||
}
|
||||
],
|
||||
"theme": {
|
||||
"id": 1,
|
||||
"primary_color": "#5190efff",
|
||||
"secondary_color": "#0eaa42ff",
|
||||
"border_color": "#d7d8d9ff",
|
||||
"main_success_color": "#12D452",
|
||||
"main_error_color": "#FF5A4A",
|
||||
"main_warning_color": "#FCC74A",
|
||||
"body_font_family": "inter",
|
||||
"body_font_size": 14,
|
||||
"body_text_color": "#070810ff",
|
||||
"body_text_alignment": "left",
|
||||
"heading_1_font_family": "inter",
|
||||
"heading_1_font_size": 24,
|
||||
"heading_1_text_color": "#070810ff",
|
||||
"heading_1_text_alignment": "left",
|
||||
"heading_2_font_family": "inter",
|
||||
"heading_2_font_size": 20,
|
||||
"heading_2_text_color": "#070810ff",
|
||||
"heading_2_text_alignment": "left",
|
||||
"heading_3_font_family": "inter",
|
||||
"heading_3_font_size": 16,
|
||||
"heading_3_text_color": "#070810ff",
|
||||
"heading_3_text_alignment": "left",
|
||||
"heading_4_font_family": "inter",
|
||||
"heading_4_font_size": 16,
|
||||
"heading_4_text_color": "#070810ff",
|
||||
"heading_4_text_alignment": "left",
|
||||
"heading_5_font_family": "inter",
|
||||
"heading_5_font_size": 14,
|
||||
"heading_5_text_color": "#070810ff",
|
||||
"heading_5_text_alignment": "left",
|
||||
"heading_6_font_family": "inter",
|
||||
"heading_6_font_size": 14,
|
||||
"heading_6_text_color": "#202128",
|
||||
"heading_6_text_alignment": "left",
|
||||
"button_font_family": "inter",
|
||||
"button_font_size": 13,
|
||||
"button_alignment": "left",
|
||||
"button_text_alignment": "center",
|
||||
"button_width": "auto",
|
||||
"button_background_color": "primary",
|
||||
"button_text_color": "#ffffffff",
|
||||
"button_border_color": "border",
|
||||
"button_border_size": 0,
|
||||
"button_border_radius": 4,
|
||||
"button_vertical_padding": 12,
|
||||
"button_horizontal_padding": 12,
|
||||
"button_hover_background_color": "#96baf6ff",
|
||||
"button_hover_text_color": "#ffffffff",
|
||||
"button_hover_border_color": "border",
|
||||
"link_font_family": "inter",
|
||||
"link_font_size": 13,
|
||||
"link_text_alignment": "left",
|
||||
"link_text_color": "primary",
|
||||
"link_hover_text_color": "#96baf6ff",
|
||||
"image_alignment": "left",
|
||||
"image_max_width": 100,
|
||||
"image_max_height": None,
|
||||
"image_constraint": "contain",
|
||||
"page_background_color": "#ffffffff",
|
||||
"page_background_file_id": None,
|
||||
"page_background_mode": "tile",
|
||||
},
|
||||
"user_sources": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "",
|
||||
"order": "1.00000000000000000000",
|
||||
"type": "local_baserow",
|
||||
"uid": "12345678123456781234567812345678",
|
||||
"integration_id": 2,
|
||||
"auth_providers": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "local_baserow_password",
|
||||
"domain": None,
|
||||
"enabled": True,
|
||||
"password_field_id": None,
|
||||
}
|
||||
],
|
||||
"table_id": None,
|
||||
"email_field_id": None,
|
||||
"name_field_id": None,
|
||||
"role_field_id": None,
|
||||
}
|
||||
],
|
||||
"favicon_file": None,
|
||||
"id": 5,
|
||||
"name": "Monica Baldwin",
|
||||
"order": 0,
|
||||
"type": "builder",
|
||||
}
|
||||
|
||||
assert serialized == reference
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "feature",
|
||||
"message": "[Builder] More styles of the builder elements can be customized",
|
||||
"issue_number": 2388,
|
||||
"bullet_points": [],
|
||||
"created_at": "2024-07-04"
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<Dropdown :value="value" fixed-items small @input="$emit('input', $event)">
|
||||
<DropdownItem
|
||||
v-for="fontFamily in fontFamilies"
|
||||
:key="fontFamily.getType()"
|
||||
:value="fontFamily.getType()"
|
||||
:name="fontFamily.name"
|
||||
/>
|
||||
</Dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FontFamilySelector',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'Inter',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
fontFamilies() {
|
||||
return Object.values(this.$registry.getAll('fontFamily')).sort((a, b) =>
|
||||
a.name.localeCompare(b.name)
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
40
web-frontend/modules/builder/components/PaddingSelector.vue
Normal file
40
web-frontend/modules/builder/components/PaddingSelector.vue
Normal file
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<div class="padding-selector">
|
||||
<FormInput
|
||||
small
|
||||
:value="value.horizontal"
|
||||
type="number"
|
||||
remove-number-input-controls
|
||||
:to-value="(val) => parseInt(val)"
|
||||
class="padding-selector__input"
|
||||
icon-right="iconoir-horizontal-split"
|
||||
@input="$emit('input', { horizontal: $event, vertical: value.vertical })"
|
||||
@blur="$emit('blur')"
|
||||
/>
|
||||
<FormInput
|
||||
small
|
||||
:value="value.vertical"
|
||||
type="number"
|
||||
remove-number-input-controls
|
||||
:to-value="(val) => parseInt(val)"
|
||||
class="padding-selector__input"
|
||||
icon-right="iconoir-vertical-split"
|
||||
@input="
|
||||
$emit('input', { horizontal: value.horizontal, vertical: $event })
|
||||
"
|
||||
@blur="$emit('blur')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PaddingSelector',
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<FormInput
|
||||
small
|
||||
:value="value"
|
||||
type="number"
|
||||
remove-number-input-controls
|
||||
:to-value="(val) => parseInt(val)"
|
||||
:style="{
|
||||
width: '100px',
|
||||
}"
|
||||
@input="$emit('input', $event)"
|
||||
@blur="$emit('blur')"
|
||||
>
|
||||
<template #suffix>px</template>
|
||||
</FormInput>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PixelValueSelector',
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -9,7 +9,7 @@
|
|||
}"
|
||||
@click="onClick"
|
||||
>
|
||||
<slot></slot>
|
||||
<span><slot></slot></span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
<template>
|
||||
<form class="table-element-form" @submit.prevent @keydown.enter.prevent>
|
||||
<CustomStyle
|
||||
v-model="values.styles"
|
||||
style-key="button"
|
||||
:config-block-types="['button']"
|
||||
:theme="builder.theme"
|
||||
:element="values"
|
||||
/>
|
||||
<FormGroup
|
||||
class="margin-bottom-2"
|
||||
small-label
|
||||
|
@ -53,7 +46,6 @@
|
|||
:config-block-types="['button']"
|
||||
:theme="builder.theme"
|
||||
/>
|
||||
|
||||
<ApplicationBuilderFormulaInputGroup
|
||||
v-model="values.button_load_more_label"
|
||||
:label="$t('tableElementForm.buttonLoadMoreLabel')"
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
style-key="typography"
|
||||
:config-block-types="['typography']"
|
||||
:theme="builder.theme"
|
||||
:extra-args="{ onlyBody: values.format === TEXT_FORMAT_TYPES.PLAIN }"
|
||||
/>
|
||||
<ApplicationBuilderFormulaInputGroup
|
||||
v-model="values.value"
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
/>
|
||||
<Context ref="context">
|
||||
<div v-auto-overflow-scroll class="custom-style__config-blocks">
|
||||
<h2>{{ $t('customStyle.themeOverrides') }}</h2>
|
||||
<div
|
||||
v-for="(themeConfigBlock, index) in themeConfigBlocks"
|
||||
:key="themeConfigBlock.getType()"
|
||||
|
@ -20,7 +21,7 @@
|
|||
</h2>
|
||||
<ThemeConfigBlock
|
||||
:theme="theme"
|
||||
:default-values="value[styleKey] || {}"
|
||||
:default-values="value?.[styleKey]"
|
||||
:preview="false"
|
||||
:theme-config-block-type="themeConfigBlock"
|
||||
:class="{ 'margin-top-3': index >= 1 }"
|
||||
|
|
|
@ -1,61 +1,97 @@
|
|||
<template>
|
||||
<form @submit.prevent>
|
||||
<StyleBoxForm
|
||||
v-for="{ name, label } in borders"
|
||||
:key="name"
|
||||
v-model="boxStyles[name]"
|
||||
:label="label"
|
||||
:padding-is-allowed="isStyleAllowed(`style_padding_${name}`)"
|
||||
:border-is-allowed="isStyleAllowed(`style_border_${name}`)"
|
||||
/>
|
||||
<FormGroup
|
||||
v-if="isStyleAllowed('style_background')"
|
||||
class="margin-bottom-2"
|
||||
small-label
|
||||
required
|
||||
:label="$t('defaultStyleForm.backgroundLabel')"
|
||||
>
|
||||
<Dropdown v-model="values.style_background">
|
||||
<DropdownItem
|
||||
v-for="type in Object.values(BACKGROUND_TYPES)"
|
||||
:key="type.value"
|
||||
:name="$t(type.name)"
|
||||
:value="type.value"
|
||||
<FormSection v-if="isStyleAllowed('style_background')">
|
||||
<FormGroup
|
||||
v-if="isStyleAllowed('style_background')"
|
||||
:label="$t('defaultStyleForm.backgroundLabel')"
|
||||
small-label
|
||||
required
|
||||
class="margin-bottom-1"
|
||||
>
|
||||
<RadioGroup
|
||||
v-model="values.style_background"
|
||||
type="button"
|
||||
:options="backgroundTypes"
|
||||
/>
|
||||
</Dropdown>
|
||||
<ColorInputGroup
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
v-if="
|
||||
values.style_background === BACKGROUND_TYPES.COLOR.value &&
|
||||
values.style_background === BACKGROUND_TYPES.COLOR &&
|
||||
isStyleAllowed('style_background_color')
|
||||
"
|
||||
v-model="values.style_background_color"
|
||||
label-after
|
||||
class="margin-bottom-1"
|
||||
small-label
|
||||
required
|
||||
>
|
||||
<ColorInput
|
||||
v-model="values.style_background_color"
|
||||
small
|
||||
:color-variables="colorVariables"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
v-if="isStyleAllowed('style_background_file')"
|
||||
:label="$t('defaultStyleForm.backgroundImage')"
|
||||
small-label
|
||||
required
|
||||
class="margin-top-2"
|
||||
:label="$t('defaultStyleForm.backgroundColor')"
|
||||
:color-variables="colorVariables"
|
||||
>
|
||||
<ImageInput v-model="values.style_background_file" />
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
v-if="
|
||||
isStyleAllowed('style_background_mode') &&
|
||||
values.style_background_file
|
||||
"
|
||||
:label="$t('defaultStyleForm.backgroundImageMode')"
|
||||
small-label
|
||||
required
|
||||
>
|
||||
<RadioGroup
|
||||
v-model="values.style_background_mode"
|
||||
type="button"
|
||||
:options="backgroundModes"
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormSection>
|
||||
<FormSection v-if="isStyleAllowed('style_width')">
|
||||
<FormGroup
|
||||
:label="$t('defaultStyleForm.widthLabel')"
|
||||
small-label
|
||||
required
|
||||
class="margin-bottom-1"
|
||||
>
|
||||
<Dropdown v-model="values.style_width">
|
||||
<DropdownItem
|
||||
v-for="type in Object.values(WIDTH_TYPES)"
|
||||
:key="type.value"
|
||||
:name="$t(type.name)"
|
||||
:value="type.value"
|
||||
>
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</FormGroup>
|
||||
</FormSection>
|
||||
<FormSection v-for="{ name, label } in borders" :key="name" :title="label">
|
||||
<StyleBoxForm
|
||||
v-model="boxStyles[name]"
|
||||
:padding-is-allowed="isStyleAllowed(`style_padding_${name}`)"
|
||||
:border-is-allowed="isStyleAllowed(`style_border_${name}`)"
|
||||
:margin-is-allowed="isStyleAllowed(`style_margin_${name}`)"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
v-if="isStyleAllowed('style_width')"
|
||||
:label="$t('defaultStyleForm.widthLabel')"
|
||||
small-label
|
||||
required
|
||||
>
|
||||
<Dropdown v-model="values.style_width">
|
||||
<DropdownItem
|
||||
v-for="type in Object.values(WIDTH_TYPES)"
|
||||
:key="type.value"
|
||||
:name="$t(type.name)"
|
||||
:value="type.value"
|
||||
></DropdownItem> </Dropdown
|
||||
></FormGroup>
|
||||
</FormSection>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StyleBoxForm from '@baserow/modules/builder/components/elements/components/forms/style/StyleBoxForm'
|
||||
import styleForm from '@baserow/modules/builder/mixins/styleForm'
|
||||
import { BACKGROUND_TYPES, WIDTH_TYPES } from '@baserow/modules/builder/enums'
|
||||
import {
|
||||
BACKGROUND_TYPES,
|
||||
WIDTH_TYPES,
|
||||
BACKGROUND_MODES,
|
||||
} from '@baserow/modules/builder/enums'
|
||||
import { IMAGE_FILE_TYPES } from '@baserow/modules/core/enums'
|
||||
|
||||
export default {
|
||||
components: { StyleBoxForm },
|
||||
|
@ -63,6 +99,37 @@ export default {
|
|||
computed: {
|
||||
BACKGROUND_TYPES: () => BACKGROUND_TYPES,
|
||||
WIDTH_TYPES: () => WIDTH_TYPES,
|
||||
backgroundTypes() {
|
||||
return [
|
||||
{
|
||||
label: this.$t('backgroundTypes.none'),
|
||||
value: BACKGROUND_TYPES.NONE,
|
||||
},
|
||||
{
|
||||
label: this.$t('backgroundTypes.color'),
|
||||
value: BACKGROUND_TYPES.COLOR,
|
||||
},
|
||||
]
|
||||
},
|
||||
backgroundModes() {
|
||||
return [
|
||||
{
|
||||
label: this.$t('backgroundModes.fill'),
|
||||
value: BACKGROUND_MODES.FILL,
|
||||
},
|
||||
{
|
||||
label: this.$t('backgroundModes.fit'),
|
||||
value: BACKGROUND_MODES.FIT,
|
||||
},
|
||||
{
|
||||
label: this.$t('backgroundModes.tile'),
|
||||
value: BACKGROUND_MODES.TILE,
|
||||
},
|
||||
]
|
||||
},
|
||||
IMAGE_FILE_TYPES() {
|
||||
return IMAGE_FILE_TYPES
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,53 +1,52 @@
|
|||
<template>
|
||||
<form @submit.prevent>
|
||||
<FormGroup
|
||||
class="style-box-form__control margin-bottom-2"
|
||||
:label="label"
|
||||
v-if="borderIsAllowed"
|
||||
horizontal
|
||||
class="margin-bottom-1"
|
||||
small-label
|
||||
required
|
||||
:error="error"
|
||||
:label="$t('styleBoxForm.borderColor')"
|
||||
>
|
||||
<div
|
||||
v-if="borderIsAllowed || paddingIsAllowed"
|
||||
class="row margin-bottom-2"
|
||||
style="--gap: 6px"
|
||||
>
|
||||
<div v-if="borderIsAllowed" class="col col-4">
|
||||
<div class="margin-bottom-1">
|
||||
{{ $t('styleBoxForm.borderLabel') }}
|
||||
</div>
|
||||
<FormInput
|
||||
v-model="values.border_size"
|
||||
size="large"
|
||||
type="number"
|
||||
:min="0"
|
||||
:max="200"
|
||||
:error="error"
|
||||
@blur="$v.values.border_size.$touch()"
|
||||
></FormInput>
|
||||
</div>
|
||||
<div v-if="paddingIsAllowed" class="col col-4">
|
||||
<div class="margin-bottom-1">
|
||||
{{ $t('styleBoxForm.paddingLabel') }}
|
||||
</div>
|
||||
<FormInput
|
||||
v-model="values.padding"
|
||||
size="large"
|
||||
type="number"
|
||||
:error="error"
|
||||
@blur="$v.values.padding.$touch()"
|
||||
></FormInput>
|
||||
</div>
|
||||
</div>
|
||||
<ColorInputGroup
|
||||
v-if="borderIsAllowed"
|
||||
<ColorInput
|
||||
v-model="values.border_color"
|
||||
label-after
|
||||
class="margin-top-2"
|
||||
:label="$t('styleBoxForm.borderLabel')"
|
||||
small
|
||||
:color-variables="colorVariables"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
v-if="borderIsAllowed"
|
||||
class="margin-bottom-1"
|
||||
small-label
|
||||
required
|
||||
:label="$t('styleBoxForm.borderLabel')"
|
||||
horizontal
|
||||
:error-message="sizeError"
|
||||
>
|
||||
<PixelValueSelector v-model="values.border_size" />
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
v-if="paddingIsAllowed"
|
||||
class="margin-bottom-1"
|
||||
small-label
|
||||
required
|
||||
:label="$t('styleBoxForm.paddingLabel')"
|
||||
horizontal
|
||||
:error-message="paddingError"
|
||||
>
|
||||
<PixelValueSelector v-model="values.padding" />
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
v-if="marginIsAllowed"
|
||||
class="margin-bottom-1"
|
||||
small-label
|
||||
required
|
||||
:label="$t('styleBoxForm.marginLabel')"
|
||||
horizontal
|
||||
:error-message="marginError"
|
||||
>
|
||||
<PixelValueSelector v-model="actualMargin" />
|
||||
</FormGroup>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
|
@ -56,15 +55,14 @@ import { required, integer, between } from 'vuelidate/lib/validators'
|
|||
import form from '@baserow/modules/core/mixins/form'
|
||||
import { themeToColorVariables } from '@baserow/modules/builder/utils/theme'
|
||||
|
||||
import PixelValueSelector from '@baserow/modules/builder/components/PixelValueSelector'
|
||||
|
||||
export default {
|
||||
name: 'StyleBoxForm',
|
||||
components: { PixelValueSelector },
|
||||
mixins: [form],
|
||||
inject: ['builder'],
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -74,6 +72,11 @@ export default {
|
|||
required: false,
|
||||
default: () => false,
|
||||
},
|
||||
marginIsAllowed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: () => false,
|
||||
},
|
||||
borderIsAllowed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
@ -83,6 +86,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
values: {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
border_color: 'border',
|
||||
border_size: 0,
|
||||
|
@ -90,15 +94,38 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
// TODO zdm can be removed when we remove the null value from backend field
|
||||
actualMargin: {
|
||||
get() {
|
||||
return this.values.margin || 0
|
||||
},
|
||||
set(newValue) {
|
||||
this.values.margin = newValue
|
||||
},
|
||||
},
|
||||
colorVariables() {
|
||||
return themeToColorVariables(this.builder.theme)
|
||||
},
|
||||
/**
|
||||
* Returns only one error because we don't have the space to write one error per
|
||||
* field as the style fields are on the same line.
|
||||
*/
|
||||
error() {
|
||||
return this.$v.values.padding.$error || this.$v.values.border_size.$error
|
||||
marginError() {
|
||||
if (this.$v.actualMargin.$invalid) {
|
||||
return this.$t('error.minMaxValueField', { min: 0, max: 200 })
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
paddingError() {
|
||||
if (this.$v.values.padding.$invalid) {
|
||||
return this.$t('error.minMaxValueField', { min: 0, max: 200 })
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
sizeError() {
|
||||
if (this.$v.values.border_size.$invalid) {
|
||||
return this.$t('error.minMaxValueField', { min: 0, max: 200 })
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -123,6 +150,11 @@ export default {
|
|||
between: between(0, 200),
|
||||
},
|
||||
},
|
||||
actualMargin: {
|
||||
required,
|
||||
integer,
|
||||
between: between(0, 200),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<ThemeProvider>
|
||||
<ThemeProvider class="page">
|
||||
<PageElement
|
||||
v-for="element in elements"
|
||||
:key="element.id"
|
||||
|
|
|
@ -3,16 +3,18 @@
|
|||
v-if="elementMode === 'editing' || isVisible"
|
||||
class="element__wrapper"
|
||||
:class="{
|
||||
'element__wrapper--full-width':
|
||||
'element__wrapper--full-bleed':
|
||||
element.style_width === WIDTH_TYPES.FULL.value,
|
||||
'element__wrapper--full-width':
|
||||
element.style_width === WIDTH_TYPES.FULL_WIDTH.value,
|
||||
'element__wrapper--medium-width':
|
||||
element.style_width === WIDTH_TYPES.MEDIUM.value,
|
||||
'element__wrapper--small-width':
|
||||
element.style_width === WIDTH_TYPES.SMALL.value,
|
||||
}"
|
||||
:style="wrapperStyles"
|
||||
:style="elementStyles"
|
||||
>
|
||||
<div class="element__inner-wrapper" :style="innerWrapperStyles">
|
||||
<div class="element__inner-wrapper">
|
||||
<component
|
||||
:is="component"
|
||||
:element="element"
|
||||
|
@ -27,11 +29,14 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { resolveColor } from '@baserow/modules/core/utils/colors'
|
||||
import { themeToColorVariables } from '@baserow/modules/builder/utils/theme'
|
||||
|
||||
import { BACKGROUND_TYPES, WIDTH_TYPES } from '@baserow/modules/builder/enums'
|
||||
import {
|
||||
BACKGROUND_TYPES,
|
||||
WIDTH_TYPES,
|
||||
BACKGROUND_MODES,
|
||||
} from '@baserow/modules/builder/enums'
|
||||
import applicationContextMixin from '@baserow/modules/builder/mixins/applicationContext'
|
||||
import {
|
||||
VISIBILITY_NOT_LOGGED,
|
||||
|
@ -107,109 +112,75 @@ export default {
|
|||
return true
|
||||
}
|
||||
},
|
||||
allowedStyles() {
|
||||
const parentElement = this.$store.getters['element/getElementById'](
|
||||
this.page,
|
||||
this.element.parent_element_id
|
||||
)
|
||||
|
||||
const elementType = this.$registry.get('element', this.element.type)
|
||||
const parentElementType = this.parentElement
|
||||
? this.$registry.get('element', parentElement?.type)
|
||||
: null
|
||||
|
||||
return !parentElementType
|
||||
? elementType.styles
|
||||
: _.difference(
|
||||
elementType.styles,
|
||||
parentElementType.childStylesForbidden
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Computes an object containing all the style properties that must be set on
|
||||
* the element wrapper.
|
||||
*/
|
||||
wrapperStyles() {
|
||||
elementStyles() {
|
||||
const styles = {
|
||||
style_background_color: {
|
||||
'--background-color':
|
||||
this.element.style_background === BACKGROUND_TYPES.COLOR.value
|
||||
? this.resolveColor(
|
||||
this.element.style_background_color,
|
||||
this.colorVariables
|
||||
)
|
||||
: 'transparent',
|
||||
},
|
||||
style_border_top: {
|
||||
'--border-top': this.border(
|
||||
this.element.style_border_top_size,
|
||||
this.element.style_border_top_color
|
||||
),
|
||||
},
|
||||
style_border_bottom: {
|
||||
'--border-bottom': this.border(
|
||||
this.element.style_border_bottom_size,
|
||||
this.element.style_border_bottom_color
|
||||
),
|
||||
},
|
||||
style_border_left: {
|
||||
'--border-left': this.border(
|
||||
this.element.style_border_left_size,
|
||||
this.element.style_border_left_color
|
||||
),
|
||||
},
|
||||
style_border_right: {
|
||||
'--border-right': this.border(
|
||||
this.element.style_border_right_size,
|
||||
this.element.style_border_right_color
|
||||
),
|
||||
},
|
||||
'--element-background-color':
|
||||
this.element.style_background === BACKGROUND_TYPES.COLOR
|
||||
? this.resolveColor(
|
||||
this.element.style_background_color,
|
||||
this.colorVariables
|
||||
)
|
||||
: 'none',
|
||||
|
||||
'--element-background-image':
|
||||
this.element.style_background_file !== null
|
||||
? `url(${this.element.style_background_file.url})`
|
||||
: 'none',
|
||||
|
||||
'--element-border-top': this.border(
|
||||
this.element.style_border_top_size,
|
||||
this.element.style_border_top_color
|
||||
),
|
||||
'--element-margin-top': `${this.element.style_margin_top || 0}px`,
|
||||
'--element-padding-top': `${this.element.style_padding_top || 0}px`,
|
||||
'--element-border-bottom': this.border(
|
||||
this.element.style_border_bottom_size,
|
||||
this.element.style_border_bottom_color
|
||||
),
|
||||
'--element-margin-bottom': `${this.element.style_margin_bottom || 0}px`,
|
||||
'--element-padding-bottom': `${
|
||||
this.element.style_padding_bottom || 0
|
||||
}px`,
|
||||
'--element-border-left': this.border(
|
||||
this.element.style_border_left_size,
|
||||
this.element.style_border_left_color
|
||||
),
|
||||
'--element-margin-left': `${this.element.style_margin_left || 0}px`,
|
||||
'--element-padding-left': `${this.element.style_padding_left || 0}px`,
|
||||
'--element-border-right': this.border(
|
||||
this.element.style_border_right_size,
|
||||
this.element.style_border_right_color
|
||||
),
|
||||
'--element-margin-right': `${this.element.style_margin_right || 0}px`,
|
||||
|
||||
'--element-padding-right': `${this.element.style_padding_right || 0}px`,
|
||||
}
|
||||
|
||||
return Object.keys(styles).reduce((acc, key) => {
|
||||
if (this.allowedStyles.includes(key)) {
|
||||
acc = { ...acc, ...styles[key] }
|
||||
if (this.element.style_background_file !== null) {
|
||||
if (this.element.style_background_mode === BACKGROUND_MODES.FILL) {
|
||||
styles['--element-background-size'] = 'cover'
|
||||
styles['--element-background-repeat'] = 'no-repeat'
|
||||
}
|
||||
if (this.element.style_background_mode === BACKGROUND_MODES.TILE) {
|
||||
styles['--element-background-size'] = 'auto'
|
||||
styles['--element-background-repeat'] = 'repeat'
|
||||
}
|
||||
if (this.element.style_background_mode === BACKGROUND_MODES.FIT) {
|
||||
styles['--element-background-size'] = 'contain'
|
||||
styles['--element-background-repeat'] = 'no-repeat'
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
|
||||
/**
|
||||
* Computes an object containing all the style properties that must be set on
|
||||
* the element inner wrapper.
|
||||
*/
|
||||
innerWrapperStyles() {
|
||||
const styles = {
|
||||
style_padding_top: {
|
||||
'--padding-top': `${this.element.style_padding_top || 0}px`,
|
||||
},
|
||||
style_padding_bottom: {
|
||||
'--padding-bottom': `${this.element.style_padding_bottom || 0}px`,
|
||||
},
|
||||
style_padding_left: {
|
||||
'--padding-left': `${this.element.style_padding_left || 0}px`,
|
||||
},
|
||||
style_padding_right: {
|
||||
'--padding-right': `${this.element.style_padding_right || 0}px`,
|
||||
},
|
||||
}
|
||||
|
||||
return Object.keys(styles).reduce((acc, key) => {
|
||||
if (this.allowedStyles.includes(key)) {
|
||||
acc = { ...acc, ...styles[key] }
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
return styles
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resolveColor,
|
||||
border(size, color) {
|
||||
return `solid ${size || 0}px ${this.resolveColor(
|
||||
color,
|
||||
this.colorVariables
|
||||
)}`
|
||||
if (!size) {
|
||||
return 'none'
|
||||
}
|
||||
return `solid ${size}px ${this.resolveColor(color, this.colorVariables)}`
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -23,21 +23,23 @@
|
|||
>
|
||||
{{ $t('pagePreview.emptyMessage') }}
|
||||
</CallToAction>
|
||||
<AddElementModal ref="addElementModal" :page="page" />
|
||||
<ElementPreview
|
||||
v-for="(element, index) in elements"
|
||||
:key="element.id"
|
||||
is-root-element
|
||||
:element="element"
|
||||
:is-first-element="index === 0"
|
||||
:is-last-element="index === elements.length - 1"
|
||||
:is-copying="copyingElementIndex === index"
|
||||
:application-context-additions="{
|
||||
recordIndexPath: [],
|
||||
}"
|
||||
@move="moveElement($event)"
|
||||
/>
|
||||
<div class="page">
|
||||
<ElementPreview
|
||||
v-for="(element, index) in elements"
|
||||
:key="element.id"
|
||||
is-root-element
|
||||
:element="element"
|
||||
:is-first-element="index === 0"
|
||||
:is-last-element="index === elements.length - 1"
|
||||
:is-copying="copyingElementIndex === index"
|
||||
:application-context-additions="{
|
||||
recordIndexPath: [],
|
||||
}"
|
||||
@move="moveElement($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<AddElementModal ref="addElementModal" :page="page" />
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</template>
|
||||
|
|
|
@ -12,9 +12,8 @@
|
|||
<WidthSelector v-model="buttonWidth" />
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
property="button_width"
|
||||
v-model="values.button_width"
|
||||
:default-value="theme?.button_width"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
@ -29,9 +28,8 @@
|
|||
<HorizontalAlignmentsSelector v-model="values.button_alignment" />
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
property="button_alignment"
|
||||
v-model="values.button_alignment"
|
||||
:default-value="theme?.button_alignment"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
@ -48,12 +46,95 @@
|
|||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
property="button_text_alignment"
|
||||
v-model="values.button_text_alignment"
|
||||
:default-value="theme?.button_text_alignment"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
:label="$t('buttonThemeConfigBlock.fontFamily')"
|
||||
class="margin-bottom-1"
|
||||
>
|
||||
<FontFamilySelector v-model="values.button_font_family" />
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values.button_font_family"
|
||||
:default-value="theme?.button_font_family"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
:label="$t('buttonThemeConfigBlock.size')"
|
||||
:error-message="getError('button_font_size')"
|
||||
class="margin-bottom-1"
|
||||
>
|
||||
<PixelValueSelector v-model="values.button_font_size" />
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values.button_font_size"
|
||||
:default-value="theme?.button_font_size"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
:label="$t('buttonThemeConfigBlock.borderSize')"
|
||||
:error-message="getError('button_border_size')"
|
||||
class="margin-bottom-1"
|
||||
>
|
||||
<PixelValueSelector v-model="values.button_border_size" />
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values.button_border_size"
|
||||
:default-value="theme?.button_border_size"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
:label="$t('buttonThemeConfigBlock.borderRadius')"
|
||||
:error-message="getError('button_border_radius')"
|
||||
class="margin-bottom-1"
|
||||
>
|
||||
<PixelValueSelector v-model="values.button_border_radius" />
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values.button_border_radius"
|
||||
:default-value="theme?.button_border_radius"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
:label="$t('buttonThemeConfigBlock.padding')"
|
||||
:error-message="getPaddingError()"
|
||||
class="margin-bottom-1"
|
||||
>
|
||||
<PaddingSelector v-model="padding" />
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="padding"
|
||||
:default-value="
|
||||
theme
|
||||
? {
|
||||
vertical: theme['button_vertical_padding'],
|
||||
horizontal: theme['button_horizontal_padding'],
|
||||
}
|
||||
: undefined
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
</template>
|
||||
<template #preview>
|
||||
<ABButton>{{ $t('buttonThemeConfigBlock.button') }}</ABButton>
|
||||
</template>
|
||||
</ThemeConfigBlockSection>
|
||||
<ThemeConfigBlockSection :title="$t('buttonThemeConfigBlock.defaultState')">
|
||||
|
@ -73,9 +154,48 @@
|
|||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
property="button_background_color"
|
||||
v-model="values.button_background_color"
|
||||
:default-value="theme?.button_background_color"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
required
|
||||
class="margin-bottom-1"
|
||||
:label="$t('buttonThemeConfigBlock.textColor')"
|
||||
>
|
||||
<ColorInput
|
||||
v-model="values.button_text_color"
|
||||
:color-variables="colorVariables"
|
||||
:default-value="theme?.button_text_color"
|
||||
small
|
||||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values.button_text_color"
|
||||
:default-value="theme?.button_text_color"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
required
|
||||
class="margin-bottom-1"
|
||||
:label="$t('buttonThemeConfigBlock.borderColor')"
|
||||
>
|
||||
<ColorInput
|
||||
v-model="values.button_border_color"
|
||||
:color-variables="colorVariables"
|
||||
:default-value="theme?.button_border_color"
|
||||
small
|
||||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values.button_border_color"
|
||||
:default-value="theme?.button_border_color"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
@ -101,9 +221,46 @@
|
|||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
property="button_hover_background_color"
|
||||
v-model="values.button_hover_background_color"
|
||||
:default-value="theme?.button_hover_background_color"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('buttonThemeConfigBlock.textColor')"
|
||||
>
|
||||
<ColorInput
|
||||
v-model="values.button_hover_text_color"
|
||||
:color-variables="colorVariables"
|
||||
:default-value="theme?.button_hover_text_color"
|
||||
small
|
||||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values.button_hover_text_color"
|
||||
:default-value="theme?.button_hover_text_color"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('buttonThemeConfigBlock.borderColor')"
|
||||
>
|
||||
<ColorInput
|
||||
v-model="values.button_hover_border_color"
|
||||
:color-variables="colorVariables"
|
||||
:default-value="theme?.button_hover_border_color"
|
||||
small
|
||||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values.button_hover_border_color"
|
||||
:default-value="theme?.button_hover_border_color"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
@ -123,6 +280,36 @@ import ThemeConfigBlockSection from '@baserow/modules/builder/components/theme/T
|
|||
import ResetButton from '@baserow/modules/builder/components/theme/ResetButton'
|
||||
import HorizontalAlignmentsSelector from '@baserow/modules/builder/components/HorizontalAlignmentsSelector'
|
||||
import WidthSelector from '@baserow/modules/builder/components/WidthSelector'
|
||||
import FontFamilySelector from '@baserow/modules/builder/components/FontFamilySelector'
|
||||
import PixelValueSelector from '@baserow/modules/builder/components/PixelValueSelector'
|
||||
import PaddingSelector from '@baserow/modules/builder/components/PaddingSelector'
|
||||
import { required, integer, minValue, maxValue } from 'vuelidate/lib/validators'
|
||||
|
||||
const pixelSizeMin = 1
|
||||
const pixelSizeMax = 100
|
||||
|
||||
const minMax = {
|
||||
button_font_size: {
|
||||
min: 1,
|
||||
max: 100,
|
||||
},
|
||||
button_border_size: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
button_border_radius: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
button_horizontal_padding: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
button_vertical_padding: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'ButtonThemeConfigBlock',
|
||||
|
@ -131,18 +318,14 @@ export default {
|
|||
ResetButton,
|
||||
WidthSelector,
|
||||
HorizontalAlignmentsSelector,
|
||||
FontFamilySelector,
|
||||
PixelValueSelector,
|
||||
PaddingSelector,
|
||||
},
|
||||
mixins: [themeConfigBlock],
|
||||
data() {
|
||||
return {
|
||||
values: {},
|
||||
allowedValues: [
|
||||
'button_background_color',
|
||||
'button_hover_background_color',
|
||||
'button_text_alignment',
|
||||
'button_alignment',
|
||||
'button_width',
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -161,6 +344,73 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
get() {
|
||||
return {
|
||||
vertical: this.values.button_vertical_padding,
|
||||
horizontal: this.values.button_horizontal_padding,
|
||||
}
|
||||
},
|
||||
set(newValue) {
|
||||
this.values.button_vertical_padding = newValue.vertical
|
||||
this.values.button_horizontal_padding = newValue.horizontal
|
||||
},
|
||||
},
|
||||
pixedSizeMin() {
|
||||
return pixelSizeMin
|
||||
},
|
||||
pixedSizeMax() {
|
||||
return pixelSizeMax
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isAllowedKey(key) {
|
||||
return key.startsWith('button_')
|
||||
},
|
||||
getError(property) {
|
||||
if (this.$v.values[property].$invalid) {
|
||||
return this.$t('error.minMaxValueField', minMax[property])
|
||||
}
|
||||
return null
|
||||
},
|
||||
getPaddingError() {
|
||||
return (
|
||||
this.getError('button_vertical_padding') ||
|
||||
this.getError('button_horizontal_padding')
|
||||
)
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
values: {
|
||||
button_font_size: {
|
||||
required,
|
||||
integer,
|
||||
minValue: minValue(minMax.button_font_size.min),
|
||||
maxValue: maxValue(minMax.button_font_size.max),
|
||||
},
|
||||
button_border_size: {
|
||||
integer,
|
||||
minValue: minValue(minMax.button_border_size.min),
|
||||
maxValue: maxValue(minMax.button_border_size.max),
|
||||
},
|
||||
button_border_radius: {
|
||||
integer,
|
||||
minValue: minValue(minMax.button_border_radius.min),
|
||||
maxValue: maxValue(minMax.button_border_radius.max),
|
||||
},
|
||||
button_horizontal_padding: {
|
||||
required,
|
||||
integer,
|
||||
minValue: minValue(minMax.button_horizontal_padding.min),
|
||||
maxValue: maxValue(minMax.button_horizontal_padding.max),
|
||||
},
|
||||
button_vertical_padding: {
|
||||
required,
|
||||
integer,
|
||||
minValue: minValue(minMax.button_vertical_padding.min),
|
||||
maxValue: maxValue(minMax.button_vertical_padding.max),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,15 +1,54 @@
|
|||
<template>
|
||||
<ThemeConfigBlockSection>
|
||||
<template #default>
|
||||
<FormGroup horizontal :label="$t('colorThemeConfigBlock.primaryColor')">
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('colorThemeConfigBlock.primaryColor')"
|
||||
>
|
||||
<ColorInput v-model="values.primary_color" small />
|
||||
</FormGroup>
|
||||
<FormGroup horizontal :label="$t('colorThemeConfigBlock.secondaryColor')">
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('colorThemeConfigBlock.secondaryColor')"
|
||||
>
|
||||
<ColorInput v-model="values.secondary_color" small />
|
||||
</FormGroup>
|
||||
<FormGroup horizontal :label="$t('colorThemeConfigBlock.borderColor')">
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('colorThemeConfigBlock.borderColor')"
|
||||
>
|
||||
<ColorInput v-model="values.border_color" small />
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('colorThemeConfigBlock.successColor')"
|
||||
>
|
||||
<ColorInput v-model="values.main_success_color" small />
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('colorThemeConfigBlock.warningColor')"
|
||||
>
|
||||
<ColorInput v-model="values.main_warning_color" small />
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('colorThemeConfigBlock.errorColor')"
|
||||
>
|
||||
<ColorInput v-model="values.main_error_color" small />
|
||||
</FormGroup>
|
||||
</template>
|
||||
</ThemeConfigBlockSection>
|
||||
</template>
|
||||
|
@ -26,8 +65,15 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
values: {},
|
||||
allowedValues: ['primary_color', 'secondary_color', 'border_color'],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isAllowedKey(key) {
|
||||
return (
|
||||
key.startsWith('main_') ||
|
||||
['primary_color', 'secondary_color', 'border_color'].includes(key)
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -13,9 +13,8 @@
|
|||
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
property="image_alignment"
|
||||
v-model="values.image_alignment"
|
||||
:default-value="theme?.image_alignment"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
@ -30,7 +29,8 @@
|
|||
v-model="values.image_max_width"
|
||||
small
|
||||
type="number"
|
||||
:error="
|
||||
remove-number-input-controls
|
||||
:error-message="
|
||||
$v.values.image_max_width.$dirty &&
|
||||
!$v.values.image_max_width.integer
|
||||
? $t('error.integerField')
|
||||
|
@ -50,9 +50,8 @@
|
|||
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
property="image_max_width"
|
||||
v-model="values.image_max_width"
|
||||
:default-value="theme?.image_max_width"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
@ -67,7 +66,8 @@
|
|||
v-model="imageMaxHeight"
|
||||
small
|
||||
type="number"
|
||||
:error="
|
||||
remove-number-input-controls
|
||||
:error-message="
|
||||
$v.values.image_max_height.$dirty &&
|
||||
!$v.values.image_max_height.integer
|
||||
? $t('error.integerField')
|
||||
|
@ -85,9 +85,8 @@
|
|||
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
property="image_max_height"
|
||||
v-model="imageMaxHeight"
|
||||
:default-value="theme?.image_max_height"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
@ -122,8 +121,7 @@
|
|||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="imageConstraintForReset"
|
||||
:theme="theme"
|
||||
property="image_constraint"
|
||||
:default-value="theme?.image_constraint"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
@ -191,18 +189,18 @@ export default {
|
|||
},
|
||||
imageConstraintForReset: {
|
||||
get() {
|
||||
return { image_constraint: this.values.image_constraint }
|
||||
return this.values.image_constraint
|
||||
},
|
||||
set(value) {
|
||||
if (value.image_constraint === 'contain') {
|
||||
if (value === 'contain') {
|
||||
// Reset the height as we can't have a max height with contain
|
||||
this.values.image_max_height = null
|
||||
}
|
||||
if (value.image_constraint === 'cover') {
|
||||
if (value === 'cover') {
|
||||
// Set the height to what is defined in theme
|
||||
this.values.image_max_height = this.theme.image_max_height
|
||||
}
|
||||
this.values.image_constraint = value.image_constraint
|
||||
this.values.image_constraint = value
|
||||
},
|
||||
},
|
||||
IMAGE_SOURCE_TYPES() {
|
||||
|
@ -235,6 +233,9 @@ export default {
|
|||
)
|
||||
}
|
||||
},
|
||||
isAllowedKey(key) {
|
||||
return key.startsWith('image_')
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
values: {
|
||||
|
|
|
@ -6,15 +6,29 @@
|
|||
horizontal
|
||||
small-label
|
||||
required
|
||||
:label="$t('linkThemeConfigBlock.alignment')"
|
||||
class="margin-bottom-1"
|
||||
:label="$t('linkThemeConfigBlock.fontFamily')"
|
||||
>
|
||||
<FontFamilySelector v-model="values.link_font_family" />
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values.link_font_family"
|
||||
:default-value="theme?.link_font_family"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
required
|
||||
class="margin-bottom-1"
|
||||
:label="$t('linkThemeConfigBlock.alignment')"
|
||||
>
|
||||
<HorizontalAlignmentsSelector v-model="values.link_text_alignment" />
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
property="link_text_alignment"
|
||||
v-model="values.link_text_alignment"
|
||||
:default-value="theme?.link_text_alignment"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
@ -37,9 +51,8 @@
|
|||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
property="link_text_color"
|
||||
v-model="values.link_text_color"
|
||||
:default-value="theme?.link_text_color"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
@ -65,9 +78,8 @@
|
|||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
property="link_hover_text_color"
|
||||
v-model="values.link_hover_text_color"
|
||||
:default-value="theme?.link_hover_text_color"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
@ -86,6 +98,7 @@ 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 FontFamilySelector from '@baserow/modules/builder/components/FontFamilySelector'
|
||||
|
||||
export default {
|
||||
name: 'LinkThemeConfigBlock',
|
||||
|
@ -93,17 +106,18 @@ export default {
|
|||
ThemeConfigBlockSection,
|
||||
ResetButton,
|
||||
HorizontalAlignmentsSelector,
|
||||
FontFamilySelector,
|
||||
},
|
||||
mixins: [themeConfigBlock],
|
||||
data() {
|
||||
return {
|
||||
values: {},
|
||||
allowedValues: [
|
||||
'link_text_color',
|
||||
'link_hover_text_color',
|
||||
'link_text_alignment',
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isAllowedKey(key) {
|
||||
return key.startsWith('link_')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,215 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<ColorPickerContext
|
||||
ref="colorPicker"
|
||||
:value="builder.theme[colorPickerPropertyName] || '#000000ff'"
|
||||
@input="colorPickerColorChanged"
|
||||
></ColorPickerContext>
|
||||
<div class="theme-settings__section margin-bottom-3">
|
||||
<div class="theme-settings__section-properties">
|
||||
<a
|
||||
class="theme-settings__section-title"
|
||||
@click="toggleClosed('colors')"
|
||||
>
|
||||
{{ $t('mainThemeConfigBlock.colorsLabel') }}
|
||||
<i
|
||||
class="iconoir-nav-arrow-down theme-settings__section-title-icon"
|
||||
:class="{
|
||||
'theme-settings__section-title-icon': true,
|
||||
'iconoir-nav-arrow-down': !isClosed('colors'),
|
||||
'iconoir-nav-arrow-right': isClosed('colors'),
|
||||
}"
|
||||
></i>
|
||||
</a>
|
||||
<div v-show="!isClosed('colors')">
|
||||
<ColorInputGroup
|
||||
:value="builder.theme.primary_color"
|
||||
label-after
|
||||
:label="$t('mainThemeConfigBlock.primaryColor')"
|
||||
class="margin-bottom-1"
|
||||
@input="setPropertyInStore('primary_color', $event)"
|
||||
/>
|
||||
<ColorInputGroup
|
||||
:value="builder.theme.secondary_color"
|
||||
label-after
|
||||
:label="$t('mainThemeConfigBlock.secondaryColor')"
|
||||
@input="setPropertyInStore('secondary_color', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="theme-settings__section">
|
||||
<div class="theme-settings__section-properties">
|
||||
<a
|
||||
class="theme-settings__section-title"
|
||||
@click="toggleClosed('typography')"
|
||||
>
|
||||
{{ $t('mainThemeConfigBlock.typography') }}
|
||||
<i
|
||||
class="iconoir-nav-arrow-down theme-settings__section-title-icon"
|
||||
:class="{
|
||||
'theme-settings__section-title-icon': true,
|
||||
'iconoir-nav-arrow-down': !isClosed('typography'),
|
||||
'iconoir-nav-arrow-right': isClosed('typography'),
|
||||
}"
|
||||
></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="i in headings"
|
||||
v-show="!isClosed('typography')"
|
||||
:key="i"
|
||||
class="theme-settings__section"
|
||||
>
|
||||
<div class="theme-settings__section-properties">
|
||||
<FormGroup
|
||||
:label="$t('mainThemeConfigBlock.headingLabel', { i })"
|
||||
small-label
|
||||
required
|
||||
:error="$v.builder.theme[`heading_${i}_font_size`].$error"
|
||||
class="margin-bottom-2"
|
||||
>
|
||||
<div class="theme-settings__inputs-wrapper">
|
||||
<ColorInput
|
||||
:value="builder.theme[`heading_${i}_color`]"
|
||||
@input="setPropertyInStore(`heading_${i}_color`, $event)"
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
class="theme-settings__input-font-size"
|
||||
type="number"
|
||||
size="large"
|
||||
remove-number-input-controls
|
||||
:min="fontSizeMin"
|
||||
:max="fontSizeMax"
|
||||
:value="builder.theme[`heading_${i}_font_size`]"
|
||||
:error="$v.builder.theme[`heading_${i}_font_size`].$error"
|
||||
@input="
|
||||
;[
|
||||
$v.builder.theme[`heading_${i}_font_size`].$touch(),
|
||||
setPropertyInStore(
|
||||
`heading_${i}_font_size`,
|
||||
$event,
|
||||
!$v.builder.theme[`heading_${i}_font_size`].$error
|
||||
),
|
||||
]
|
||||
"
|
||||
>
|
||||
<template #suffix>px</template></FormInput
|
||||
>
|
||||
</div>
|
||||
|
||||
<template #error>
|
||||
{{ $t('error.minMaxLength', { min: 1, max: 100 }) }}
|
||||
</template>
|
||||
</FormGroup>
|
||||
</div>
|
||||
<div class="theme-settings__section-preview">
|
||||
<component
|
||||
:is="`h${i}`"
|
||||
class="margin-bottom-2 theme-settings__section-ellipsis"
|
||||
:class="`ab-heading--h${i}`"
|
||||
:style="{
|
||||
[`--heading-h${i}-color`]: builder.theme[`heading_${i}_color`],
|
||||
[`--heading-h${i}-font-size`]:
|
||||
builder.theme[`heading_${i}_font_size`] + 'px',
|
||||
}"
|
||||
>
|
||||
{{ $t('mainThemeConfigBlock.headingValue', { i }) }}
|
||||
</component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex'
|
||||
import { required, integer, minValue, maxValue } from 'vuelidate/lib/validators'
|
||||
import ColorPickerContext from '@baserow/modules/core/components/ColorPickerContext'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
|
||||
const fontSizeMin = 1
|
||||
const fontSizeMax = 100
|
||||
const headings = [1, 2, 3]
|
||||
|
||||
export default {
|
||||
name: 'MainThemeConfigBlock',
|
||||
components: { ColorPickerContext },
|
||||
props: {
|
||||
builder: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
closed: [],
|
||||
colorPickerPropertyName: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
headings() {
|
||||
return headings
|
||||
},
|
||||
fontSizeMin() {
|
||||
return fontSizeMin
|
||||
},
|
||||
fontSizeMax() {
|
||||
return fontSizeMax
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
setThemeProperty: 'theme/setProperty',
|
||||
forceSetThemeProperty: 'theme/forceSetProperty',
|
||||
}),
|
||||
toggleClosed(value) {
|
||||
const index = this.closed.indexOf(value)
|
||||
if (index < 0) {
|
||||
this.closed.push(value)
|
||||
} else {
|
||||
this.closed.splice(index, 1)
|
||||
}
|
||||
},
|
||||
isClosed(value) {
|
||||
return this.closed.includes(value)
|
||||
},
|
||||
openColorPicker(opener, propertyName) {
|
||||
this.colorPickerPropertyName = propertyName
|
||||
this.$refs.colorPicker.toggle(opener)
|
||||
},
|
||||
colorPickerColorChanged(value) {
|
||||
this.setPropertyInStore(this.colorPickerPropertyName, value)
|
||||
},
|
||||
async setPropertyInStore(key, value, makeRequest = true) {
|
||||
const action = makeRequest ? 'setThemeProperty' : 'forceSetThemeProperty'
|
||||
|
||||
try {
|
||||
await this[action]({
|
||||
builder: this.builder,
|
||||
key,
|
||||
value,
|
||||
})
|
||||
} catch (error) {
|
||||
notifyIf(error, 'row')
|
||||
}
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
builder: {
|
||||
theme: headings.reduce((o, i) => {
|
||||
o[`heading_${i}_font_size`] = {
|
||||
required,
|
||||
integer,
|
||||
minValue: minValue(fontSizeMin),
|
||||
maxValue: maxValue(fontSizeMax),
|
||||
}
|
||||
return o
|
||||
}, {}),
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<ThemeConfigBlockSection>
|
||||
<template #default>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('pageThemeConfigBlock.backgroundColor')"
|
||||
>
|
||||
<ColorInput
|
||||
v-model="values.page_background_color"
|
||||
small
|
||||
:allow-opacity="false"
|
||||
:color-variables="colorVariables"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('pageThemeConfigBlock.backgroundImage')"
|
||||
>
|
||||
<ImageInput v-model="values.page_background_file" />
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
v-if="values.page_background_file"
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('pageThemeConfigBlock.backgroundMode')"
|
||||
>
|
||||
<RadioGroup
|
||||
v-model="values.page_background_mode"
|
||||
type="button"
|
||||
:options="backgroundModes"
|
||||
/>
|
||||
</FormGroup>
|
||||
</template>
|
||||
</ThemeConfigBlockSection>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import themeConfigBlock from '@baserow/modules/builder/mixins/themeConfigBlock'
|
||||
import ThemeConfigBlockSection from '@baserow/modules/builder/components/theme/ThemeConfigBlockSection'
|
||||
import { BACKGROUND_MODES } from '@baserow/modules/builder/enums'
|
||||
|
||||
export default {
|
||||
name: 'PageThemeConfigBlock',
|
||||
components: { ThemeConfigBlockSection },
|
||||
mixins: [themeConfigBlock],
|
||||
data() {
|
||||
return {
|
||||
values: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backgroundModes() {
|
||||
return [
|
||||
{
|
||||
label: this.$t('backgroundModes.tile'),
|
||||
value: BACKGROUND_MODES.TILE,
|
||||
},
|
||||
{
|
||||
label: this.$t('backgroundModes.fill'),
|
||||
value: BACKGROUND_MODES.FILL,
|
||||
},
|
||||
{
|
||||
label: this.$t('backgroundModes.fit'),
|
||||
value: BACKGROUND_MODES.FIT,
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isAllowedKey(key) {
|
||||
return key.startsWith('page_')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -13,25 +13,25 @@ import _ from 'lodash'
|
|||
export default {
|
||||
inject: ['builder'],
|
||||
props: {
|
||||
theme: { type: Object, required: false, default: null },
|
||||
property: { type: String, required: true },
|
||||
value: { type: Object, required: true },
|
||||
value: { required: true, validator: (v) => true },
|
||||
defaultValue: {
|
||||
required: false,
|
||||
validator: (v) => true,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
propertyModified() {
|
||||
if (!this.theme) {
|
||||
return false
|
||||
}
|
||||
return !_.isEqual(this.value[this.property], this.theme[this.property])
|
||||
return (
|
||||
this.defaultValue !== undefined &&
|
||||
!_.isEqual(this.value, this.defaultValue)
|
||||
)
|
||||
},
|
||||
resetProperty() {
|
||||
this.$emit('input', {
|
||||
...this.value,
|
||||
[this.property]: this.theme[this.property],
|
||||
})
|
||||
this.$emit('input', this.defaultValue)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -3,54 +3,66 @@
|
|||
<ThemeConfigBlockSection
|
||||
v-if="showBody"
|
||||
:title="$t('typographyThemeConfigBlock.bodyLabel')"
|
||||
class="margin-bottom-2"
|
||||
>
|
||||
<template #default>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('typographyThemeConfigBlock.fontFamily')"
|
||||
>
|
||||
<FontFamilySelector v-model="values.body_font_family" />
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values.body_font_family"
|
||||
:default-value="theme?.body_font_family"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('typographyThemeConfigBlock.textAlignment')"
|
||||
>
|
||||
<HorizontalAlignmentsSelector
|
||||
v-model="values[`body_text_alignment`]"
|
||||
<HorizontalAlignmentsSelector v-model="values.body_text_alignment" />
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values.body_text_alignment"
|
||||
:default-value="theme?.body_text_alignment"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('typographyThemeConfigBlock.size')"
|
||||
:error-message="
|
||||
$v.values[`body_font_size`].$invalid
|
||||
? $t('error.minMaxValueField', {
|
||||
min: fontSizeMin,
|
||||
max: bodyFontSizeMax,
|
||||
})
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<PixelValueSelector
|
||||
v-model="values.body_font_size"
|
||||
class="typography-theme-config-block__input-number"
|
||||
@blur="$v.values[`body_font_size`].$touch()"
|
||||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
:property="`body_text_alignment`"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
:label="$t('typographyThemeConfigBlock.size')"
|
||||
:error="$v.values[`body_font_size`].$invalid"
|
||||
>
|
||||
<FormInput
|
||||
v-model="values[`body_font_size`]"
|
||||
type="number"
|
||||
remove-number-input-controls
|
||||
:min="fontSizeMin"
|
||||
:max="fontSizeMax"
|
||||
:error="$v.values[`body_font_size`].$invalid"
|
||||
@blur="$v.values[`body_font_size`].$touch()"
|
||||
>
|
||||
<template #suffix>px</template>
|
||||
</FormInput>
|
||||
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
:property="`body_font_size`"
|
||||
v-model="values.body_font_size"
|
||||
:default-value="theme?.body_font_size"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('typographyThemeConfigBlock.color')"
|
||||
>
|
||||
<ColorInput
|
||||
|
@ -61,9 +73,8 @@
|
|||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
:property="'body_text_color'"
|
||||
v-model="values.body_text_color"
|
||||
:default-value="theme?.body_text_color"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
@ -75,87 +86,103 @@
|
|||
</ABParagraph>
|
||||
</template>
|
||||
</ThemeConfigBlockSection>
|
||||
<ThemeConfigBlockSection
|
||||
v-for="level in headings"
|
||||
:key="level"
|
||||
:title="$t('typographyThemeConfigBlock.headingLabel', { i: level })"
|
||||
class="margin-bottom-2"
|
||||
>
|
||||
<template #default>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
:label="$t('typographyThemeConfigBlock.textAlignment')"
|
||||
>
|
||||
<HorizontalAlignmentsSelector
|
||||
v-model="values[`heading_${level}_text_alignment`]"
|
||||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
:property="`heading_${level}_text_alignment`"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
:label="$t('typographyThemeConfigBlock.size')"
|
||||
:error="$v.values[`heading_${level}_font_size`].$invalid"
|
||||
>
|
||||
<FormInput
|
||||
v-model="values[`heading_${level}_font_size`]"
|
||||
type="number"
|
||||
remove-number-input-controls
|
||||
:min="fontSizeMin"
|
||||
:max="fontSizeMax"
|
||||
:error="$v.values[`heading_${level}_font_size`].$invalid"
|
||||
@blur="$v.values[`heading_${level}_font_size`].$touch()"
|
||||
<template v-if="showHeadings">
|
||||
<ThemeConfigBlockSection
|
||||
v-for="level in headings"
|
||||
:key="level"
|
||||
:title="$t('typographyThemeConfigBlock.headingLabel', { i: level })"
|
||||
>
|
||||
<template #default>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('typographyThemeConfigBlock.fontFamily')"
|
||||
>
|
||||
<template #suffix>px</template>
|
||||
</FormInput>
|
||||
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
:property="`body_font_size`"
|
||||
<FontFamilySelector
|
||||
v-model="values[`heading_${level}_font_family`]"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
:label="$t('typographyThemeConfigBlock.color')"
|
||||
>
|
||||
<ColorInput
|
||||
v-model="values[`heading_${level}_text_color`]"
|
||||
:color-variables="colorVariables"
|
||||
:default-value="theme ? theme[`heading_${level}_text_color`] : null"
|
||||
small
|
||||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values"
|
||||
:theme="theme"
|
||||
:property="`heading_${level}_text_color`"
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values[`heading_${level}_font_family`]"
|
||||
:default-value="theme?.[`heading_${level}_font_family`]"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('typographyThemeConfigBlock.textAlignment')"
|
||||
>
|
||||
<HorizontalAlignmentsSelector
|
||||
v-model="values[`heading_${level}_text_alignment`]"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
</template>
|
||||
<template #preview>
|
||||
<component
|
||||
:is="`h${level}`"
|
||||
class="margin-bottom-2 theme-settings__section-ellipsis"
|
||||
:class="`ab-heading--h${level}`"
|
||||
>
|
||||
{{ $t('typographyThemeConfigBlock.headingValue', { i: level }) }}
|
||||
</component>
|
||||
</template>
|
||||
</ThemeConfigBlockSection>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values[`heading_${level}_text_alignment`]"
|
||||
:default-value="theme?.[`heading_${level}_text_alignment`]"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('typographyThemeConfigBlock.size')"
|
||||
:error-message="
|
||||
$v.values[`heading_${level}_font_size`].$invalid
|
||||
? $t('error.minMaxValueField', {
|
||||
min: fontSizeMin,
|
||||
max: fontSizeMax,
|
||||
})
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<PixelValueSelector
|
||||
v-model="values[`heading_${level}_font_size`]"
|
||||
class="typography-theme-config-block__input-number"
|
||||
@blur="$v.values[`heading_${level}_font_size`].$touch()"
|
||||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values[`heading_${level}_font_size`]"
|
||||
:default-value="theme?.[`heading_${level}_font_size`]"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
horizontal
|
||||
small-label
|
||||
class="margin-bottom-1"
|
||||
:label="$t('typographyThemeConfigBlock.color')"
|
||||
>
|
||||
<ColorInput
|
||||
v-model="values[`heading_${level}_text_color`]"
|
||||
:color-variables="colorVariables"
|
||||
:default-value="
|
||||
theme ? theme[`heading_${level}_text_color`] : null
|
||||
"
|
||||
small
|
||||
/>
|
||||
<template #after-input>
|
||||
<ResetButton
|
||||
v-model="values[`heading_${level}_text_color`]"
|
||||
:default-value="theme?.[`heading_${level}_text_color`]"
|
||||
/>
|
||||
</template>
|
||||
</FormGroup>
|
||||
</template>
|
||||
<template #preview>
|
||||
<ABHeading
|
||||
class="typography-theme-config-block__heading-preview"
|
||||
:level="level"
|
||||
>
|
||||
{{ $t('typographyThemeConfigBlock.headingValue', { i: level }) }}
|
||||
</ABHeading>
|
||||
</template>
|
||||
</ThemeConfigBlockSection>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -165,9 +192,12 @@ 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 FontFamilySelector from '@baserow/modules/builder/components/FontFamilySelector'
|
||||
import PixelValueSelector from '@baserow/modules/builder/components/PixelValueSelector'
|
||||
|
||||
const fontSizeMin = 1
|
||||
const fontSizeMax = 100
|
||||
const bodyFontSizeMax = 30
|
||||
const headings = [1, 2, 3, 4, 5, 6]
|
||||
|
||||
export default {
|
||||
|
@ -176,23 +206,13 @@ export default {
|
|||
ThemeConfigBlockSection,
|
||||
ResetButton,
|
||||
HorizontalAlignmentsSelector,
|
||||
FontFamilySelector,
|
||||
PixelValueSelector,
|
||||
},
|
||||
mixins: [themeConfigBlock],
|
||||
data() {
|
||||
return {
|
||||
values: {},
|
||||
allowedValues: [
|
||||
...headings
|
||||
.map((level) => [
|
||||
`heading_${level}_text_color`,
|
||||
`heading_${level}_font_size`,
|
||||
`heading_${level}_text_alignment`,
|
||||
])
|
||||
.flat(),
|
||||
'body_font_size',
|
||||
'body_text_alignment',
|
||||
'body_text_color',
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -206,12 +226,23 @@ export default {
|
|||
showBody() {
|
||||
return !this.extraArgs?.headingLevel
|
||||
},
|
||||
showHeadings() {
|
||||
return !this.extraArgs?.onlyBody
|
||||
},
|
||||
fontSizeMin() {
|
||||
return fontSizeMin
|
||||
},
|
||||
fontSizeMax() {
|
||||
return fontSizeMax
|
||||
},
|
||||
bodyFontSizeMax() {
|
||||
return bodyFontSizeMax
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isAllowedKey(key) {
|
||||
return key.startsWith('heading_') || key.startsWith('body_')
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
values: {
|
||||
|
@ -228,7 +259,7 @@ export default {
|
|||
required,
|
||||
integer,
|
||||
minValue: minValue(fontSizeMin),
|
||||
maxValue: maxValue(fontSizeMax),
|
||||
maxValue: maxValue(bodyFontSizeMax),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -73,12 +73,18 @@ export class ElementType extends Registerable {
|
|||
'style_padding_bottom',
|
||||
'style_padding_left',
|
||||
'style_padding_right',
|
||||
'style_margin_top',
|
||||
'style_margin_bottom',
|
||||
'style_margin_left',
|
||||
'style_margin_right',
|
||||
'style_border_top',
|
||||
'style_border_bottom',
|
||||
'style_border_left',
|
||||
'style_border_right',
|
||||
'style_background',
|
||||
'style_background_color',
|
||||
'style_background_file',
|
||||
'style_background_mode',
|
||||
'style_width',
|
||||
]
|
||||
}
|
||||
|
|
|
@ -72,15 +72,22 @@ export const WIDTHS_NEW = {
|
|||
}
|
||||
|
||||
export const BACKGROUND_TYPES = {
|
||||
NONE: { value: 'none', name: 'backgroundTypes.none' },
|
||||
COLOR: { value: 'color', name: 'backgroundTypes.color' },
|
||||
NONE: 'none',
|
||||
COLOR: 'color',
|
||||
}
|
||||
|
||||
export const BACKGROUND_MODES = {
|
||||
FILL: 'fill',
|
||||
TILE: 'tile',
|
||||
FIT: 'fit',
|
||||
}
|
||||
|
||||
export const WIDTH_TYPES = {
|
||||
FULL: { value: 'full', name: 'widthTypes.full' },
|
||||
NORMAL: { value: 'normal', name: 'widthTypes.normal' },
|
||||
MEDIUM: { value: 'medium', name: 'widthTypes.medium' },
|
||||
SMALL: { value: 'small', name: 'widthTypes.small' },
|
||||
MEDIUM: { value: 'medium', name: 'widthTypes.medium' },
|
||||
NORMAL: { value: 'normal', name: 'widthTypes.normal' },
|
||||
FULL: { value: 'full', name: 'widthTypes.fullBleed' },
|
||||
FULL_WIDTH: { value: 'full-width', name: 'widthTypes.fullWidth' },
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
131
web-frontend/modules/builder/fontFamilyTypes.js
Normal file
131
web-frontend/modules/builder/fontFamilyTypes.js
Normal file
|
@ -0,0 +1,131 @@
|
|||
import { Registerable } from '@baserow/modules/core/registry'
|
||||
|
||||
export class FontFamilyType extends Registerable {
|
||||
get name() {
|
||||
return ''
|
||||
}
|
||||
|
||||
get safeFont() {
|
||||
return 'sans-serif'
|
||||
}
|
||||
}
|
||||
|
||||
export class InterFontFamilyType extends FontFamilyType {
|
||||
static getType() {
|
||||
return 'inter'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'Inter'
|
||||
}
|
||||
}
|
||||
|
||||
export class ArialFontFamilyType extends FontFamilyType {
|
||||
static getType() {
|
||||
return 'arial'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'Arial'
|
||||
}
|
||||
}
|
||||
|
||||
export class VerdanaFontFamilyType extends FontFamilyType {
|
||||
static getType() {
|
||||
return 'verdana'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'Verdana'
|
||||
}
|
||||
}
|
||||
|
||||
export class TahomaFontFamilyType extends FontFamilyType {
|
||||
static getType() {
|
||||
return 'tahoma'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'Tahoma'
|
||||
}
|
||||
}
|
||||
|
||||
export class TrebuchetMSFontFamilyType extends FontFamilyType {
|
||||
static getType() {
|
||||
return 'trebuchet_ms'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'Trebuchet MS'
|
||||
}
|
||||
}
|
||||
|
||||
export class TimesNewRomanFontFamilyType extends FontFamilyType {
|
||||
static getType() {
|
||||
return 'times_new_roman'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'Times new roman'
|
||||
}
|
||||
|
||||
get safeFont() {
|
||||
return 'serif'
|
||||
}
|
||||
}
|
||||
|
||||
export class GeorgiaFontFamilyType extends FontFamilyType {
|
||||
static getType() {
|
||||
return 'georgia'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'Georgia'
|
||||
}
|
||||
|
||||
get safeFont() {
|
||||
return 'serif'
|
||||
}
|
||||
}
|
||||
|
||||
export class GaramondFontFamilyType extends FontFamilyType {
|
||||
static getType() {
|
||||
return 'garamond'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'Garamond'
|
||||
}
|
||||
|
||||
get safeFont() {
|
||||
return 'serif'
|
||||
}
|
||||
}
|
||||
|
||||
export class CourierNewFontFamilyType extends FontFamilyType {
|
||||
static getType() {
|
||||
return 'courier_new'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'Courier new'
|
||||
}
|
||||
|
||||
get safeFont() {
|
||||
return 'monospace'
|
||||
}
|
||||
}
|
||||
|
||||
export class BrushScriptMTFontFamilyType extends FontFamilyType {
|
||||
static getType() {
|
||||
return 'brush_script_mt'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'Brush Script MT'
|
||||
}
|
||||
|
||||
get safeFont() {
|
||||
return 'cursive'
|
||||
}
|
||||
}
|
|
@ -159,10 +159,6 @@
|
|||
"imageElement": {
|
||||
"emptyState": "No alt text defined..."
|
||||
},
|
||||
"imageInput": {
|
||||
"labelDescription": "Default description",
|
||||
"labelButton": "Upload"
|
||||
},
|
||||
"generalForm": {
|
||||
"labelTitle": "Label",
|
||||
"labelPlaceholder": "Enter a label (optional)",
|
||||
|
@ -300,7 +296,8 @@
|
|||
"color": "Color"
|
||||
},
|
||||
"widthTypes": {
|
||||
"full": "Full width",
|
||||
"fullBleed": "Full bleed",
|
||||
"fullWidth": "Full width",
|
||||
"normal": "Normal",
|
||||
"medium": "Medium",
|
||||
"small": "Small"
|
||||
|
@ -364,14 +361,19 @@
|
|||
"boxRight": "Right",
|
||||
"backgroundLabel": "Background",
|
||||
"backgroundColor": "Background color",
|
||||
"widthLabel": "Width"
|
||||
"widthLabel": "Width",
|
||||
"backgroundImage": "Image",
|
||||
"backgroundImageMode": "Fill mode"
|
||||
},
|
||||
"styleBoxForm": {
|
||||
"borderLabel": "Border",
|
||||
"paddingLabel": "Padding"
|
||||
"borderColor": "Border color",
|
||||
"borderLabel": "Size",
|
||||
"paddingLabel": "Padding",
|
||||
"marginLabel": "Margin"
|
||||
},
|
||||
"themeConfigBlockType": {
|
||||
"color": "Colors",
|
||||
"page": "Page",
|
||||
"typography": "Typography",
|
||||
"button": "Button",
|
||||
"link": "Link",
|
||||
|
@ -380,12 +382,23 @@
|
|||
"colorThemeConfigBlock": {
|
||||
"primaryColor": "Primary",
|
||||
"secondaryColor": "Secondary",
|
||||
"borderColor": "Border"
|
||||
"borderColor": "Border",
|
||||
"successColor": "Success",
|
||||
"warningColor": "Warning",
|
||||
"errorColor": "Error"
|
||||
},
|
||||
"pageThemeConfigBlock": {
|
||||
"backgroundColor": "Background color",
|
||||
"backgroundImage": "Background image",
|
||||
"backgroundMode": "Background mode"
|
||||
},
|
||||
"colorThemeConfigBlockType": {
|
||||
"primary": "Primary",
|
||||
"secondary": "Secondary",
|
||||
"border": "Border"
|
||||
"border": "Border",
|
||||
"success": "Success",
|
||||
"warning": "Warning",
|
||||
"error": "Error"
|
||||
},
|
||||
"typographyThemeConfigBlock": {
|
||||
"headingLabel": "Heading {i} (h{i})",
|
||||
|
@ -393,7 +406,8 @@
|
|||
"color": "Color",
|
||||
"size": "Size",
|
||||
"textAlignment": "Alignment",
|
||||
"bodyLabel": "Body"
|
||||
"bodyLabel": "Body",
|
||||
"fontFamily": "Font"
|
||||
},
|
||||
"buttonThemeConfigBlock": {
|
||||
"backgroundColor": "Background color",
|
||||
|
@ -402,14 +416,22 @@
|
|||
"hoverState": "Hover state",
|
||||
"textAlignment": "Text alignment",
|
||||
"alignment": "Alignment",
|
||||
"width": "Width"
|
||||
"width": "Width",
|
||||
"textColor": "Text color",
|
||||
"borderColor": "Border color",
|
||||
"borderSize": "Border size",
|
||||
"borderRadius": "Border radius",
|
||||
"padding": "Padding",
|
||||
"fontFamily": "Font",
|
||||
"size": "Font size"
|
||||
},
|
||||
"linkThemeConfigBlock": {
|
||||
"color": "Color",
|
||||
"link": "Link",
|
||||
"defaultState": "Default state",
|
||||
"hoverState": "Hover state",
|
||||
"alignment": "Alignment"
|
||||
"alignment": "Alignment",
|
||||
"fontFamily": "Font"
|
||||
},
|
||||
"imageThemeConfigBlock": {
|
||||
"alignment": "Alignment",
|
||||
|
@ -644,5 +666,13 @@
|
|||
},
|
||||
"resetButton": {
|
||||
"reset": "Reset to default theme value"
|
||||
},
|
||||
"backgroundModes": {
|
||||
"fill": "Fill",
|
||||
"tile": "Tile",
|
||||
"fit": "Fit"
|
||||
},
|
||||
"customStyle": {
|
||||
"themeOverrides": "Theme overrides"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ export default {
|
|||
getStyleOverride(key, colorVariables = null) {
|
||||
return ThemeConfigBlockType.getAllStyles(
|
||||
this.themeConfigBlocks,
|
||||
this.element.styles[key] || {},
|
||||
this.element.styles?.[key] || {},
|
||||
colorVariables || this.colorVariables,
|
||||
this.builder.theme
|
||||
)
|
||||
|
|
|
@ -62,6 +62,7 @@ export default {
|
|||
},
|
||||
getBoxStyleValue(pos) {
|
||||
return {
|
||||
margin: this.defaultValues[`style_margin_${pos}`],
|
||||
padding: this.defaultValues[`style_padding_${pos}`],
|
||||
border_color: this.defaultValues[`style_border_${pos}_color`],
|
||||
border_size: this.defaultValues[`style_border_${pos}_size`],
|
||||
|
@ -69,6 +70,7 @@ export default {
|
|||
},
|
||||
setBoxStyleValue(pos, newValue) {
|
||||
if (newValue.padding !== undefined) {
|
||||
this.values[`style_margin_${pos}`] = newValue.margin
|
||||
this.values[`style_padding_${pos}`] = newValue.padding
|
||||
this.values[`style_border_${pos}_color`] = newValue.border_color
|
||||
this.values[`style_border_${pos}_size`] = newValue.border_size
|
||||
|
@ -104,7 +106,8 @@ export default {
|
|||
},
|
||||
getValuesFromElement(allowedValues) {
|
||||
return allowedValues.reduce((obj, value) => {
|
||||
obj[value] = this.element[value] || null
|
||||
obj[value] =
|
||||
this.element[value] === undefined ? null : this.element[value]
|
||||
return obj
|
||||
}, {})
|
||||
},
|
||||
|
|
|
@ -90,6 +90,7 @@ import {
|
|||
ButtonThemeConfigBlockType,
|
||||
LinkThemeConfigBlockType,
|
||||
ImageThemeConfigBlockType,
|
||||
PageThemeConfigBlockType,
|
||||
} from '@baserow/modules/builder/themeConfigBlockTypes'
|
||||
import {
|
||||
CreateRowWorkflowActionType,
|
||||
|
@ -108,6 +109,19 @@ import {
|
|||
TagsCollectionFieldType,
|
||||
} from '@baserow/modules/builder/collectionFieldTypes'
|
||||
|
||||
import {
|
||||
InterFontFamilyType,
|
||||
ArialFontFamilyType,
|
||||
VerdanaFontFamilyType,
|
||||
TahomaFontFamilyType,
|
||||
TrebuchetMSFontFamilyType,
|
||||
TimesNewRomanFontFamilyType,
|
||||
GeorgiaFontFamilyType,
|
||||
GaramondFontFamilyType,
|
||||
CourierNewFontFamilyType,
|
||||
BrushScriptMTFontFamilyType,
|
||||
} from '@baserow/modules/builder/fontFamilyTypes'
|
||||
|
||||
export default (context) => {
|
||||
const { store, app, isDev } = context
|
||||
|
||||
|
@ -146,6 +160,7 @@ export default (context) => {
|
|||
app.$registry.registerNamespace('pathParamType')
|
||||
app.$registry.registerNamespace('builderDataProvider')
|
||||
app.$registry.registerNamespace('themeConfigBlock')
|
||||
app.$registry.registerNamespace('fontFamily')
|
||||
|
||||
app.$registry.register('application', new BuilderApplicationType(context))
|
||||
app.$registry.register('job', new DuplicatePageJobType(context))
|
||||
|
@ -267,6 +282,10 @@ export default (context) => {
|
|||
'themeConfigBlock',
|
||||
new ImageThemeConfigBlockType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'themeConfigBlock',
|
||||
new PageThemeConfigBlockType(context)
|
||||
)
|
||||
|
||||
app.$registry.register(
|
||||
'workflowAction',
|
||||
|
@ -313,4 +332,15 @@ export default (context) => {
|
|||
'collectionField',
|
||||
new ButtonCollectionFieldType(context)
|
||||
)
|
||||
|
||||
app.$registry.register('fontFamily', new InterFontFamilyType(context))
|
||||
app.$registry.register('fontFamily', new ArialFontFamilyType(context))
|
||||
app.$registry.register('fontFamily', new VerdanaFontFamilyType(context))
|
||||
app.$registry.register('fontFamily', new TahomaFontFamilyType(context))
|
||||
app.$registry.register('fontFamily', new TrebuchetMSFontFamilyType(context))
|
||||
app.$registry.register('fontFamily', new TimesNewRomanFontFamilyType(context))
|
||||
app.$registry.register('fontFamily', new GeorgiaFontFamilyType(context))
|
||||
app.$registry.register('fontFamily', new GaramondFontFamilyType(context))
|
||||
app.$registry.register('fontFamily', new CourierNewFontFamilyType(context))
|
||||
app.$registry.register('fontFamily', new BrushScriptMTFontFamilyType(context))
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@ import TypographyThemeConfigBlock from '@baserow/modules/builder/components/them
|
|||
import ButtonThemeConfigBlock from '@baserow/modules/builder/components/theme/ButtonThemeConfigBlock'
|
||||
import LinkThemeConfigBlock from '@baserow/modules/builder/components/theme/LinkThemeConfigBlock'
|
||||
import ImageThemeConfigBlock from '@baserow/modules/builder/components/theme/ImageThemeConfigBlock'
|
||||
import PageThemeConfigBlock from '@baserow/modules/builder/components/theme/PageThemeConfigBlock'
|
||||
import { resolveColor } from '@baserow/modules/core/utils/colors'
|
||||
import {
|
||||
WIDTHS_NEW,
|
||||
HORIZONTAL_ALIGNMENTS,
|
||||
BACKGROUND_MODES,
|
||||
} from '@baserow/modules/builder/enums'
|
||||
import get from 'lodash/get'
|
||||
|
||||
|
@ -118,11 +120,7 @@ export class ColorThemeConfigBlockType extends ThemeConfigBlockType {
|
|||
}
|
||||
|
||||
getCSS(theme, colorVariables, baseTheme = null) {
|
||||
const style = new ThemeStyle()
|
||||
style.addIfExists(theme, 'primary_color', '--primary-color')
|
||||
style.addIfExists(theme, 'secondary_color', '--secondary-color')
|
||||
style.addIfExists(theme, 'border_color', '--border-color')
|
||||
return style.toObject()
|
||||
return {}
|
||||
}
|
||||
|
||||
getColorVariables(theme) {
|
||||
|
@ -143,6 +141,21 @@ export class ColorThemeConfigBlockType extends ThemeConfigBlockType {
|
|||
value: 'border',
|
||||
color: theme.border_color,
|
||||
},
|
||||
{
|
||||
name: i18n.t('colorThemeConfigBlockType.success'),
|
||||
value: 'success',
|
||||
color: theme.main_success_color,
|
||||
},
|
||||
{
|
||||
name: i18n.t('colorThemeConfigBlockType.warning'),
|
||||
value: 'warning',
|
||||
color: theme.main_warning_color,
|
||||
},
|
||||
{
|
||||
name: i18n.t('colorThemeConfigBlockType.error'),
|
||||
value: 'error',
|
||||
color: theme.main_error_color,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -185,6 +198,15 @@ export class TypographyThemeConfigBlockType extends ThemeConfigBlockType {
|
|||
`--heading-h${level}-text-alignment`,
|
||||
(v) => v
|
||||
)
|
||||
style.addIfExists(
|
||||
theme,
|
||||
`heading_${level}_font_family`,
|
||||
`--heading-h${level}-font-family`,
|
||||
(v) => {
|
||||
const fontFamilyType = this.app.$registry.get('fontFamily', v)
|
||||
return `"${fontFamilyType.name}","${fontFamilyType.safeFont}"`
|
||||
}
|
||||
)
|
||||
})
|
||||
style.addIfExists(
|
||||
theme,
|
||||
|
@ -201,6 +223,10 @@ export class TypographyThemeConfigBlockType extends ThemeConfigBlockType {
|
|||
`--body-text-alignment`,
|
||||
(v) => v
|
||||
)
|
||||
style.addIfExists(theme, `body_font_family`, `--body-font-family`, (v) => {
|
||||
const fontFamilyType = this.app.$registry.get('fontFamily', v)
|
||||
return `"${fontFamilyType.name}","${fontFamilyType.safeFont}"`
|
||||
})
|
||||
return style.toObject()
|
||||
}
|
||||
|
||||
|
@ -233,7 +259,28 @@ export class ButtonThemeConfigBlockType extends ThemeConfigBlockType {
|
|||
style.addIfExists(
|
||||
theme,
|
||||
'button_hover_background_color',
|
||||
'--hover-button-background-color',
|
||||
'--button-hover-background-color',
|
||||
(v) => resolveColor(v, colorVariables)
|
||||
)
|
||||
style.addIfExists(theme, 'button_text_color', '--button-text-color', (v) =>
|
||||
resolveColor(v, colorVariables)
|
||||
)
|
||||
style.addIfExists(
|
||||
theme,
|
||||
'button_hover_text_color',
|
||||
'--button-hover-text-color',
|
||||
(v) => resolveColor(v, colorVariables)
|
||||
)
|
||||
style.addIfExists(
|
||||
theme,
|
||||
'button_border_color',
|
||||
'--button-border-color',
|
||||
(v) => resolveColor(v, colorVariables)
|
||||
)
|
||||
style.addIfExists(
|
||||
theme,
|
||||
'button_hover_border_color',
|
||||
'--button-hover-border-color',
|
||||
(v) => resolveColor(v, colorVariables)
|
||||
)
|
||||
style.addIfExists(theme, 'button_width', '--button-width', (v) =>
|
||||
|
@ -256,6 +303,51 @@ export class ButtonThemeConfigBlockType extends ThemeConfigBlockType {
|
|||
[HORIZONTAL_ALIGNMENTS.RIGHT]: 'flex-end',
|
||||
}[v])
|
||||
)
|
||||
style.addIfExists(
|
||||
theme,
|
||||
'button_font_alignment',
|
||||
'--button-text-alignment',
|
||||
(v) => v
|
||||
)
|
||||
style.addIfExists(
|
||||
theme,
|
||||
`button_font_family`,
|
||||
`--button-font-family`,
|
||||
(v) => {
|
||||
const fontFamilyType = this.app.$registry.get('fontFamily', v)
|
||||
return `"${fontFamilyType.name}","${fontFamilyType.safeFont}"`
|
||||
}
|
||||
)
|
||||
style.addIfExists(
|
||||
theme,
|
||||
`button_font_size`,
|
||||
`--button-font-size`,
|
||||
(v) => `${Math.min(100, v)}px`
|
||||
)
|
||||
style.addIfExists(
|
||||
theme,
|
||||
`button_border_radius`,
|
||||
`--button-border-radius`,
|
||||
(v) => `${v}px`
|
||||
)
|
||||
style.addIfExists(
|
||||
theme,
|
||||
`button_border_size`,
|
||||
`--button-border-size`,
|
||||
(v) => `${v}px`
|
||||
)
|
||||
style.addIfExists(
|
||||
theme,
|
||||
`button_horizontal_padding`,
|
||||
`--button-horizontal-padding`,
|
||||
(v) => `${v}px`
|
||||
)
|
||||
style.addIfExists(
|
||||
theme,
|
||||
`button_vertical_padding`,
|
||||
`--button-vertical-padding`,
|
||||
(v) => `${v}px`
|
||||
)
|
||||
return style.toObject()
|
||||
}
|
||||
|
||||
|
@ -299,6 +391,10 @@ export class LinkThemeConfigBlockType extends ThemeConfigBlockType {
|
|||
[HORIZONTAL_ALIGNMENTS.RIGHT]: 'flex-end',
|
||||
}[v])
|
||||
)
|
||||
style.addIfExists(theme, `link_font_family`, `--link-font-family`, (v) => {
|
||||
const fontFamilyType = this.app.$registry.get('fontFamily', v)
|
||||
return `"${fontFamilyType.name}","${fontFamilyType.safeFont}"`
|
||||
})
|
||||
return style.toObject()
|
||||
}
|
||||
|
||||
|
@ -350,31 +446,40 @@ export class ImageThemeConfigBlockType extends ThemeConfigBlockType {
|
|||
baseTheme?.image_constraint
|
||||
)
|
||||
|
||||
style.style['--image-wrapper-width'] = `${imageMaxWidth}%`
|
||||
style.style['--image-wrapper-max-width'] = `${imageMaxWidth}%`
|
||||
|
||||
if (imageMaxHeight) {
|
||||
style.style['--image-max-width'] = 'auto'
|
||||
style.style['--image-wrapper-max-height'] = `${imageMaxHeight}px`
|
||||
if (Object.prototype.hasOwnProperty.call(theme, 'image_max_width')) {
|
||||
style.style['--image-wrapper-width'] = `${imageMaxWidth}%`
|
||||
style.style['--image-wrapper-max-width'] = `${imageMaxWidth}%`
|
||||
}
|
||||
|
||||
switch (imageConstraint) {
|
||||
case 'cover':
|
||||
style.style['--image-wrapper-width'] = '100%'
|
||||
style.style['--image-object-fit'] = 'cover'
|
||||
style.style['--image-width'] = '100%'
|
||||
style.style['--image-height'] = '100%'
|
||||
break
|
||||
case 'contain':
|
||||
style.style['--image-object-fit'] = 'contain'
|
||||
style.style['--image-max-width'] = '100%'
|
||||
break
|
||||
case 'full-width':
|
||||
style.style['--image-object-fit'] = 'fill'
|
||||
style.style['--image-width'] = '100%'
|
||||
style.style['--image-height'] = '100%'
|
||||
style.style['--image-max-width'] = 'none'
|
||||
break
|
||||
if (Object.prototype.hasOwnProperty.call(theme, 'image_max_height')) {
|
||||
if (imageMaxHeight) {
|
||||
style.style['--image-max-width'] = 'auto'
|
||||
style.style['--image-wrapper-max-height'] = `${imageMaxHeight}px`
|
||||
} else {
|
||||
style.style['--image-max-width'] = 'auto'
|
||||
style.style['--image-wrapper-max-height'] = 'auto'
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(theme, 'image_constraint')) {
|
||||
switch (imageConstraint) {
|
||||
case 'cover':
|
||||
style.style['--image-wrapper-width'] = '100%'
|
||||
style.style['--image-object-fit'] = 'cover'
|
||||
style.style['--image-width'] = '100%'
|
||||
style.style['--image-height'] = '100%'
|
||||
break
|
||||
case 'contain':
|
||||
style.style['--image-object-fit'] = 'contain'
|
||||
style.style['--image-max-width'] = '100%'
|
||||
break
|
||||
case 'full-width':
|
||||
style.style['--image-object-fit'] = 'fill'
|
||||
style.style['--image-width'] = '100%'
|
||||
style.style['--image-height'] = '100%'
|
||||
style.style['--image-max-width'] = 'none'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return style.toObject()
|
||||
|
@ -388,3 +493,50 @@ export class ImageThemeConfigBlockType extends ThemeConfigBlockType {
|
|||
return 60
|
||||
}
|
||||
}
|
||||
|
||||
export class PageThemeConfigBlockType extends ThemeConfigBlockType {
|
||||
static getType() {
|
||||
return 'page'
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this.app.i18n.t('themeConfigBlockType.page')
|
||||
}
|
||||
|
||||
getCSS(theme, colorVariables, baseTheme = null) {
|
||||
const style = new ThemeStyle()
|
||||
style.addIfExists(
|
||||
theme,
|
||||
'page_background_color',
|
||||
'--page-background-color',
|
||||
(v) => resolveColor(v, colorVariables)
|
||||
)
|
||||
style.addIfExists(
|
||||
theme,
|
||||
'page_background_file',
|
||||
'--page-background-image',
|
||||
(v) => (v ? `url(${v.url})` : 'none')
|
||||
)
|
||||
if (theme.page_background_mode === BACKGROUND_MODES.FILL) {
|
||||
style.style['--page-background-size'] = 'cover'
|
||||
style.style['--page-background-repeat'] = 'no-repeat'
|
||||
}
|
||||
if (theme.page_background_mode === BACKGROUND_MODES.TILE) {
|
||||
style.style['--page-background-size'] = 'auto'
|
||||
style.style['--page-background-repeat'] = 'repeat'
|
||||
}
|
||||
if (theme.page_background_mode === BACKGROUND_MODES.FIT) {
|
||||
style.style['--page-background-size'] = 'contain'
|
||||
style.style['--page-background-repeat'] = 'no-repeat'
|
||||
}
|
||||
return style.toObject()
|
||||
}
|
||||
|
||||
get component() {
|
||||
return PageThemeConfigBlock
|
||||
}
|
||||
|
||||
getOrder() {
|
||||
return 15
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,3 +31,5 @@
|
|||
@import 'update_user_source_form';
|
||||
@import 'user_source_users_context';
|
||||
@import 'device_selector';
|
||||
@import 'padding_selector';
|
||||
@import 'page';
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
.element__wrapper {
|
||||
background-color: var(--background-color, $black);
|
||||
border-top: var(--border-top, none);
|
||||
border-bottom: var(--border-bottom, none);
|
||||
border-left: var(--border-left, none);
|
||||
border-right: var(--border-right, none);
|
||||
background-color: var(--element-background-color, transparent);
|
||||
background-image: var(--element-background-image, none);
|
||||
background-size: var(--element-background-size, cover);
|
||||
background-repeat: var(--element-background-repeat, no-repeat);
|
||||
margin: 0 auto;
|
||||
max-width: $builder-page-max-width;
|
||||
|
||||
// We use padding here as margin to prevent margin collapsing
|
||||
padding: var(--element-margin-top, 0) var(--element-margin-right, 0)
|
||||
var(--element-margin-bottom, 0) var(--element-margin-left, 0);
|
||||
|
||||
&--full-bleed {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&--full-width {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
@ -21,22 +28,18 @@
|
|||
}
|
||||
|
||||
.element__inner-wrapper {
|
||||
padding: var(--padding-top, 0) var(--padding-right, 0)
|
||||
var(--padding-bottom, 0) var(--padding-left, 0);
|
||||
border-top: var(--element-border-top, none);
|
||||
border-bottom: var(--element-border-bottom, none);
|
||||
border-left: var(--element-border-left, none);
|
||||
border-right: var(--element-border-right, none);
|
||||
padding: var(--element-padding-top, 0) var(--element-padding-right, 0)
|
||||
var(--element-padding-bottom, 0) var(--element-padding-left, 0);
|
||||
margin: 0 auto;
|
||||
max-width: $builder-page-max-width;
|
||||
|
||||
&--full-width {
|
||||
.element__wrapper--full-width & {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&--medium-width {
|
||||
max-width: 960px;
|
||||
}
|
||||
|
||||
&--small-width {
|
||||
max-width: 680px;
|
||||
}
|
||||
}
|
||||
|
||||
.element {
|
||||
|
|
|
@ -1,52 +1,26 @@
|
|||
.ab-button {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
color: $white;
|
||||
background-color: var(--button-background-color, $black);
|
||||
line-height: 28px;
|
||||
border: none;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
line-height: 1em;
|
||||
color: var(--button-text-color, $white);
|
||||
background-color: var(--button-background-color, $black);
|
||||
font-size: var(--button-font-size, 12px);
|
||||
border: var(--button-border-size, 0) solid var(--button-border-color, black);
|
||||
border-radius: var(--button-border-radius, 4px);
|
||||
width: var(--button-width, auto);
|
||||
text-align: var(--button-text-alignment, center);
|
||||
align-self: var(--button-alignment, flex-start);
|
||||
|
||||
@include rounded($rounded);
|
||||
|
||||
&--full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&--left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&--center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&--right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&--small {
|
||||
font-size: 12px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
&--medium {
|
||||
font-size: 14px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
&--large {
|
||||
font-size: 15px;
|
||||
padding: 5px 12px;
|
||||
}
|
||||
font-family: var(--button-font-family, Inter);
|
||||
padding: var(--button-vertical-padding, 4px)
|
||||
var(--button-horizontal-padding, 12px);
|
||||
|
||||
&:not(.loading-spinner):hover,
|
||||
&:not(.loading-spinner).ab-button--force-hover {
|
||||
background-color: var(--hover-button-background-color, $black);
|
||||
background-color: var(--button-hover-background-color, $black);
|
||||
border-color: var(--button-hover-border-color, $white);
|
||||
color: var(--button-hover-text-color, $white);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,35 +6,41 @@
|
|||
color: var(--heading-h1-color, $black);
|
||||
font-size: var(--heading-h1-font-size, 30px);
|
||||
text-align: var(--heading-h1-text-alignment, left);
|
||||
font-family: var(--heading-h1-font-family, Inter);
|
||||
}
|
||||
|
||||
.ab-heading--h2 {
|
||||
color: var(--heading-h2-color, $black);
|
||||
font-size: var(--heading-h2-font-size, 26px);
|
||||
text-align: var(--heading-h2-text-alignment, left);
|
||||
font-family: var(--heading-h2-font-family, Inter);
|
||||
}
|
||||
|
||||
.ab-heading--h3 {
|
||||
color: var(--heading-h3-color, $black);
|
||||
font-size: var(--heading-h3-font-size, 22px);
|
||||
text-align: var(--heading-h3-text-alignment, left);
|
||||
font-family: var(--heading-h3-font-family, Inter);
|
||||
}
|
||||
|
||||
.ab-heading--h4 {
|
||||
color: var(--heading-h4-color, $black);
|
||||
font-size: var(--heading-h4-font-size, 18px);
|
||||
text-align: var(--heading-h4-text-alignment, left);
|
||||
font-family: var(--heading-h4-font-family, Inter);
|
||||
}
|
||||
|
||||
.ab-heading--h5 {
|
||||
color: var(--heading-h5-color, $black);
|
||||
font-size: var(--heading-h5-font-size, 14px);
|
||||
text-align: var(--heading-h5-text-alignment, left);
|
||||
font-family: var(--heading-h5-font-family, Inter);
|
||||
}
|
||||
|
||||
.ab-heading--h6 {
|
||||
color: var(--heading-h6-color, $black);
|
||||
font-size: var(--heading-h6-font-size, 14px);
|
||||
text-align: var(--heading-h6-text-alignment, left);
|
||||
font-family: var(--heading-h6-font-family, Inter);
|
||||
font-style: italic;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
text-decoration: var(--link-text-decoration, underline);
|
||||
color: var(--link-text-color, $black);
|
||||
align-self: var(--link-text-alignment, flex-start);
|
||||
font-family: var(--link-font-family, Inter);
|
||||
|
||||
&:hover,
|
||||
&--force-hover {
|
||||
|
|
|
@ -3,4 +3,10 @@
|
|||
margin: 0;
|
||||
color: var(--body-text-color, $black);
|
||||
text-align: var(--body-text-alignment, left);
|
||||
font-family: var(--body-font-family, Inter);
|
||||
|
||||
// for markdown text
|
||||
strong {
|
||||
color: var(--body-text-color, $black);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
.padding-selector {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.padding-selector__input {
|
||||
flex: 1;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
.page {
|
||||
background-color: var(--page-background-color, #fff);
|
||||
background-image: var(--page-background-image, none);
|
||||
background-size: var(--page-background-size, cover);
|
||||
background-repeat: var(--page-background-repeat, no-repeat);
|
||||
|
||||
.public-page & {
|
||||
// We want to fill the screen when it's the published version
|
||||
min-height: 100vh;
|
||||
}
|
||||
}
|
|
@ -35,10 +35,19 @@
|
|||
.page-preview__scaled {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
background-color: $white;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
|
||||
// These properties are duplicated from the page element because during the SSR
|
||||
// the screen size is fixed to an arbitrary value and when returned to the browser
|
||||
// there is a small gap at the bottom of the screen between the end of the `.page` and
|
||||
// the end of this element. By duplicating them, we hide the difference before the
|
||||
// front hydratation.
|
||||
background-color: var(--page-background-color, #fff);
|
||||
background-image: var(--page-background-image, none);
|
||||
background-size: var(--page-background-size, cover);
|
||||
background-repeat: var(--page-background-repeat, no-repeat);
|
||||
|
||||
// We need to do this because the border of the preview is round and we need to round
|
||||
// the border of the selection box so that border has to have the same shape.
|
||||
> div:first-child {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
place-content: center space-between;
|
||||
margin-bottom: 15px;
|
||||
width: 60%;
|
||||
padding: 0;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
|
@ -24,6 +25,25 @@
|
|||
.theme-config-block {
|
||||
position: relative;
|
||||
|
||||
& .control--horizontal {
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.control__label {
|
||||
flex-basis: 40%;
|
||||
color: $color-neutral-700;
|
||||
}
|
||||
|
||||
.control__elements {
|
||||
flex-basis: 60%;
|
||||
white-space: initial;
|
||||
}
|
||||
|
||||
.control__wrapper {
|
||||
flex-basis: 60%;
|
||||
white-space: initial;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.theme-config-block--no-preview)::after {
|
||||
@include absolute(0, calc(40% - 14px), 0, auto);
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
display: flex;
|
||||
width: 100%;
|
||||
gap: 28px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.theme-config-block-section__properties {
|
||||
|
@ -17,6 +18,8 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
max-width: calc(40% - 15px);
|
||||
overflow: hidden;
|
||||
|
||||
.theme-config-block--no-preview & {
|
||||
display: none;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
.typography-theme-config-block__input-number {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.typography-theme-config-block__heading-preview {
|
||||
@extend %ellipsis;
|
||||
}
|
||||
|
|
|
@ -3,9 +3,30 @@
|
|||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
border: 1px solid $palette-neutral-400;
|
||||
}
|
||||
|
||||
.color-input__preview::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-size: 14px 14px;
|
||||
background-image: conic-gradient(
|
||||
$white 90deg,
|
||||
$color-neutral-400 90deg 180deg,
|
||||
$white 180deg 270deg,
|
||||
$color-neutral-400 270deg
|
||||
);
|
||||
}
|
||||
|
||||
.color-input__preview::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-color: var(--selected-color, black);
|
||||
}
|
||||
|
||||
.color-input__input {
|
||||
width: 100%;
|
||||
border: 1px solid $palette-neutral-400;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
.color-picker {
|
||||
display: flex;
|
||||
gap: 22px;
|
||||
}
|
||||
|
||||
.color-picker__space {
|
||||
position: relative;
|
||||
flex: 230px 0 0;
|
||||
height: 230px;
|
||||
width: 230px;
|
||||
overflow: hidden;
|
||||
border: 1px solid $color-neutral-200;
|
||||
background-image: linear-gradient(to top, #000, transparent),
|
||||
|
@ -38,8 +39,7 @@
|
|||
|
||||
.color-picker__slider {
|
||||
position: relative;
|
||||
flex: 18px 0 0;
|
||||
margin-left: 22px;
|
||||
width: 18px;
|
||||
overflow: hidden;
|
||||
border: 1px solid $color-neutral-200;
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
.color-picker-context {
|
||||
padding: 15px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.color-picker-context__color {
|
||||
display: flex;
|
||||
margin-top: 12px;
|
||||
width: 310px;
|
||||
}
|
||||
|
||||
.color-picker-context__color-type {
|
||||
|
@ -13,25 +14,28 @@
|
|||
}
|
||||
|
||||
.color-picker-context__color-hex {
|
||||
flex-basis: 95px;
|
||||
width: 95px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.color-picker-context__color-rgb {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
margin: 0 5px;
|
||||
width: 135px;
|
||||
margin-left: 11px;
|
||||
|
||||
// input {
|
||||
// width: 38px;
|
||||
// padding-left: 5px;
|
||||
// padding-right: 5px;
|
||||
// }
|
||||
.form-input__input {
|
||||
padding: 12px 5px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker-context__color-opacity {
|
||||
flex: 0 0 72px;
|
||||
margin-left: auto;
|
||||
width: 80px;
|
||||
|
||||
.form-input__input {
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker-context__variables {
|
||||
|
|
|
@ -17,6 +17,11 @@
|
|||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
&.control--error .form-input__input,
|
||||
&.control--error .form-input__icon {
|
||||
color: $palette-red-600;
|
||||
}
|
||||
}
|
||||
|
||||
.control__elements--flex {
|
||||
|
@ -180,3 +185,26 @@
|
|||
|
||||
@include flex-align-items(3px);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
border-bottom: 1px solid $color-neutral-200;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
// If we have an horizontal FormGroup
|
||||
// these labels should be grayer for better effect.
|
||||
& .control--horizontal .control__label {
|
||||
color: $color-neutral-700;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.form-section__title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
}
|
||||
|
||||
&.form-input--error {
|
||||
border-color: $palette-red-600;
|
||||
color: $palette-red-600;
|
||||
}
|
||||
|
||||
&.form-input--disabled {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.image-input {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
|
@ -9,11 +9,13 @@
|
|||
}
|
||||
|
||||
.image-input__image-placeholder-img {
|
||||
object-fit: contain;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.image-input__image-upload {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
@ -21,30 +23,7 @@
|
|||
}
|
||||
|
||||
.image-input__image-upload-description {
|
||||
font-weight: lighter;
|
||||
color: $palette-neutral-900;
|
||||
}
|
||||
|
||||
.image-input__thumbnail-remove {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
color: $color-error-500;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
:hover > & {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
margin-bottom: 0;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
ref="colorPicker"
|
||||
:value="value"
|
||||
:variables="localColorVariables"
|
||||
:allow-opacity="allowOpacity"
|
||||
@input="$emit('input', $event)"
|
||||
/>
|
||||
<div
|
||||
|
@ -15,7 +16,7 @@
|
|||
<span
|
||||
class="color-input__preview"
|
||||
:style="{
|
||||
'background-color': actualValue,
|
||||
'--selected-color': actualValue,
|
||||
}"
|
||||
/>
|
||||
<span>{{ displayValue }}</span>
|
||||
|
@ -51,6 +52,11 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
allowOpacity: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
variablesMap() {
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="allowOpacity"
|
||||
ref="alphaSpace"
|
||||
class="color-picker__slider color-picker__slider--alpha color-picker__thumb--negative-bottom-margin"
|
||||
draggable="false"
|
||||
|
@ -91,6 +92,11 @@ export default {
|
|||
type: String,
|
||||
default: '#ffffffff',
|
||||
},
|
||||
allowOpacity: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<Context class="color-picker-context">
|
||||
<ColorPicker
|
||||
:value="hexColorIncludingAlpha"
|
||||
:allow-opacity="allowOpacity"
|
||||
@input="setColorFromPicker($event)"
|
||||
></ColorPicker>
|
||||
<div class="color-picker-context__color">
|
||||
|
@ -9,21 +10,18 @@
|
|||
v-model="type"
|
||||
class="dropdown--floating color-picker-context__color-type"
|
||||
:show-search="false"
|
||||
small
|
||||
>
|
||||
<DropdownItem name="Hex" :value="COLOR_NOTATIONS.HEX"></DropdownItem>
|
||||
<DropdownItem name="RGB" :value="COLOR_NOTATIONS.RGB"></DropdownItem>
|
||||
</Dropdown>
|
||||
<div v-if="type === 'hex'" class="color-picker-context__color-hex">
|
||||
<FormInput
|
||||
size="large"
|
||||
:value="hexColorExcludingAlpha"
|
||||
@input="hexChanged"
|
||||
/>
|
||||
<FormInput small :value="hexColorExcludingAlpha" @input="hexChanged" />
|
||||
</div>
|
||||
<div v-if="type === 'rgb'" class="color-picker-context__color-rgb">
|
||||
<FormInput
|
||||
type="number"
|
||||
size="large"
|
||||
small
|
||||
:min="0"
|
||||
:max="255"
|
||||
:value="r"
|
||||
|
@ -32,7 +30,7 @@
|
|||
/>
|
||||
<FormInput
|
||||
type="number"
|
||||
size="large"
|
||||
small
|
||||
:min="0"
|
||||
:max="255"
|
||||
:value="g"
|
||||
|
@ -41,7 +39,7 @@
|
|||
/>
|
||||
<FormInput
|
||||
type="number"
|
||||
size="large"
|
||||
small
|
||||
:min="0"
|
||||
:max="255"
|
||||
:value="b"
|
||||
|
@ -49,17 +47,18 @@
|
|||
@input="rgbaChanged($event, 'b')"
|
||||
/>
|
||||
</div>
|
||||
<div class="color-picker-context__color-opacity">
|
||||
<div class="flex-grow-1" />
|
||||
<div v-if="allowOpacity" class="color-picker-context__color-opacity">
|
||||
<FormInput
|
||||
type="number"
|
||||
size="large"
|
||||
small
|
||||
:min="0"
|
||||
:max="100"
|
||||
:value="a"
|
||||
remove-number-input-controls
|
||||
icon-right="iconoir-percentage"
|
||||
remove-number-input-controls
|
||||
@input="rgbaChanged($event, 'a')"
|
||||
></FormInput>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -121,6 +120,11 @@ export default {
|
|||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
allowOpacity: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -182,7 +186,12 @@ export default {
|
|||
this.r = rgba.r * 255
|
||||
this.g = rgba.g * 255
|
||||
this.b = rgba.b * 255
|
||||
this.a = Math.round(rgba.a * 100)
|
||||
|
||||
if (this.allowOpacity) {
|
||||
this.a = Math.round(rgba.a * 100)
|
||||
} else {
|
||||
this.a = 100
|
||||
}
|
||||
|
||||
this.hexColorIncludingAlpha = convertRgbToHex(rgba)
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
'control--horizontal-variable': horizontalVariable,
|
||||
'control--messages': hasMessages,
|
||||
'control--after-input': hasAfterInputSlot,
|
||||
'control--error': hasError,
|
||||
}"
|
||||
>
|
||||
<label
|
||||
|
@ -42,7 +43,8 @@
|
|||
<slot v-if="hasHelperSlot" name="helper" />
|
||||
</p>
|
||||
<p v-if="hasError" class="control__messages--error">
|
||||
<slot name="error" />
|
||||
<slot v-if="hasErrorSlot" name="error" />
|
||||
<template v-else-if="errorMessage">{{ errorMessage }}</template>
|
||||
</p>
|
||||
<p v-if="hasWarningSlot" class="control__messages--warning">
|
||||
<slot name="warning" />
|
||||
|
@ -64,6 +66,14 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Shorthand when you don't need a specific error display.
|
||||
*/
|
||||
errorMessage: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* The id of the form group.
|
||||
*/
|
||||
|
@ -123,7 +133,10 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
hasError() {
|
||||
return Boolean(this.error)
|
||||
return Boolean(this.error) || Boolean(this.errorMessage)
|
||||
},
|
||||
hasErrorSlot() {
|
||||
return !!this.$slots.error
|
||||
},
|
||||
hasLabelSlot() {
|
||||
return !!this.$slots.label
|
||||
|
|
19
web-frontend/modules/core/components/FormSection.vue
Normal file
19
web-frontend/modules/core/components/FormSection.vue
Normal file
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<div class="form-section">
|
||||
<h3 v-if="title" class="form-section__title">{{ title }}</h3>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FormSection',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,21 +1,13 @@
|
|||
<template>
|
||||
<div class="image-input">
|
||||
<div class="image-input__image-placeholder">
|
||||
<div class="image-input image-input--with-image">
|
||||
<div v-if="imageUrl" class="image-input__image-placeholder">
|
||||
<img class="image-input__image-placeholder-img" :src="imageUrl" />
|
||||
<a
|
||||
v-if="removable"
|
||||
class="image-input__thumbnail-remove"
|
||||
@click="$emit('input', null)"
|
||||
>
|
||||
<i class="iconoir-cancel"></i>
|
||||
{{ $t('action.remove') }}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<div class="image-input__image-upload">
|
||||
<span class="image-input__image-upload-description">
|
||||
<div class="image-input__image-upload">
|
||||
<template v-if="!hasImage">
|
||||
<p class="image-input__image-upload-description">
|
||||
{{ labelDescription || $t('imageInput.labelDescription') }}
|
||||
</span>
|
||||
</p>
|
||||
<Button
|
||||
icon="iconoir-upload-square"
|
||||
type="upload"
|
||||
|
@ -23,7 +15,14 @@
|
|||
>
|
||||
{{ labelButton || $t('imageInput.labelButton') }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="image-input__image-delete">
|
||||
<ButtonIcon
|
||||
v-if="hasImage"
|
||||
icon="iconoir-bin"
|
||||
@click="$emit('input', null)"
|
||||
/>
|
||||
</div>
|
||||
<UserFilesModal
|
||||
ref="userFilesModal"
|
||||
|
@ -75,7 +74,7 @@ export default {
|
|||
defaultImage: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
|
@ -83,14 +82,21 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
imageUrl() {
|
||||
if (!this.value) {
|
||||
return this.defaultImage
|
||||
if (this.value === null) {
|
||||
if (this.defaultImage) {
|
||||
return this.defaultImage
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return this.value.url
|
||||
},
|
||||
removable() {
|
||||
return this.value !== null
|
||||
},
|
||||
hasImage() {
|
||||
return this.value !== null
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openFileUploadModal() {
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
export const IMAGE_FILE_TYPES = ['image/jpeg', 'image/jpg', 'image/png']
|
||||
export const IMAGE_FILE_TYPES = [
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/png',
|
||||
'image/apng',
|
||||
'image/gif',
|
||||
'image/tiff',
|
||||
'image/bmp',
|
||||
'image/webp',
|
||||
]
|
||||
|
||||
export const FAVICON_IMAGE_FILE_TYPES = [...IMAGE_FILE_TYPES, 'image/x-icon']
|
||||
|
||||
|
|
|
@ -711,5 +711,9 @@
|
|||
},
|
||||
"colorInput": {
|
||||
"default": "Default"
|
||||
},
|
||||
"imageInput": {
|
||||
"labelDescription": "Select an image to upload...",
|
||||
"labelButton": "Upload"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,17 +38,23 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
/**
|
||||
* Returns all the provided default values, but if the allowedValues are set
|
||||
* an object only containing those values is returned. This could be useful
|
||||
* when the defaultValues also contain other values which must not be used
|
||||
* when submitting.
|
||||
* Returns whether a key of the given defaultValue should be handled by this
|
||||
* form component. This is useful when the defaultValues also contain other
|
||||
* values which must not be used when submitting. By default this implementation
|
||||
* is filtered by the list of `allowedValues`.
|
||||
*/
|
||||
isAllowedKey(key) {
|
||||
if (this.allowedValues !== null) {
|
||||
return this.allowedValues.includes(key)
|
||||
}
|
||||
return true
|
||||
},
|
||||
/**
|
||||
* Returns all the provided default values filtered by the `isAllowedKey` method.
|
||||
*/
|
||||
getDefaultValues() {
|
||||
if (this.allowedValues === null) {
|
||||
return clone(this.defaultValues)
|
||||
}
|
||||
return Object.keys(this.defaultValues).reduce((result, key) => {
|
||||
if (this.allowedValues.includes(key)) {
|
||||
if (this.isAllowedKey(key)) {
|
||||
let value = this.defaultValues[key]
|
||||
|
||||
// If the value is an array or object, it could be that it contains
|
||||
|
|
|
@ -612,7 +612,7 @@
|
|||
</template>
|
||||
</Alert>
|
||||
|
||||
<Alert type="error" close-button>
|
||||
<Alert type="danger" close-button>
|
||||
<template #title>Alert title</template>
|
||||
<template #actions>
|
||||
<button
|
||||
|
@ -1375,7 +1375,7 @@
|
|||
toggle error toast
|
||||
</Button>
|
||||
<Button
|
||||
type="warning"
|
||||
type="danger"
|
||||
@click="
|
||||
$store.dispatch('toast/warning', {
|
||||
title: 'Custom warning toast',
|
||||
|
@ -1957,6 +1957,11 @@
|
|||
{{ color }} - {{ resolveColor(color, colorVariables) }} <br /><br />
|
||||
<br />
|
||||
<ColorInput v-model="color" :color-variables="colorVariables" />
|
||||
<ColorInput
|
||||
v-model="color"
|
||||
:color-variables="colorVariables"
|
||||
:allow-opacity="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="margin-bottom-3">
|
||||
|
|
|
@ -38,6 +38,7 @@ import FormGroup from '@baserow/modules/core/components/FormGroup'
|
|||
import FormRow from '@baserow/modules/core/components/FormRow'
|
||||
import Logo from '@baserow/modules/core/components/Logo'
|
||||
import ReadOnlyForm from '@baserow/modules/core/components/ReadOnlyForm'
|
||||
import FormSection from '@baserow/modules/core/components/FormSection'
|
||||
|
||||
import lowercase from '@baserow/modules/core/filters/lowercase'
|
||||
import uppercase from '@baserow/modules/core/filters/uppercase'
|
||||
|
@ -105,6 +106,7 @@ function setupVue(Vue) {
|
|||
Vue.component('SelectSearch', SelectSearch)
|
||||
Vue.component('Logo', Logo)
|
||||
Vue.component('ReadOnlyForm', ReadOnlyForm)
|
||||
Vue.component('FormSection', FormSection)
|
||||
|
||||
Vue.filter('lowercase', lowercase)
|
||||
Vue.filter('uppercase', uppercase)
|
||||
|
|
Loading…
Add table
Reference in a new issue