diff --git a/ui/bundle_templates.js b/ui/bundle_templates.js index 6151a0cfeb..95dfd733e4 100644 --- a/ui/bundle_templates.js +++ b/ui/bundle_templates.js @@ -29,6 +29,7 @@ window.templates = { header: require('./src/templates/forms/inputs/header.hbs'), richText: require('./src/templates/forms/inputs/richText.hbs'), divider: require('./src/templates/forms/inputs/divider.hbs'), + buttonSwitch: require('./src/templates/forms/inputs/buttonSwitch.hbs'), custom: require('./src/templates/forms/inputs/custom.hbs'), datasetSelector: require('./src/templates/forms/inputs/datasetSelector.hbs'), diff --git a/ui/src/core/forms.js b/ui/src/core/forms.js index 54d1bc8b36..2d9aa9e73e 100644 --- a/ui/src/core/forms.js +++ b/ui/src/core/forms.js @@ -725,6 +725,29 @@ window.forms = { }); }); + // Button group switch + findElements( + '.button-switch-input-group', + target, + ).each(function(_k, el) { + const $buttonGroup = $(el).find('.btn-group'); + const $hiddenInput = $(el).find('input[type="hidden"]'); + + $buttonGroup.find('button').on('click', function(ev) { + const $button = $(ev.currentTarget); + + // Deselect all other buttons + $buttonGroup.find('button') + .removeClass('selected'); + // Add selected to target button + $button.addClass('selected'); + + // Update hidden input with chosen button value + $hiddenInput.val($button.data('value')) + .trigger('change'); + }); + }); + // Dataset order clause findElements( '.dataset-order-clause', diff --git a/ui/src/editor-core/element-group.js b/ui/src/editor-core/element-group.js index 14a88351bc..259b088c9d 100644 --- a/ui/src/editor-core/element-group.js +++ b/ui/src/editor-core/element-group.js @@ -238,6 +238,11 @@ ElementGroup.prototype.transform = function(transform) { scaleY: 0, }; + const originalDimensions = { + width: this.width, + height: this.height, + }; + // Apply changes to the group ( updating values ) if (transform.width) { transformation.scaleX = transform.width / this.width; @@ -275,7 +280,66 @@ ElementGroup.prototype.transform = function(transform) { left: elGroup.left + elRelativePositionScaled.left, }); } else { - // TODO: Don't scale, but stick to corners + // Keep top and left on the same place by default + let newTop = el.top - elGroup.top; + let newLeft = el.left - elGroup.left; + + // If bottom bound + if ( + el.groupScaleTypeV === 'bottom' + ) { + // Distance to bottom + const distToBottom = originalDimensions.height - el.height; + + // Calculate top based on bottom position + newTop = newTop - (distToBottom - (elGroup.height - el.height)); + } else if ( + el.groupScaleTypeV === 'middle' + ) { + // Group middle + const groupMiddle = originalDimensions.height / 2; + + const newGroupMiddle = elGroup.height /2; + + // Element middle + const elMiddle = el.height / 2; + + // Distance to middle + const distMiddleToMiddle = groupMiddle - elMiddle; + + // Calculate top based on bottom position + newTop = newTop + (newGroupMiddle - distMiddleToMiddle - elMiddle); + } + + // If right bound + if ( + el.groupScaleTypeH === 'right' + ) { + // Calculate left based on right position + newLeft = newLeft - (originalDimensions.width - elGroup.width); + } else if ( + el.groupScaleTypeV === 'middle' + ) { + // Group center + const groupCenter = originalDimensions.width / 2; + + const newGroupCenter = elGroup.width /2; + + // Element center + const elCenter = el.width / 2; + + // Distance to center + const distCenterToCenter = groupCenter - elCenter; + + // Calculate top based on bottom position + newLeft = newLeft + (newGroupCenter - distCenterToCenter - elCenter); + } + + // Transform without scaling + el.transform({ + top: elGroup.top + newTop, + left: elGroup.left + newLeft, + }); } }); }; diff --git a/ui/src/editor-core/element.js b/ui/src/editor-core/element.js index c33eb84a70..ea41387bd5 100644 --- a/ui/src/editor-core/element.js +++ b/ui/src/editor-core/element.js @@ -61,8 +61,10 @@ const Element = function(data, widgetId, regionId, parentWidget) { // Group scale this.groupScale = (data.groupScale != undefined) ? data.groupScale : 1; - this.groupScaleType = (data.groupScaleType != undefined) ? - data.groupScaleType : 'top_left'; + this.groupScaleTypeV = (data.groupScaleTypeV != undefined) ? + data.groupScaleTypeV : 'top'; + this.groupScaleTypeH = (data.groupScaleTypeH != undefined) ? + data.groupScaleTypeH : 'left'; // Animation effect this.effect = data.effect || 'noTransition'; diff --git a/ui/src/editor-core/properties-panel.js b/ui/src/editor-core/properties-panel.js index d83ce25902..773108f210 100644 --- a/ui/src/editor-core/properties-panel.js +++ b/ui/src/editor-core/properties-panel.js @@ -916,16 +916,15 @@ PropertiesPanel.prototype.render = function( // Create common fields const commonFields = []; - // TODO: for now we disable scaling type // Show scaling type if element is in a group if ( - false && targetAux.groupId != '' && targetAux.groupId != undefined ) { commonFields.unshift( { id: 'groupScale', + customClass: 'group-scale-properties', title: propertiesPanelTrans.groupScale, helpText: propertiesPanelTrans.groupScaleHelpText, value: targetAux.groupScale, @@ -933,31 +932,59 @@ PropertiesPanel.prototype.render = function( visibility: [], }, { - id: 'groupScaleType', - title: propertiesPanelTrans.groupScaleType, - helpText: propertiesPanelTrans.groupScaleTypeHelpText, - value: targetAux.groupScaleType, + id: 'groupScaleTypeH', + customClass: 'group-scale-properties', + title: propertiesPanelTrans.groupScaleTypeH, + helpText: propertiesPanelTrans.groupScaleTypeHHelpText, + value: targetAux.groupScaleTypeH, options: [ { - title: propertiesPanelTrans.groupScaleTypeOptions.topLeft, - name: 'top_left', + title: propertiesPanelTrans.groupScaleTypeOptions.left, + name: 'left', }, { - title: propertiesPanelTrans.groupScaleTypeOptions.topRight, - name: 'top_right', + title: propertiesPanelTrans.groupScaleTypeOptions.center, + name: 'center', }, { - title: propertiesPanelTrans - .groupScaleTypeOptions.bottomLeft, - name: 'bottom_left', + title: propertiesPanelTrans.groupScaleTypeOptions.right, + name: 'right', }, + ], + type: 'buttonSwitch', + visibility: [ + { + conditions: [ + { + field: 'groupScale', + type: 'eq', + value: '0', + }, + ], + }, + ], + }, + { + id: 'groupScaleTypeV', + customClass: 'group-scale-properties', + title: propertiesPanelTrans.groupScaleTypeV, + helpText: propertiesPanelTrans.groupScaleTypeVHelpText, + value: targetAux.groupScaleTypeV, + options: [ { - title: propertiesPanelTrans - .groupScaleTypeOptions.bottomRight, - name: 'bottom_right', + title: propertiesPanelTrans.groupScaleTypeOptions.top, + name: 'top', + }, + { + title: propertiesPanelTrans.groupScaleTypeOptions.middle, + name: 'middle', + }, + { + title: propertiesPanelTrans.groupScaleTypeOptions.bottom, + name: 'bottom', }, ], - type: 'dropdown', + type: 'buttonSwitch', visibility: [ { conditions: [ @@ -1700,6 +1727,11 @@ PropertiesPanel.prototype.render = function( // Update moveable lD.viewer.updateMoveable(); }); + + // Check if we have group scale properties for elements + // and move them to the top of the position tab + self.DOMObject.find('#appearanceTab .group-scale-properties') + .prependTo(self.DOMObject.find('#positionTab')); }; // If it's an element, get properties, first to update it diff --git a/ui/src/editor-core/widget.js b/ui/src/editor-core/widget.js index 47f146febc..f30299fbb7 100644 --- a/ui/src/editor-core/widget.js +++ b/ui/src/editor-core/widget.js @@ -513,9 +513,13 @@ const Widget = function(id, data, regionId = null, layoutObject = null) { // Save group scale type if exists if (element.groupScale) { elementObject.groupScale = 1; - } else if (element.groupScaleType) { + } else if ( + element.groupScaleTypeH && + element.groupScaleTypeV + ) { elementObject.groupScale = 0; - elementObject.groupScaleType = element.groupScaleType; + elementObject.groupScaleTypeH = element.groupScaleTypeH; + elementObject.groupScaleTypeV = element.groupScaleTypeV; } } else { // Save effect if exists diff --git a/ui/src/layout-editor/viewer.js b/ui/src/layout-editor/viewer.js index 0f3390753e..2870ec6a21 100644 --- a/ui/src/layout-editor/viewer.js +++ b/ui/src/layout-editor/viewer.js @@ -2813,6 +2813,11 @@ Viewer.prototype.initMoveable = function() { // Save transformation transformSplit = saveTransformation(e.target); + // Check if item was not resized + if (transformSplit.length === 1) { + return; + } + // Check if the region moved when resizing const moved = ( parseFloat(transformSplit[1]) != 0 || @@ -2850,7 +2855,7 @@ Viewer.prototype.initMoveable = function() { lD.selectedObject.type == 'element-group' ) && self.saveElementGroupProperties( e.target, - false, + true, true, ); }); diff --git a/ui/src/style/forms.scss b/ui/src/style/forms.scss index 820e6cadd1..e1bb0e3e21 100644 --- a/ui/src/style/forms.scss +++ b/ui/src/style/forms.scss @@ -865,4 +865,22 @@ select[readonly].select2-hidden-accessible + .select2-container .select2-selecti } } } +} + +// Button switch +.button-switch-input-group { + button { + color: $xibo-color-primary; + border-color: $xibo-color-primary; + + &:not(.selected):hover { + color: $xibo-color-primary; + background-color: lighten($xibo-color-primary, 35%); + } + + &.selected { + background-color: $xibo-color-primary; + color: $xibo-color-neutral-0; + } + } } \ No newline at end of file diff --git a/ui/src/templates/forms/inputs/buttonSwitch.hbs b/ui/src/templates/forms/inputs/buttonSwitch.hbs new file mode 100644 index 0000000000..197524e510 --- /dev/null +++ b/ui/src/templates/forms/inputs/buttonSwitch.hbs @@ -0,0 +1,36 @@ +
+ +
+ {{#if helpText}} + {{> add-ons/helpText helpText=helpText}} + {{/if}} +
+ +
+ {{!-- Set default property values --}} + {{#unless optionsValue}} + {{set "optionsValue" "name"}} + {{/unless}} + {{#unless optionsTitle}} + {{set "optionsTitle" "title"}} + {{/unless}} + + {{!-- Render options --}} + {{#each options}} + + {{/each}} +
+
\ No newline at end of file diff --git a/views/editorTranslations.twig b/views/editorTranslations.twig index edd0a89252..89aaae0a7f 100644 --- a/views/editorTranslations.twig +++ b/views/editorTranslations.twig @@ -147,13 +147,17 @@ pinSlotHelpText: "{{ "The first item that appears in a slot will be pinned and will not cycle with the rest of the items."|trans }}", groupScale: "{{ "Scale with group"|trans }}", groupScaleHelpText: "{{ "Scale element when scaling containing group."|trans }}", - groupScaleType: "{{ "Align"|trans }}", - groupScaleTypeHelpText: "{{ "Alignment when scaling the containing group."|trans }}", + groupScaleTypeH: "{{ "Horizontal Align"|trans }}", + groupScaleTypeHHelpText: "{{ "Horizontal alignment when scaling the containing group."|trans }}", + groupScaleTypeV: "{{ "Vertical Align"|trans }}", + groupScaleTypeVHelpText: "{{ "Vertical alignment when scaling the containing group."|trans }}", groupScaleTypeOptions: { - topLeft: "{{ "Top/Left" |trans }}", - topRight: "{{ "Top/Right" |trans }}", - bottomLeft: "{{ "Bottom/Left" |trans }}", - bottomRight: "{{ "Bottom/Right" |trans }}", + left: "{{ "Left" |trans }}", + center: "{{ "Center" |trans }}", + right: "{{ "Right" |trans }}", + top: "{{ "Top" |trans }}", + middle: "{{ "Middle" |trans }}", + bottom: "{{ "Bottom" |trans }}", }, somethingWentWrong: "{{ "Something went wrong!"|trans }}", somethingWentWrongEditPermissions: "{{ "Selected item is not shared with you with edit permission!"|trans }}",