diff --git a/.gitignore b/.gitignore index 682b185ac..98f3a88f9 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ workdir/ filer_public/ filer_public_thumbnails/ examples/bs3demo/private_settings.py +.venv/ +poetry.lock diff --git a/cmsplugin_cascade/bootstrap4/accordion.py b/cmsplugin_cascade/bootstrap4/accordion.py index 50d0d6743..ab831d0cf 100644 --- a/cmsplugin_cascade/bootstrap4/accordion.py +++ b/cmsplugin_cascade/bootstrap4/accordion.py @@ -10,7 +10,7 @@ from cmsplugin_cascade.plugin_base import TransparentWrapper, TransparentContainer from cmsplugin_cascade.widgets import NumberInputWidget from .plugin_base import BootstrapPluginBase - +from cmsplugin_cascade.helpers import used_compact_form, entangled_nested class AccordionFormMixin(ManageChildrenFormMixin, EntangledModelFormMixin): num_children = IntegerField( @@ -35,6 +35,9 @@ class AccordionFormMixin(ManageChildrenFormMixin, EntangledModelFormMixin): help_text=_("Start with the first card open.") ) + if used_compact_form: + entangled_nested(num_children, close_others,first_is_open, data_nested='accordion') + class Meta: untangled_fields = ['num_children'] entangled_fields = {'glossary': ['close_others', 'first_is_open']} @@ -85,6 +88,9 @@ class AccordionGroupFormMixin(EntangledModelFormMixin): help_text=_("Add standard padding to card body."), ) + if used_compact_form: + entangled_nested(heading,body_padding, data_nested='accordion_group') + class Meta: entangled_fields = {'glossary': ['heading', 'body_padding']} diff --git a/cmsplugin_cascade/bootstrap4/buttons.py b/cmsplugin_cascade/bootstrap4/buttons.py index 282d74cb6..03f5783fc 100644 --- a/cmsplugin_cascade/bootstrap4/buttons.py +++ b/cmsplugin_cascade/bootstrap4/buttons.py @@ -9,7 +9,10 @@ from cmsplugin_cascade.icon.forms import IconFormMixin from cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin from cmsplugin_cascade.link.plugin_base import LinkElementMixin +from cmsplugin_cascade.helpers import used_compact_form, entangled_nested +from sass_processor.processor import sass_processor +sass_processor('cascade/css/admin/compact_forms/bootstrap4-colors.scss') class ButtonTypeWidget(widgets.RadioSelect): """ @@ -24,7 +27,6 @@ class ButtonSizeWidget(widgets.RadioSelect): """ template_name = 'cascade/admin/legacy_widgets/button_sizes.html' if DJANGO_VERSION < (2, 0) else 'cascade/admin/widgets/button_sizes.html' - class ButtonFormMixin(EntangledModelFormMixin): BUTTON_TYPES = [ ('btn-primary', _("Primary")), @@ -104,6 +106,12 @@ class ButtonFormMixin(EntangledModelFormMixin): help_text=_("Add an Icon before or after the button content."), ) + if used_compact_form: + entangled_nested(link_content,button_type,button_size,button_options,\ + stretched_link, data_nested='button') + entangled_nested(button_type, data_nested='button', template_key='button_type') + entangled_nested(icon_align, data_nested='icon') + class Meta: entangled_fields = {'glossary': ['link_content', 'button_type', 'button_size', 'button_options', 'icon_align', 'stretched_link']} diff --git a/cmsplugin_cascade/bootstrap4/carousel.py b/cmsplugin_cascade/bootstrap4/carousel.py index a838d5e0a..53f46be16 100644 --- a/cmsplugin_cascade/bootstrap4/carousel.py +++ b/cmsplugin_cascade/bootstrap4/carousel.py @@ -14,6 +14,7 @@ from cmsplugin_cascade.bootstrap4.utils import IMAGE_RESIZE_OPTIONS from cmsplugin_cascade.forms import ManageChildrenFormMixin from cmsplugin_cascade.image import ImagePropertyMixin, ImageFormMixin +from cmsplugin_cascade.helpers import used_compact_form, entangled_nested logger = logging.getLogger('cascade') @@ -55,6 +56,9 @@ class CarouselSlidesFormMixin(ManageChildrenFormMixin, EntangledModelFormMixin): initial=['upscale', 'crop', 'subject_location', 'high_resolution'], ) + if used_compact_form: + entangled_nested(resize_options, container_max_heights,options,interval,num_children, data_nested='carousel') + class Meta: untangled_fields = ['num_children'] entangled_fields = {'glossary': ['interval', 'options', 'container_max_heights', 'resize_options']} diff --git a/cmsplugin_cascade/bootstrap4/container.py b/cmsplugin_cascade/bootstrap4/container.py index fcefe3ac0..e9e0beb20 100644 --- a/cmsplugin_cascade/bootstrap4/container.py +++ b/cmsplugin_cascade/bootstrap4/container.py @@ -11,6 +11,7 @@ from cmsplugin_cascade import app_settings from cmsplugin_cascade.bootstrap4.grid import Breakpoint from cmsplugin_cascade.forms import ManageChildrenFormMixin +from cmsplugin_cascade.helpers import entangled_nested, used_compact_form from .plugin_base import BootstrapPluginBase from . import grid @@ -19,17 +20,26 @@ def get_widget_choices(): breakpoints = app_settings.CMSPLUGIN_CASCADE['bootstrap4']['fluid_bounds'] widget_choices = [] for index, (bp, bound) in enumerate(breakpoints.items()): - if index == 0: - widget_choices.append((bp.name, "{} (<{:.1f}px)".format(bp.label, bound.max))) - elif index == len(breakpoints) - 1: - widget_choices.append((bp.name, "{} (≥{:.1f}px)".format(bp.label, bound.min))) + if not used_compact_form: + if index == 0: + widget_choices.append((bp.name, "{} (<{:.1f}px)".format(bp.label, bound.max))) + elif index == len(breakpoints) - 1: + widget_choices.append((bp.name, "{} (≥{:.1f}px)".format(bp.label, bound.min))) + else: + widget_choices.append((bp.name, "{} (≥{:.1f}px and <{:.1f}px)".format(bp.label, bound.min, bound.max))) else: - widget_choices.append((bp.name, "{} (≥{:.1f}px and <{:.1f}px)".format(bp.label, bound.min, bound.max))) + if index == 0: + widget_choices.append((bp.name, "{} <{:.1f}px".format(bp._name_, bound.max))) + else: + widget_choices.append((bp.name, "{} ≥{:.1f}px".format(bp._name_, bound.min))) return widget_choices class ContainerBreakpointsWidget(widgets.CheckboxSelectMultiple): - template_name = 'cascade/admin/legacy_widgets/container_breakpoints.html' if DJANGO_VERSION < (2, 0) else 'cascade/admin/widgets/container_breakpoints.html' + if not used_compact_form: + template_name = 'cascade/admin/legacy_widgets/container_breakpoints.html' if DJANGO_VERSION < (2, 0) else 'cascade/admin/widgets/container_breakpoints.html' + else: + template_name = 'cascade/admin/compact_forms/widgets/container_breakpoints.html' class ContainerFormMixin(EntangledModelFormMixin): @@ -48,6 +58,9 @@ class ContainerFormMixin(EntangledModelFormMixin): help_text=_("Changing your outermost '.container' to '.container-fluid'.") ) + if used_compact_form: + entangled_nested( breakpoints, fluid, data_nested="container") + class Meta: entangled_fields = {'glossary': ['breakpoints', 'fluid']} @@ -121,6 +134,9 @@ class BootstrapRowFormMixin(ManageChildrenFormMixin, EntangledModelFormMixin): help_text=_('Number of columns to be created with this row.'), ) + if used_compact_form: + entangled_nested( num_children, data_nested="row") + class Meta: untangled_fields = ['num_children'] @@ -196,7 +212,7 @@ def choose_help_text(*phrases): return phrases[1].format(bs4_breakpoints[first].min) else: return phrases[2] - + if 'parent' in self._cms_initial_attributes: container=self._cms_initial_attributes['parent'].get_ancestors().order_by('depth').last().get_bound_plugin() else: @@ -233,7 +249,7 @@ def choose_help_text(*phrases): width_fields[field_name] = ChoiceField( choices=choices, label=_("Column width for {}").format(devices), - initial='col-{}-12'.format(bp), + initial='col' if bp =='xs' else 'col-{}'.format(bp), help_text=choose_help_text( _("Column width for devices narrower than {:.1f} pixels."), _("Column width for devices wider than {:.1f} pixels."), @@ -325,6 +341,13 @@ def choose_help_text(*phrases): glossary_fields.extend(reorder_fields.keys()) glossary_fields.extend(responsive_fields.keys()) + + if used_compact_form: + entangled_nested(width_fields, data_nested="column", template_key="column", ) + entangled_nested(offset_fields, data_nested="offset", template_key="column", ) + entangled_nested(reorder_fields, data_nested="reorder", template_key="column",) + entangled_nested(responsive_fields, data_nested="responsive", template_key="column") + class Meta: entangled_fields = {'glossary': glossary_fields} diff --git a/cmsplugin_cascade/bootstrap4/icon.py b/cmsplugin_cascade/bootstrap4/icon.py index 0a47647ad..4927e8fac 100644 --- a/cmsplugin_cascade/bootstrap4/icon.py +++ b/cmsplugin_cascade/bootstrap4/icon.py @@ -8,7 +8,7 @@ from cmsplugin_cascade.link.plugin_base import LinkElementMixin from cmsplugin_cascade.icon.forms import IconFormMixin from cmsplugin_cascade.icon.plugin_base import IconPluginMixin - +from cmsplugin_cascade.helpers import used_compact_form, entangled_nested class FramedIconFormMixin(IconFormMixin): SIZE_CHOICES = [('{}em'.format(c), "{} em".format(c)) for c in range(1, 13)] @@ -56,6 +56,10 @@ class FramedIconFormMixin(IconFormMixin): required=False, ) + if used_compact_form: + entangled_nested(font_size, color, background_color, text_align, border, + border_radius, data_nested='icon') + class Meta: entangled_fields = {'glossary': ['font_size', 'color', 'background_color', 'text_align', 'border', 'border_radius']} diff --git a/cmsplugin_cascade/bootstrap4/image.py b/cmsplugin_cascade/bootstrap4/image.py index d3f536e95..ab4b93396 100644 --- a/cmsplugin_cascade/bootstrap4/image.py +++ b/cmsplugin_cascade/bootstrap4/image.py @@ -10,6 +10,7 @@ from cmsplugin_cascade.fields import SizeField from cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin from cmsplugin_cascade.link.plugin_base import LinkElementMixin +from cmsplugin_cascade.helpers import used_compact_form, entangled_nested logger = logging.getLogger('cascade.bootstrap4') @@ -67,6 +68,10 @@ class BootstrapImageFormMixin(ImageFormMixin): help_text=_("How to align a non-responsive image."), ) + if used_compact_form: + entangled_nested(image_shapes, image_width_responsive, image_width_fixed, + image_height, resize_options, image_alignment, data_nested='image_setting') + class Meta: entangled_fields = {'glossary': ['image_shapes', 'image_width_responsive', 'image_width_fixed', 'image_height', 'resize_options', 'image_alignment']} diff --git a/cmsplugin_cascade/bootstrap4/jumbotron.py b/cmsplugin_cascade/bootstrap4/jumbotron.py index ffc61d689..91b5d3e35 100644 --- a/cmsplugin_cascade/bootstrap4/jumbotron.py +++ b/cmsplugin_cascade/bootstrap4/jumbotron.py @@ -12,6 +12,7 @@ from cmsplugin_cascade.bootstrap4.container import ContainerGridMixin from cmsplugin_cascade.bootstrap4.fields import BootstrapMultiSizeField from cmsplugin_cascade.bootstrap4.picture import get_picture_elements +from cmsplugin_cascade.helpers import entangled_nested, used_compact_form logger = logging.getLogger('cascade') @@ -154,6 +155,11 @@ class JumbotronFormMixin(EntangledModelFormMixin): help_text=_("This property specifies the width and height of a background image in px or %."), ) + if used_compact_form: + entangled_nested(fluid, background_color, element_heights, image_file, + background_repeat, background_attachment, + background_vertical_position, background_horizontal_position, + background_size, background_width_height, data_nested='jumbotron') class Meta: entangled_fields = {'glossary': ['fluid', 'background_color', 'element_heights', 'image_file', 'background_repeat', 'background_attachment', diff --git a/cmsplugin_cascade/bootstrap4/mixins.py b/cmsplugin_cascade/bootstrap4/mixins.py index 029bf53b0..45684fba5 100644 --- a/cmsplugin_cascade/bootstrap4/mixins.py +++ b/cmsplugin_cascade/bootstrap4/mixins.py @@ -4,7 +4,7 @@ from entangled.forms import EntangledModelFormMixin from cmsplugin_cascade.utils import CascadeUtilitiesMixin from cmsplugin_cascade.bootstrap4.grid import Breakpoint - +from cmsplugin_cascade.helpers import entangled_nested, used_compact_form class BootstrapUtilities(type): """ @@ -26,23 +26,53 @@ class BootstrapUtilities(type): The class ``BootstrapUtilities`` offers a bunch of property methods which return a list of input fields and/or select boxes. They then can be added to the plugin's editor. This is - specially useful to add CSS classes from the utilities section of Bootstrap-4, such as + specially useful to add CSS classes or HTML data attributes from the utilities section of Bootstrap-4, such as margins, borders, colors, etc. + + The 'property_name' attritbute in property methods is needed because python property methods don't have name + attributes without using inspect module or others things. + The 'attrs_type' attritbute in property methods can have two possiblity values 'css_classes' or 'html_data_attrs'. + The 'anchors_fields' in the property_fields attributes can add choices id elements of the current page, theses + choices are realy set when the request is available. """ + def __new__(cls, *args): form_fields = {} + form_fields_by_property_name = {} + form_fields_by_attr_type = {} + fields_choices_anchors = [] + for arg in args: if isinstance(arg, property): - form_fields.update(arg.fget(cls)) + property_fields=arg.fget(cls) + form_subfields = property_fields['form_fields'] + attrs_type = property_fields['attrs_type'] + property_name = property_fields['property_name'] + + form_fields.update(form_subfields) + form_fields_by_property_name[property_name]= property_fields['form_fields'] + + form_fields_by_attr_type.setdefault(attrs_type, []) + form_fields_by_attr_type[attrs_type ].extend(property_fields['form_fields'].keys()) + + if 'anchors_fields' in property_fields: + fields_choices_anchors.extend(property_fields['anchors_fields']) + + if used_compact_form: + for property_name , field in form_fields_by_property_name.items(): + entangled_nested(field, data_nested=property_name , template_key=property_name) class Meta: entangled_fields = {'glossary': list(form_fields.keys())} - utility_form_mixin = type('UtilitiesFormMixin', (EntangledModelFormMixin,), dict(form_fields, Meta=Meta)) - return type('BootstrapUtilitiesMixin', (CascadeUtilitiesMixin,), {'utility_form_mixin': utility_form_mixin}) + utility_form_mixin = type('UtilitiesFormMixin', (EntangledModelFormMixin,), dict(form_fields, Meta=Meta) ) + return type('HtmlAttrsUtilitiesMixin', (CascadeUtilitiesMixin,), {'utility_form_mixin': utility_form_mixin, + 'attr_type': form_fields_by_attr_type , 'fields_with_choices_anchors': fields_choices_anchors }) @property def background_and_color(cls): + attrs_type = 'css_classes' + property_name = 'background_and_color' choices = [ ('', _("Default")), ('bg-primary text-white', _("Primary with white text")), @@ -57,15 +87,19 @@ def background_and_color(cls): ('bg-transparent text-dark', _("Transparent with dark text")), ('bg-transparent text-white', _("Transparent with white text")), ] - return {'background_and_color': ChoiceField( + form_fields = {'background_and_color': ChoiceField( label=_("Background and color"), choices=choices, required=False, initial='', )} + property_fields = { 'form_fields':form_fields, 'attrs_type': attrs_type, 'property_name':property_name } + return property_fields @property def margins(cls): + attrs_type = 'css_classes' + property_name = 'margins' form_fields = {} choices_format = [ ('m-{}{}', _("4 sided margins ({})")), @@ -91,10 +125,13 @@ def margins(cls): required=False, initial='', ) - return form_fields + property_fields = { 'form_fields':form_fields, 'attrs_type': attrs_type, 'property_name':property_name } + return property_fields @property def vertical_margins(cls): + attrs_type = 'css_classes' + property_name = 'vertical_margins' form_fields = {} choices_format = [ ('my-{}{}', _("Vertical margins ({})")), @@ -116,10 +153,13 @@ def vertical_margins(cls): required=False, initial='', ) - return form_fields + property_fields = { 'form_fields':form_fields, 'attrs_type': attrs_type, 'property_name':property_name } + return property_fields @property def paddings(cls): + attrs_type = 'css_classes' + property_name = 'paddings' form_fields = {} choices_format = [ ('p-{}{}', _("4 sided padding ({})")), @@ -145,11 +185,14 @@ def paddings(cls): required=False, initial='', ) - return form_fields + property_fields = { 'form_fields':form_fields, 'attrs_type': attrs_type, 'property_name':property_name } + return property_fields @property def floats(cls): form_fields = {} + attrs_type = 'css_classes' + property_name = 'floats' choices_format = [ ('float-{}none', _("Do not float")), ('float-{}left', _("Float left")), @@ -169,4 +212,5 @@ def floats(cls): required=False, initial='', ) - return form_fields + property_fields = { 'form_fields':form_fields, 'attrs_type': attrs_type, 'property_name':property_name } + return property_fields diff --git a/cmsplugin_cascade/bootstrap4/picture.py b/cmsplugin_cascade/bootstrap4/picture.py index a939968ff..3e82c24f7 100644 --- a/cmsplugin_cascade/bootstrap4/picture.py +++ b/cmsplugin_cascade/bootstrap4/picture.py @@ -10,6 +10,7 @@ from cmsplugin_cascade.image import ImageFormMixin, ImagePropertyMixin from cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin from cmsplugin_cascade.link.plugin_base import LinkElementMixin +from cmsplugin_cascade.helpers import used_compact_form, entangled_nested logger = logging.getLogger('cascade.bootstrap4') @@ -48,6 +49,9 @@ class BootstrapPictureFormMixin(ImageFormMixin): initial=['img-fluid'] ) + if used_compact_form: + entangled_nested(responsive_heights, responsive_zoom, resize_options, image_shapes, data_nested='picture') + class Meta: entangled_fields = {'glossary': ['responsive_heights', 'responsive_zoom', 'resize_options', 'image_shapes']} diff --git a/cmsplugin_cascade/bootstrap4/tabs.py b/cmsplugin_cascade/bootstrap4/tabs.py index 0fd6cb485..1c14a00dc 100644 --- a/cmsplugin_cascade/bootstrap4/tabs.py +++ b/cmsplugin_cascade/bootstrap4/tabs.py @@ -11,7 +11,7 @@ from cmsplugin_cascade.plugin_base import TransparentWrapper, TransparentContainer from cmsplugin_cascade.widgets import NumberInputWidget from .plugin_base import BootstrapPluginBase - +from cmsplugin_cascade.helpers import used_compact_form, entangled_nested class TabSetFormMixin(ManageChildrenFormMixin, EntangledModelFormMixin): num_children = IntegerField( @@ -27,6 +27,9 @@ class TabSetFormMixin(ManageChildrenFormMixin, EntangledModelFormMixin): required=False, ) + if used_compact_form: + entangled_nested(num_children, justified, data_nested='tab') + class Meta: untangled_fields = ['num_children'] entangled_fields = {'glossary': ['justified']} diff --git a/cmsplugin_cascade/extra_fields/mixins.py b/cmsplugin_cascade/extra_fields/mixins.py index 1fa95f50f..b0b99bcaf 100644 --- a/cmsplugin_cascade/extra_fields/mixins.py +++ b/cmsplugin_cascade/extra_fields/mixins.py @@ -1,3 +1,4 @@ + from django.contrib.sites.shortcuts import get_current_site from django.core.exceptions import ObjectDoesNotExist from django.forms import MediaDefiningClass, widgets @@ -7,7 +8,7 @@ from entangled.forms import EntangledModelFormMixin from cmsplugin_cascade import app_settings from cmsplugin_cascade.fields import SizeField - +from cmsplugin_cascade.helpers import entangled_nested, used_compact_form class ExtraFieldsMixin(metaclass=MediaDefiningClass): """ @@ -62,7 +63,8 @@ def get_form(self, request, obj=None, **kwargs): required=False, help_text=_("Customized CSS class to be added to this element."), ) - + if used_compact_form: + entangled_nested(form_fields['extra_css_classes'], data_nested="custom_css_classes") # add input fields to let the user enter styling information for style, choices_list in app_settings.CMSPLUGIN_CASCADE['extra_inline_styles'].items(): inline_styles = extra_fields.inline_styles.get('extra_fields:{0}'.format(style)) @@ -77,7 +79,10 @@ def get_form(self, request, obj=None, **kwargs): } if issubclass(Field, SizeField): field_kwargs['allowed_units'] = extra_fields.inline_styles.get('extra_units:{0}'.format(style)).split(',') - form_fields[key] = Field(**field_kwargs) + field = Field(**field_kwargs) + if used_compact_form: + entangled_nested(field, data_nested=style.split(':')[0]) + form_fields[key] = field # extend the form with some extra fields base_form = kwargs.pop('form', self.form) diff --git a/cmsplugin_cascade/generic/cms_plugins.py b/cmsplugin_cascade/generic/cms_plugins.py index 7148ebfae..57d91bb2f 100644 --- a/cmsplugin_cascade/generic/cms_plugins.py +++ b/cmsplugin_cascade/generic/cms_plugins.py @@ -11,7 +11,7 @@ from cmsplugin_cascade.link.plugin_base import LinkElementMixin from cmsplugin_cascade.plugin_base import CascadePluginBase, TransparentContainer from cmsplugin_cascade.utils import compute_aspect_ratio - +from cmsplugin_cascade.helpers import used_compact_form, entangled_nested class SimpleWrapperFormMixin(EntangledModelFormMixin): TAG_CHOICES = [(cls, _("<{}> – Element").format(cls)) for cls in ['div', 'span', 'section', 'article']] + \ @@ -75,6 +75,9 @@ class HeadingFormMixin(EntangledModelFormMixin): widget=widgets.TextInput(attrs={'style': 'width: 100%; padding-right: 0; font-weight: bold; font-size: 125%;'}), ) + if used_compact_form: + entangled_nested(tag_type, content, tag_type , data_nested='heading') + class Meta: entangled_fields = {'glossary': ['tag_type', 'content']} @@ -165,6 +168,9 @@ class TextImageFormMixin(ImageFormMixin): initial='', ) + if used_compact_form: + entangled_nested(image_width, image_height, resize_options, alignement, data_nested='text_image') + class Meta: entangled_fields = {'glossary': ['image_width', 'image_height', 'resize_options', 'alignement']} diff --git a/cmsplugin_cascade/generic/mixins_html_attrs.py b/cmsplugin_cascade/generic/mixins_html_attrs.py new file mode 100644 index 000000000..c40b1b6e9 --- /dev/null +++ b/cmsplugin_cascade/generic/mixins_html_attrs.py @@ -0,0 +1,120 @@ +from django.forms.fields import ChoiceField +from django.utils.text import format_lazy +from django.utils.translation import ugettext_lazy as _ +from entangled.forms import EntangledModelFormMixin +from cmsplugin_cascade.utils import CascadeUtilitiesMixin +from cmsplugin_cascade.bootstrap4.grid import Breakpoint +from cmsplugin_cascade import app_settings +from cmsplugin_cascade.helpers import used_compact_form + +def get_widget_choices(widget_choices): + return widget_choices + +class GenericUtilities(type): + """ + Factory for building a class ``GenericUtilitiesMixin``. This class then is used as a mixin to + all sorts of generic plugins. Various plugins are shipped using this mixin class + in different configurations. These configurations can be overridden through the project's + settings using: + ``` + CMSPLUGIN_CASCADE['plugins_with_extra_mixins'] = { + 'BootstrapPlugin': GenericUtilities( + GenericUtilities.scroll_animate, + … + ), + … + } + ``` + + The class ``GenericUtilities`` offers a bunch of property methods which return a list of + input fields and/or select boxes. They then can be added to the plugin's editor. + Specficed with attritbute in property methods 'attrs_type' with two possible values 'css_classes' or 'html_data_attrs' + Html data attribute need some time anchors of current page. + form_fields has reserved string '#anchors' used with choices='#anchors', after the form request anchor of element_ids + are disponible in choicesfields. + """ + def __new__(cls, *args): + form_fields = {} + form_fields_by_property_name = {} + form_fields_by_attr_type = {} + fields_choices_anchors = [] + + for arg in args: + if isinstance(arg, property): + property_fields=arg.fget(cls) + form_subfields = property_fields['form_fields'] + attrs_type = property_fields['attrs_type'] + property_name = property_fields['property_name'] + + form_fields.update(form_subfields) + form_fields_by_property_name[property_name]= property_fields['form_fields'] + + form_fields_by_attr_type.setdefault(attrs_type, []) + form_fields_by_attr_type[attrs_type ].extend(property_fields['form_fields'].keys()) + + if 'anchors_fields' in property_fields: + fields_choices_anchors.extend(property_fields['anchors_fields']) + + if used_compact_form: + for property_name , field in form_fields_by_property_name.items(): + entangled_nested(field, data_nested=property_name) + + class Meta: + entangled_fields = {'glossary': list(form_fields) } + + utility_form_mixin = type('UtilitiesFormMixin', (EntangledModelFormMixin,), dict(form_fields, Meta=Meta) ) + return type('HtmlAttrsUtilitiesMixin', (CascadeUtilitiesMixin,), {'utility_form_mixin': utility_form_mixin, + 'attr_type': form_fields_by_attr_type , 'fields_with_choices_anchors': fields_choices_anchors }) + + @property + def scroll_animate(cls): + form_fields = {} + attrs_type = 'html_data_attrs' + property_name = 'scroll_animate' + + choices_data_sal = [ + ('inherit', _("inherit")), + ('fade', _("fade")), + ('slide-up', _("slide-up")), + ('slide-down', _("slide-down")), + ('slide-left', _("slide-left")), + ('slide-right', _("slide-right")), + ('zoom-in', _("zoom-in")), + ('zoom-out', _("zoom-out")), + ('flip-up', _("flip-up")), + ('flip-down', _("flip-down")), + ('zoom-out', _("zoom-out")), + ('flip-up', _("flip-up")), + ('flip-right', _("flip-right")), + ] + + choices_data_sal_delay= list((c, c) for c in ["inherit"] + [ i for i in range(0, 2100, 100)]) + choices_data_sal_easing= [ + ('ease', _("ease")), + ('ease-in-out-back', _("ease-in-out-back")), + ('ease-out-back', _("ease-out-back")), + ('ease-in-out-sine', _("ease-in-out-sine")), + ('ease-in-quad', _("ease-in-quad")), + ] + form_fields['data-sal'] = ChoiceField( + label=_("Scroll effects"), + choices=choices_data_sal, + required=False, + initial='', + ) + form_fields['data-sal-delay'] = ChoiceField( + label=_("Delay effect"), + choices= choices_data_sal_delay, + required=False, + initial='', + help_text='Delay in milliseconde', + ) + attrs={'data_entangled':'Scroll_animate'} + form_fields['data-sal-delay'].widget.attrs = {**attrs } + form_fields['data-sal-easing'] = ChoiceField( + label=_("Animation type"), + choices=choices_data_sal_easing, + required=False, + initial='', + ) + return { 'form_fields':form_fields, 'attrs_type': attrs_type, 'property_name':property_name } diff --git a/cmsplugin_cascade/helpers.py b/cmsplugin_cascade/helpers.py new file mode 100644 index 000000000..1105c0beb --- /dev/null +++ b/cmsplugin_cascade/helpers.py @@ -0,0 +1,105 @@ +from os import environ +from django.utils.translation import ugettext_lazy as _ +from django.contrib.admin.utils import flatten_fieldsets +from django.forms import widgets +from cmsplugin_cascade import app_settings +from django.forms import Media +from collections import OrderedDict + +from django.forms.widgets import media_property + +used_compact_form = True if environ.get('COMPACT_FORM', False) == 'True' else False + +traductions_keys_to_title = { + 'background_and_color': _( "Background and color" ), + 'scroll_animate': _( "Scroll Animate" ), + 'custom_css_classes': _( "Custom css classes" ), + } + +def fieldset_by_widget_attr( form, attr_data_name, cls_media, traductions=traductions_keys_to_title , change_form_template=None): + + """Filter and classed fields with or not group attribute 'data_nested' and create fieldset.""" + nested = {} + css_appended = [ 'cascade/css/admin/compact_forms/main_compact_form.css', 'cascade/css/admin/cascade_box.css' ] + fieldsets = () + for key, field in form.declared_fields.items(): + if 'data_nested' in field.widget.attrs : + data_entangled_value = field.widget.attrs[attr_data_name] + if data_entangled_value == 'background_and_color': + css_appended.append('cascade/css/admin/compact_forms/bootstrap4-colors.css') + + nested.setdefault(data_entangled_value,[]) + if len(key) > 1: + nested[data_entangled_value].append(key) + else: + nested[data_entangled_value].extend(key) + + if hasattr(cls_media, 'css'): + if not css_appended[0] in cls_media.css['all']: + cls_media.css['all'].extend(css_appended) + else: + cls_media.css = { + 'all': css_appended + } + + for key_title, fields_lists_str in nested.items(): + + if key_title in traductions: + key_title_trad = traductions[key_title] + else: + key_title_trad = key_title + + icon = '{0}-title'.format(key_title) + + if 'link_type' in fields_lists_str or 'icon_font' in fields_lists_str or 'button' in fields_lists_str: + fieldsets +=( None, {'classes': ('cascade_box',),'fields':((fields_lists_str),), 'description': + '
{1}
\ +
'.format(icon , key_title )}), + if key_title in ['background_and_color', 'column', 'offset', 'reorder',\ + 'responsive', 'floats', 'paddings','margins', 'buttons', 'vertical_margins', 'container']: + fieldsets +=( None, { 'classes': ['cascade_box', 'nested'], 'fields':((fields_lists_str),), + 'description': '
{1}\ +
'.format(icon, key_title_trad )}), + # else: + if not key_title in ['background_and_color', 'column', 'offset', 'reorder',\ + 'responsive', 'floats', 'paddings','margins', 'buttons', 'vertical_margins', 'container']: + fieldsets +=( None ,{'classes': ['cascade_box_classic',], 'fields':fields_lists_str }), + + extra_fields = OrderedDict.fromkeys(x for x in list(form.declared_fields.keys()) if x not in flatten_fieldsets(fieldsets)) + if not nested: + fieldsets +=(None, {'fields':list(extra_fields)}), + else: + # fieldsets +=('extra_fields',{'fields':list(extra_fields)}), + fieldsets +=(None ,{'fields':list(extra_fields)}), + return fieldsets, cls_media + + +def apply_widgets_tpl( field,template_key): + if template_key == 'select_icon': + field.widget.template_name = 'cascade/admin/widgets/select_icon.html' + if template_key == 'button_type': + field.widget.template_name = 'cascade/admin/compact_forms/widgets/select_icon_button_types.html' + if template_key == 'width': + field.widget.template_name = 'cascade/admin/compact_forms/widgets/select_icon_button_types.html' + if template_key == 'column' or template_key == 'paddings' or template_key == 'margins' or template_key == 'floats' or template_key == 'vertical_margins' : + field.widget.template_name = 'cascade/admin/compact_forms/widgets/select_icon_columns.html' + if template_key == 'background_and_color': + field.widget.template_name = 'cascade/admin/compact_forms/widgets/select_icon_colors.html' + + +def entangled_nested(*fields, data_nested=None,template_key=None): + """ + The Fields are classed by groups key with widget attribute 'data_nested' and set widget template name. + Used in Compact form mode. + """ + for index, field in enumerate(fields): + if isinstance(field, dict): + for index_sub, field in enumerate(field.values()): + field.widget.attrs['data_nested'] = data_nested + field.widget.attrs['pk'] = index + apply_widgets_tpl(field, template_key) + index += 1 + else: + field.widget.attrs['data_nested'] = data_nested + field.widget.attrs['pk'] = index + apply_widgets_tpl(field, template_key) diff --git a/cmsplugin_cascade/icon/forms.py b/cmsplugin_cascade/icon/forms.py index 8d1eaddd7..26981425d 100644 --- a/cmsplugin_cascade/icon/forms.py +++ b/cmsplugin_cascade/icon/forms.py @@ -2,7 +2,7 @@ from django.utils.translation import ugettext_lazy as _ from cmsplugin_cascade.models import IconFont from entangled.forms import EntangledModelFormMixin - +from cmsplugin_cascade.helpers import used_compact_form, entangled_nested def get_default_icon_font(): try: @@ -23,6 +23,10 @@ class IconFormMixin(EntangledModelFormMixin): label=_("Select Symbol"), ) + if used_compact_form : + entangled_nested(icon_font,symbol, data_nested='icon') + + class Meta: entangled_fields = {'glossary': ['icon_font', 'symbol']} diff --git a/cmsplugin_cascade/image.py b/cmsplugin_cascade/image.py index 6142511d8..254b03153 100644 --- a/cmsplugin_cascade/image.py +++ b/cmsplugin_cascade/image.py @@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _ from entangled.forms import EntangledModelFormMixin, EntangledField, get_related_object from cmsplugin_cascade.fields import CascadeImageField - +from cmsplugin_cascade.helpers import used_compact_form, entangled_nested class ImageFormMixin(EntangledModelFormMixin): image_file = CascadeImageField() @@ -22,6 +22,9 @@ class ImageFormMixin(EntangledModelFormMixin): _image_properties = EntangledField() + if used_compact_form: + entangled_nested(image_file,image_title, alt_tag, data_nested='image_file') + class Meta: entangled_fields = {'glossary': ['image_file', 'image_title', 'alt_tag', '_image_properties']} diff --git a/cmsplugin_cascade/link/forms.py b/cmsplugin_cascade/link/forms.py index 639931d30..655236a7c 100644 --- a/cmsplugin_cascade/link/forms.py +++ b/cmsplugin_cascade/link/forms.py @@ -14,7 +14,7 @@ from entangled.forms import EntangledModelFormMixin, get_related_object from filer.models.filemodels import File as FilerFileModel from filer.fields.file import AdminFileWidget, FilerFileField - +from cmsplugin_cascade.helpers import used_compact_form, entangled_nested def format_page_link(title, path): html = format_html("{} ({})", mark_safe(title), path) @@ -138,6 +138,10 @@ class LinkForm(EntangledModelFormMixin): help_text=_("Link's Title"), ) + if used_compact_form: + entangled_nested(link_type, cms_page, section, download_file, ext_url, mail_to, + link_target, link_title, data_nested='link') + class Meta: entangled_fields = {'glossary': ['link_type', 'cms_page', 'section', 'download_file', 'ext_url', 'mail_to', 'link_target', 'link_title']} diff --git a/cmsplugin_cascade/plugin_base.py b/cmsplugin_cascade/plugin_base.py index 0150758bd..1dd1f2434 100644 --- a/cmsplugin_cascade/plugin_base.py +++ b/cmsplugin_cascade/plugin_base.py @@ -18,6 +18,8 @@ from .hide_plugins import HidePluginMixin from .render_template import RenderTemplateMixin from .utils import remove_duplicates +from .helpers import fieldset_by_widget_attr +from cmsplugin_cascade.helpers import used_compact_form mark_safe_lazy = lazy(mark_safe, str) @@ -324,7 +326,11 @@ def get_form(self, request, obj=None, **kwargs): bases = (CascadeFormMixin,) + bases if not issubclass(form, ModelForm): bases += (ModelForm,) - kwargs['form'] = type(form.__name__, bases, {}) + + form = type(form.__name__, bases, {}) + if used_compact_form: + self.fieldsets, self.Media = fieldset_by_widget_attr(form ,'data_nested', self.Media) + kwargs['form'] = form return super().get_form(request, obj, **kwargs) def get_parent_instance(self, request=None, obj=None): diff --git a/cmsplugin_cascade/static/cascade/css/admin/cascade_box.css b/cmsplugin_cascade/static/cascade/css/admin/cascade_box.css new file mode 100644 index 000000000..7550fa40c --- /dev/null +++ b/cmsplugin_cascade/static/cascade/css/admin/cascade_box.css @@ -0,0 +1,108 @@ +/* Generated by Glyphter (http://www.glyphter.com) on Wed Jan 08 2020*/ +@font-face { + font-family: 'cascade_box'; + src: url('../fonts/cascade_box.eot'); + src: url('../fonts/cascade_box.eot?#iefix') format('embedded-opentype'), + url('../fonts/cascade_box.woff') format('woff'), + url('../fonts/cascade_box.ttf') format('truetype'), + url('../fonts/cascade_box.svg#cascade_box') format('svg'); + font-weight: normal; + font-style: normal; +} +[class*='icon-']:before{ + display: inline-block; + font-family: 'cascade_box'; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale +} +.icon-col:before{content:'\0041';} +.icon-col-1:before{content:'\0042';} +.icon-col-2:before{content:'\0043';} +.icon-col-3:before{content:'\0044';} +.icon-col-4:before{content:'\0045';} +.icon-col-5:before{content:'\0046';} +.icon-col-6:before{content:'\0047';} +.icon-col-7:before{content:'\0048';} +.icon-col-8:before{content:'\0049';} +.icon-col-9:before{content:'\004a';} +.icon-col-10:before{content:'\004b';} +.icon-col-11:before{content:'\004c';} +.icon-col-12:before{content:'\004d';} +.icon-col-auto:before{content:'\004e';} +.icon-col-inherit:before{content:'\004f';} +.icon-order-12:before{content:'\0050';} +.icon-offset-title:before{content:'\0051';} +.icon-column-title:before{content:'\0052';} +.icon-inherit:before{content:'\0053';} +.icon-container-title:before{content:'\0054';} +.icon-responsive-title:before{content:'\0055';} +.icon-xs:before{content:'\0056';} +.icon-sm:before{content:'\0057';} +.icon-md:before{content:'\0058';} +.icon-lg:before{content:'\0059';} +.icon-xl:before{content:'\005a';} +.icon-offtest-inherit:before{content:'\0061';} +.icon-offset-1:before{content:'\0062';} +.icon-offset-2:before{content:'\0063';} +.icon-offset-3:before{content:'\0064';} +.icon-offset-4:before{content:'\0065';} +.icon-offset-5:before{content:'\0066';} +.icon-offset-6:before{content:'\0067';} +.icon-offset-7:before{content:'\0068';} +.icon-offset-8:before{content:'\0069';} +.icon-offset-9:before{content:'\006a';} +.icon-offset-10:before{content:'\006b';} +.icon-offset-11:before{content:'\006c';} +.icon-offset-12:before{content:'\006d';} +.icon-order-1:before{content:'\006e';} +.icon-order-2:before{content:'\006f';} +.icon-order-3:before{content:'\0070';} +.icon-order-4:before{content:'\0071';} +.icon-order-5:before{content:'\0072';} +.icon-order-6:before{content:'\0073';} +.icon-order-7:before{content:'\0074';} +.icon-order-8:before{content:'\0075';} +.icon-order-9:before{content:'\0076';} +.icon-order-10:before{content:'\0077';} +.icon-order-11:before{content:'\0078';} +.icon-order-12:before{content:'\0079';} +.icon-hidden:before{content:'\007a';} +.icon-visible:before{content:'\0030';} +.icon-order-inherit:before{content:'\0031';} +.icon-visible-inherit:before{content:'\0032';} +.icon-reorder-title:before{content:'\0033';} +.icon-px-0:before{content:'\0034';} +.icon-px-1:before{content:'\0035';} +.icon-px-2:before{content:'\0036';} +.icon-px-3:before{content:'\0037';} +.icon-px-4:before{content:'\0038';} +.icon-px-5:before{content:'\0039';} +.icon-background_and_color-title:before{content:'\0021';} +.icon-p-0:before{content:'\0022';} +.icon-p-1:before{content:'\0023';} +.icon-p-2:before{content:'\0024';} +.icon-p-3:before{content:'\0025';} +.icon-p-4:before{content:'\0026';} +.icon-p-5:before{content:'\0027';} +.icon-paddings-title:before{content:'\0028';} +.icon-m-0:before{content:'\0029';} +.icon-m-1:before{content:'\002a';} +.icon-m-2:before{content:'\002b';} +.icon-m-3:before{content:'\002c';} +.icon-m-4:before{content:'\002d';} +.icon-m-5:before{content:'\002e';} +.icon-margins-title:before{content:'\002f';} +.icon-vertical_margins-title:before{content:'\005b';} +.icon-floats-title:before{content:'\005c';} +.icon-my-5:before{content:'\005d';} +.icon-float-none:before{content:'\005e';} +.icon-float-right:before{content:'\005f';} +.icon-float-left:before{content:'\0060';} +.icon-my-0:before{content:'\007b';} +.icon-my-1:before{content:'\007c';} +.icon-my-2:before{content:'\007d';} +.icon-my-3:before{content:'\007e';} +.icon-my-4:before{content:'\003f';} \ No newline at end of file diff --git a/cmsplugin_cascade/static/cascade/css/admin/compact_forms/bootstrap4-colors.css b/cmsplugin_cascade/static/cascade/css/admin/compact_forms/bootstrap4-colors.css new file mode 100644 index 000000000..416077c8d --- /dev/null +++ b/cmsplugin_cascade/static/cascade/css/admin/compact_forms/bootstrap4-colors.css @@ -0,0 +1,234 @@ +.text-monospace { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important; } + +.text-justify { + text-align: justify !important; } + +.text-wrap { + white-space: normal !important; } + +.text-nowrap { + white-space: nowrap !important; } + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } + +.text-left { + text-align: left !important; } + +.text-right { + text-align: right !important; } + +.text-center { + text-align: center !important; } + +@media (min-width: 576px) { + .text-sm-left { + text-align: left !important; } + .text-sm-right { + text-align: right !important; } + .text-sm-center { + text-align: center !important; } } + +@media (min-width: 768px) { + .text-md-left { + text-align: left !important; } + .text-md-right { + text-align: right !important; } + .text-md-center { + text-align: center !important; } } + +@media (min-width: 992px) { + .text-lg-left { + text-align: left !important; } + .text-lg-right { + text-align: right !important; } + .text-lg-center { + text-align: center !important; } } + +@media (min-width: 1200px) { + .text-xl-left { + text-align: left !important; } + .text-xl-right { + text-align: right !important; } + .text-xl-center { + text-align: center !important; } } + +.text-lowercase { + text-transform: lowercase !important; } + +.text-uppercase { + text-transform: uppercase !important; } + +.text-capitalize { + text-transform: capitalize !important; } + +.font-weight-light { + font-weight: 300 !important; } + +.font-weight-lighter { + font-weight: lighter !important; } + +.font-weight-normal { + font-weight: 400 !important; } + +.font-weight-bold { + font-weight: 700 !important; } + +.font-weight-bolder { + font-weight: bolder !important; } + +.font-italic { + font-style: italic !important; } + +.text-white { + color: #fff !important; } + +.text-primary { + color: #007bff !important; } + +a.text-primary:hover, a.text-primary:focus { + color: #0056b3 !important; } + +.text-secondary { + color: #6c757d !important; } + +a.text-secondary:hover, a.text-secondary:focus { + color: #494f54 !important; } + +.text-success { + color: #28a745 !important; } + +a.text-success:hover, a.text-success:focus { + color: #19692c !important; } + +.text-info { + color: #17a2b8 !important; } + +a.text-info:hover, a.text-info:focus { + color: #0f6674 !important; } + +.text-warning { + color: #ffc107 !important; } + +a.text-warning:hover, a.text-warning:focus { + color: #ba8b00 !important; } + +.text-danger { + color: #dc3545 !important; } + +a.text-danger:hover, a.text-danger:focus { + color: #a71d2a !important; } + +.text-light { + color: #f8f9fa !important; } + +a.text-light:hover, a.text-light:focus { + color: #cbd3da !important; } + +.text-dark { + color: #343a40 !important; } + +a.text-dark:hover, a.text-dark:focus { + color: #121416 !important; } + +.text-body { + color: #212529 !important; } + +.text-muted { + color: #6c757d !important; } + +.text-black-50 { + color: rgba(0, 0, 0, 0.5) !important; } + +.text-white-50 { + color: rgba(255, 255, 255, 0.5) !important; } + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; } + +.text-decoration-none { + text-decoration: none !important; } + +.text-break { + word-break: break-word !important; + overflow-wrap: break-word !important; } + +.text-reset { + color: inherit !important; } + +.bg-primary { + background-color: #007bff !important; } + +a.bg-primary:hover, a.bg-primary:focus, +button.bg-primary:hover, +button.bg-primary:focus { + background-color: #0062cc !important; } + +.bg-secondary { + background-color: #6c757d !important; } + +a.bg-secondary:hover, a.bg-secondary:focus, +button.bg-secondary:hover, +button.bg-secondary:focus { + background-color: #545b62 !important; } + +.bg-success { + background-color: #28a745 !important; } + +a.bg-success:hover, a.bg-success:focus, +button.bg-success:hover, +button.bg-success:focus { + background-color: #1e7e34 !important; } + +.bg-info { + background-color: #17a2b8 !important; } + +a.bg-info:hover, a.bg-info:focus, +button.bg-info:hover, +button.bg-info:focus { + background-color: #117a8b !important; } + +.bg-warning { + background-color: #ffc107 !important; } + +a.bg-warning:hover, a.bg-warning:focus, +button.bg-warning:hover, +button.bg-warning:focus { + background-color: #d39e00 !important; } + +.bg-danger { + background-color: #dc3545 !important; } + +a.bg-danger:hover, a.bg-danger:focus, +button.bg-danger:hover, +button.bg-danger:focus { + background-color: #bd2130 !important; } + +.bg-light { + background-color: #f8f9fa !important; } + +a.bg-light:hover, a.bg-light:focus, +button.bg-light:hover, +button.bg-light:focus { + background-color: #dae0e5 !important; } + +.bg-dark { + background-color: #343a40 !important; } + +a.bg-dark:hover, a.bg-dark:focus, +button.bg-dark:hover, +button.bg-dark:focus { + background-color: #1d2124 !important; } + +.bg-white { + background-color: #fff !important; } + +.bg-transparent { + background-color: transparent !important; } diff --git a/cmsplugin_cascade/static/cascade/css/admin/compact_forms/bootstrap4-colors.scss b/cmsplugin_cascade/static/cascade/css/admin/compact_forms/bootstrap4-colors.scss new file mode 100644 index 000000000..17f7df0e1 --- /dev/null +++ b/cmsplugin_cascade/static/cascade/css/admin/compact_forms/bootstrap4-colors.scss @@ -0,0 +1,11 @@ +@import "bootstrap/scss/_functions"; +@import "bootstrap/scss/_variables"; +@import "bootstrap/scss/mixins/deprecate"; +@import "bootstrap/scss/mixins/background-variant"; +@import "bootstrap/scss/mixins/breakpoints"; +@import "bootstrap/scss/mixins/hover"; +@import "bootstrap/scss/mixins/text-hide"; +@import "bootstrap/scss/mixins/text-truncate"; +@import "bootstrap/scss/mixins/text-emphasis"; +@import "bootstrap/scss/utilities/text"; +@import "bootstrap/scss/utilities/background"; diff --git a/cmsplugin_cascade/static/cascade/css/admin/compact_forms/main_compact_form.css b/cmsplugin_cascade/static/cascade/css/admin/compact_forms/main_compact_form.css new file mode 100644 index 000000000..b09afd1d5 --- /dev/null +++ b/cmsplugin_cascade/static/cascade/css/admin/compact_forms/main_compact_form.css @@ -0,0 +1,367 @@ + +.form-row[class*="column-width"], +.form-row[class*="offset"], +.form-row[class*="column-ordering"], +.form-row[class*="responsive"], +.form-row[class*="float"]{ + +} + + + +.fieldBox[class*="column-width"] div.help, +.fieldBox[class*="column-ordering"] div.help, +.fieldBox[class*="responsive"] div.help, +.fieldBox[class*="offset"] div.help { + display:none; +} + + +.nested .fieldBox > label:first-child{ + display:none; +} + +.cascade_box { +display: flex; +justify-content: center; +margin-bottom: 0px; +} + +.cascade_box .form-row .fieldBox{ +margin-right:0px; +margin-top:0px; +padding:0px; +margin:0px; +} + + +.cascade_box .form-row .fieldBox .container-thumbnail{ +display:inline-grid; +} + +.cascade_box .form-row .field-breakpoints{ + +width: 100%; +display: grid; +} + +.cascade_box .form-row .container-thumbnail label { + width: 82px !important; + text-align: center; +} + +.fieldBox.field-fluid{ +padding: 2rem !important; +display: inherit; +} + +.cascade_box .form-row{ + width: 463px; +} + +.nested .form-row{ + padding:0px; +} + +.cascade_box .field-background_and_color div > label:first-child{ +opacity:0; +position:absolute; +} + +.cascade_box .fieldBox.form-row{ +padding: 0; +} + +.cascade_box h2{ + width: 65px; +border: 1px solid; +} + + +.fieldBox[class*="data-sal"], +.fieldBox[class*="inline_styles:margin"], +.fieldBox[class*="inline_styles:padding"]{ + display: inline-grid !important; +} + + +input[name="cascade-input"] + label{ + width:100% ; + padding: 0px; +} + +/* overide django form.css*/ +input[name="cascade-input"] + label::after { + content: none !important; +} + +input[name="cascade-input"] { + opacity:0; +} + + + + + +.form-row .field-background_and_color{ + width: 548px; +} + +.input__container{ + opacity:0; + position:absolute; +} + + +.select-box__option i[class*="icon-"]{ + font-size:60px; +} + +svg { +z-index:42; + +} + + + +.select-box__option div[class*="icon-"]{ +font-size:60px; + +} + +.min_overlay{ +position:absolute; +opacity:0 !important; +display: inline-block; +width: 60px; +height: 60px; +} + +.min_overlay + label{ +width:100%; +} + + +form .form-row.field-cms_page { + height: 100% !important; + float: left; +} + +fieldset .fieldBox { + float: left; + margin-right: 20px; + display: inline-flex; +} + + +.select-box { + position: absolute; + display: contents; + font-family: "Open Sans", "Helvetica Neue", "Segoe UI", "Calibri", "Arial", sans-serif; + font-size: 18px; + color: #60666d; +} + +.select-box help { + display: none; +} + + +.select-box__current { + position: relative; + box-shadow: 0 15px 30px -10px rgba(0, 0, 0, 0.1); + outline: none; + width: 90px +} + +.select-box__current:focus > .select-box__icon { + -webkit-transform: translateY(-50%) rotate(180deg); + transform: translateY(-50%) rotate(180deg); +} + +.select-box__option:hover, .select-box__option:focus { + color: #546c84; + background-color: #fbfbfb; +} + .select-box__input{ + display: none; +} + +.select-box__option a:active + { + color: #546c84; + background-color: #fbfbfb; + // pointer-events:none; +} + +.select-box__input:checked + .select-box__input-text { + display: inline-grid !important; + +} + +.select-box__option { + display: block; + background-color: #fff; +} + +.select-box__input { + display: none; +} + +.select-box__input-text { + display: none; + width: 100%; + cursor:pointer; +} + + +.select-box__input-text::after { + display: block !important; + width: 100%; + background-color: red; +} + +.select-box__list li label { + padding: 0 !important; +} + +.select-box__list{ + overflow : hidden; + position: absolute; + width: 96%; + padding:10px; + + list-style: none; + display: inline-flex; + flex-wrap: wrap; + left:0px; + font-size:400; + justify-content: center; + margin-left: 0 !important; + padding-left: 0!important; + box-shadow: 0 15px 30px -10px rgba(0, 0, 0, 0.1); +} + + +.select-box__option{ + padding: 15px; + background-color: #fff; +} + + +svg { + width: 2px; + height:1px; + position: absolute; + left: 0px; +} + +.form-row.field-button_type { + max-width: none !important; +} + + +.form-row[class*="background_and_color"] svg foreignObject a { +pointer-events:all; +} + +.form-row[class*="background_and_color"] div labeel { +opacity: 0; +position: absolute; +left: -1000px; +} + + +input[name="cascade-input"]:focus-within:checked + label .select-box svg foreignObject { +width:100%; +height:100%; +} + +input[name="cascade-input"]:focus-within:checked + label .select-box svg { +width:100% ; +height:100%; +} + + + +input[name="cascade-input"]:focus-within:checked + label .select-box svg foreignObject div .select-box__list { +clip-path : unset; +background-color: white !important; +pointer-events:visible; +height: auto; +opacity: 1; + -webkit-animation-name: none; +animation-name: none; +z-index:9900; +left:0px; +} + + +.description { +background:#79aec8; +width:70px; +color:white; +} + + +.icon_desc { +font-size: xx-large; +} + + +.select-box__icon { + position: absolute; + top: 50%; + right: 15px; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); + width: 20px; + opacity: 0.3; + transition: 0.2s ease; +} + + +.select-box__value { + display: block; + text-align: center; + width:90px; +} + +.buttons ul { +background: white; +} + +.buttons ul a { +padding:10px !important; +pointer-events: none; + +} + +.buttons ul label{ +padding:0,4rem; +} + +.buttons ul div{ +cursor:pointer; +} + +.form-row .fieldBox > label[for*=""] { + display: none; + font-family:'border box'; + font-size: xx-large; +} + + +.field-button_type .select-box__input-text { + padding: 2px !important; + width: auto; + background-color: none; +} + +.select-box__list label { +font-size: none !important; +} + +.detail-select-icon { + color:black; +} + diff --git a/cmsplugin_cascade/static/cascade/css/fonts/cascade_box.eot b/cmsplugin_cascade/static/cascade/css/fonts/cascade_box.eot new file mode 100644 index 000000000..df6631620 Binary files /dev/null and b/cmsplugin_cascade/static/cascade/css/fonts/cascade_box.eot differ diff --git a/cmsplugin_cascade/static/cascade/css/fonts/cascade_box.svg b/cmsplugin_cascade/static/cascade/css/fonts/cascade_box.svg new file mode 100644 index 000000000..19f8d5cf8 --- /dev/null +++ b/cmsplugin_cascade/static/cascade/css/fonts/cascade_box.svg @@ -0,0 +1 @@ +Generated by Glyphter \ No newline at end of file diff --git a/cmsplugin_cascade/static/cascade/css/fonts/cascade_box.ttf b/cmsplugin_cascade/static/cascade/css/fonts/cascade_box.ttf new file mode 100644 index 000000000..c9ca89831 Binary files /dev/null and b/cmsplugin_cascade/static/cascade/css/fonts/cascade_box.ttf differ diff --git a/cmsplugin_cascade/static/cascade/css/fonts/cascade_box.woff b/cmsplugin_cascade/static/cascade/css/fonts/cascade_box.woff new file mode 100644 index 000000000..b3d1ec96f Binary files /dev/null and b/cmsplugin_cascade/static/cascade/css/fonts/cascade_box.woff differ diff --git a/cmsplugin_cascade/templates/cascade/admin/change_form.html b/cmsplugin_cascade/templates/cascade/admin/change_form.html index eed0c9b39..f8fb77fab 100644 --- a/cmsplugin_cascade/templates/cascade/admin/change_form.html +++ b/cmsplugin_cascade/templates/cascade/admin/change_form.html @@ -1,6 +1,7 @@ {% extends "admin/cms/usersettings/change_form.html" %} {% load admin_static admin_urls i18n %} + {% block field_sets %} {{ plugin_intro }} {% if empty_form %} diff --git a/cmsplugin_cascade/templates/cascade/admin/compact_forms/widgets/container_breakpoints.html b/cmsplugin_cascade/templates/cascade/admin/compact_forms/widgets/container_breakpoints.html new file mode 100644 index 000000000..b7db6cec3 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/admin/compact_forms/widgets/container_breakpoints.html @@ -0,0 +1,10 @@ +{% load static %}{% spaceless %} +
+{% for group, options, index in widget.optgroups %}{% with widget=options.0 %} +
+
+ +
{% include widget.template_name %}
+
+
{% endwith %}{% endfor %} +
{% endspaceless %} diff --git a/cmsplugin_cascade/templates/cascade/admin/compact_forms/widgets/select_icon_button_types.html b/cmsplugin_cascade/templates/cascade/admin/compact_forms/widgets/select_icon_button_types.html new file mode 100644 index 000000000..7f59e4bd2 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/admin/compact_forms/widgets/select_icon_button_types.html @@ -0,0 +1,44 @@ +{% load static %} +{% spaceless %} + +{% with id=widget.attrs.id %} + + +{% endwith %} +{% endspaceless %} + diff --git a/cmsplugin_cascade/templates/cascade/admin/compact_forms/widgets/select_icon_colors.html b/cmsplugin_cascade/templates/cascade/admin/compact_forms/widgets/select_icon_colors.html new file mode 100644 index 000000000..421adb81b --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/admin/compact_forms/widgets/select_icon_colors.html @@ -0,0 +1,44 @@ +{% load static %} +{% spaceless %} + +{% with id=widget.attrs.id %} + + +{% endwith %} +{% endspaceless %} diff --git a/cmsplugin_cascade/templates/cascade/admin/compact_forms/widgets/select_icon_columns.html b/cmsplugin_cascade/templates/cascade/admin/compact_forms/widgets/select_icon_columns.html new file mode 100644 index 000000000..d4ec1ecfe --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/admin/compact_forms/widgets/select_icon_columns.html @@ -0,0 +1,79 @@ +{% load static %} +{% spaceless %} +{% with id=widget.attrs.id %} + + +{% endwith %} +{% endspaceless %} + + + diff --git a/cmsplugin_cascade/utils.py b/cmsplugin_cascade/utils.py index 3142da131..f4299feba 100644 --- a/cmsplugin_cascade/utils.py +++ b/cmsplugin_cascade/utils.py @@ -2,7 +2,7 @@ from django.forms import MediaDefiningClass from django.utils.translation import ugettext_lazy as _ from entangled.forms import EntangledModelFormMixin - +from cmsplugin_cascade import app_settings def remove_duplicates(lst): """ @@ -86,23 +86,45 @@ class CascadeUtilitiesMixin(metaclass=MediaDefiningClass): """ If a Cascade plugin is listed in ``settings.CMSPLUGIN_CASCADE['plugins_with_extra_mixins']``, then this ``BootstrapUtilsMixin`` class is added automatically to its plugin class in order to - enrich it with utility classes, such as :class:`cmsplugin_cascade.bootstrap4.mixins.BootstrapUtilities`. + enrich it with utility classes or html_attrs, such as :class:`cmsplugin_cascade.bootstrap4.mixins.BootstrapUtilities`. + If anchor_fields is specified in the property_fields attributes, these attribute choices are set when the request + is available whit id elements of the current page. """ + def __str__(self): return self.plugin_class.get_identifier(self) def get_form(self, request, obj=None, **kwargs): form = kwargs.get('form', self.form) + for anchors_field in self.fields_with_choices_anchors: + if hasattr(obj.page,'cascadepage'): + currentpage_element_ids = obj.page.cascadepage.glossary.get('element_ids', {}) + self.utility_form_mixin.base_fields[anchors_field].choices=[[items,value] for items, value in currentpage_element_ids.items()] + else: + self.utility_form_mixin.base_fields[anchors_field].choices=[] assert issubclass(form, EntangledModelFormMixin), "Form must inherit from EntangledModelFormMixin" kwargs['form'] = type(form.__name__, (self.utility_form_mixin, form), {}) return super().get_form(request, obj, **kwargs) + @classmethod def get_css_classes(cls, obj): """Enrich list of CSS classes with customized ones""" css_classes = super().get_css_classes(obj) - for utility_field_name in cls.utility_form_mixin.base_fields.keys(): - css_class = obj.glossary.get(utility_field_name) - if css_class: - css_classes.append(css_class) + if 'css_classes' in cls.attr_type: + for utility_field_name in cls.attr_type['css_classes']: + css_class = obj.glossary.get(utility_field_name) + if css_class: + css_classes.append(css_class) return css_classes + + @classmethod + def get_html_tag_attributes(cls, obj): + """Enrich list of HTML attribute data with customized ones""" + attributes = super().get_html_tag_attributes(obj) + if 'html_data_attrs' in cls.attr_type: + for utility_field_name in cls.attr_type['html_data_attrs']: + attribute = obj.glossary.get(utility_field_name) + if attribute: + attributes.update({utility_field_name:attribute}) + return attributes diff --git a/examples/bs4demo/settings.py b/examples/bs4demo/settings.py index 8e8e3f130..41212eb00 100644 --- a/examples/bs4demo/settings.py +++ b/examples/bs4demo/settings.py @@ -7,6 +7,8 @@ from django.urls import reverse_lazy from cmsplugin_cascade.extra_fields.config import PluginExtraFieldsConfig +from cmsplugin_cascade.bootstrap4.mixins import BootstrapUtilities +from cmsplugin_cascade.generic.mixins_html_attrs import GenericUtilities from django.utils.text import format_lazy DEBUG = True @@ -217,12 +219,30 @@ 'plugins_with_sharables': { 'BootstrapImagePlugin': ('image_shapes', 'image_width_responsive', 'image_width_fixed', 'image_height', 'resize_options',), - 'BootstrapPicturePlugin': ('image_shapes', 'responsive_heights', 'image_size', 'resize_options',), + 'BootstrapPicturePlugin': ('image_shapes', 'responsive_heights', 'resize_options',), }, 'exclude_hiding_plugin': ('SegmentPlugin', 'Badge'), 'allow_plugin_hiding': True, 'leaflet': {'default_position': {'lat': 50.0, 'lng': 12.0, 'zoom': 6}}, 'cache_strides': True, + 'plugins_with_extra_fields': { + 'BootstrapColumnPlugin': PluginExtraFieldsConfig( + css_classes={'multiple': True, 'class_names': 'custom-zoom-over, custom-zoom-over2'}, + inline_styles={ + 'extra_fields:Border': ['border',], + 'extra_fields:Border Radius': ['border-radius'], + 'extra_units:Border Radius': 'px,rem', + 'extra_fields:Colors': ['color', 'background-color'], + 'extra_fields:Margins': ['margin-top', 'margin-right', 'margin-botton,', 'margin-left'], + 'extra_units:Margins': 'px,em', + 'extra_fields:Paddings': ['padding-top', 'padding-right', 'padding-botton,', 'padding-left'], + 'extra_units:Paddings': 'px,em', + }), + }, + 'plugins_with_extra_mixins': { + 'BootstrapColumnPlugin':BootstrapUtilities(BootstrapUtilities.background_and_color, BootstrapUtilities.vertical_margins, BootstrapUtilities.floats, BootstrapUtilities.paddings, BootstrapUtilities.margins, + GenericUtilities.scroll_animate ), + }, } CMS_PLACEHOLDER_CONF = { diff --git a/examples/bs4demo/templates/bs4demo/base.html b/examples/bs4demo/templates/bs4demo/base.html index 002ad408b..86d05577a 100644 --- a/examples/bs4demo/templates/bs4demo/base.html +++ b/examples/bs4demo/templates/bs4demo/base.html @@ -52,6 +52,26 @@

Services

{% endif %} + + + + {% if request.toolbar and request.toolbar.edit_mode_active %} + + {% endif %} {% render_block "js" %} diff --git a/examples/bs4demo/templates/bs4demo/main.html b/examples/bs4demo/templates/bs4demo/main.html index 51ae5935b..6980a235a 100644 --- a/examples/bs4demo/templates/bs4demo/main.html +++ b/examples/bs4demo/templates/bs4demo/main.html @@ -2,7 +2,11 @@ {% load static cms_tags bootstrap_tags sekizai_tags sass_tags %} {% block head %} -{% addtoblock "css" %}{% endaddtoblock %} +{% addtoblock "css" %} + + +{% endaddtoblock %} + {% endblock head %} {% block header %} diff --git a/examples/manage.py b/examples/manage.py index 050490705..8584b3801 100755 --- a/examples/manage.py +++ b/examples/manage.py @@ -8,4 +8,6 @@ from django.core.management import execute_from_command_line os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bs4demo.settings') +# os.environ.setdefault('COMPACT_FORM', 'True') + execute_from_command_line(sys.argv) diff --git a/examples/package.json b/examples/package.json index ba87b90da..9d46e0606 100644 --- a/examples/package.json +++ b/examples/package.json @@ -11,14 +11,16 @@ "angular": "^1.5.11", "angular-animate": "^1.5.11", "angular-sanitize": "^1.5.11", - "ui-bootstrap4": "^3.0.5", "bootstrap": "^4.1.3", + "intersection-observer": "^0.7.0", "jquery": "^3.2.1", "leaflet": "^1.2.0", "leaflet-easybutton": "^2.2.0", "picturefill": "^3.0.2", "popper.js": "^1.12.9", - "select2": "^4.0.3" + "sal.js": "^0.6.5", + "select2": "^4.0.3", + "ui-bootstrap4": "^3.0.5" }, "devDependencies": {}, "scripts": {