Skip to content

Commit

Permalink
Merge pull request #20281 from colemanw/afformDragDrop
Browse files Browse the repository at this point in the history
Afform drag n drop fixes
  • Loading branch information
seamuslee001 authored May 17, 2021
2 parents e7811aa + 933b852 commit a81d027
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 39 deletions.
77 changes: 64 additions & 13 deletions ext/afform/admin/ang/afGuiEditor.css
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
#afGuiEditor #afGuiEditor-palette {
margin-right: 5px;
height: 100%;
}

#afGuiEditor #afGuiEditor-canvas {
margin-left: 5px;
}

#afGuiEditor .panel-body {
padding: 5px 12px;
position: relative;
height: 100%;
}

#afGuiEditor fieldset legend {
Expand All @@ -26,13 +23,39 @@
margin-bottom: 10px;
}

#afGuiEditor #afGuiEditor-palette-tabs li {
#afGuiEditor .panel {
height: 100%;
}
#afGuiEditor .panel-heading {
height: 44px;
padding: 10px;
}
#afGuiEditor .panel-heading ul.nav-tabs {
border-bottom: 0 none;
}
#afGuiEditor .panel-heading ul.nav-tabs li {
top: 1px;
}

#afGuiEditor #afGuiEditor-palette-tabs li > a {
padding: 10px 15px;
#afGuiEditor .panel-heading ul.nav-tabs li.fluid-width-tab {
white-space: nowrap;
overflow: hidden;
}
#afGuiEditor .panel-heading ul.nav-tabs li.active {
max-width: 50%;
}
#afGuiEditor .panel-heading ul.nav-tabs li > a {
padding: 5px 3px 5px 8px;
height: 33px;
font-size: 12px;
margin: 0;
}

#afGuiEditor .panel-body {
padding: 5px 12px;
position: relative;
height: calc(100% - 44px);
overflow-y: scroll;
overflow-x: hidden;
}

#afGuiEditor .af-gui-columns {
Expand All @@ -49,7 +72,7 @@
}

#afGuiEditor .crm-editable-enabled,
#afGuiEditor-palette-tabs > li > a > span {
#afGuiEditor .panel-heading ul.nav-tabs li > a > span {
display: inline-block;
padding: 0 4px !important;
border: 2px solid transparent !important;
Expand Down Expand Up @@ -112,7 +135,7 @@
left: 0;
padding-left: 15px;
}
#afGuiEditor:not(.af-gui-dragging) #afGuiEditor-canvas:hover .af-gui-bar {
#afGuiEditor:not(.af-gui-dragging *) #afGuiEditor-canvas:hover .af-gui-bar {
opacity: 1;
transition: opacity .2s;
}
Expand All @@ -122,6 +145,16 @@
transition: opacity .1s;
}

/* Disable menu while dragging */
body.af-gui-dragging #civicrm-menu {
pointer-events: none;
}
/* Disable scrollbars while dragging */
body.af-gui-dragging {
overflow-x: hidden;
overflow-y: hidden;
}

#afGuiEditor .af-gui-bar .btn.active {
background-color: #b3b3b3;
}
Expand All @@ -142,9 +175,11 @@
position: relative;
padding: 22px 3px 3px;
min-height: 40px;
display: block;
margin-bottom: 10px;
margin-top: 10px;
}

#afGuiEditor af-gui-container,
#afGuiEditor af-gui-markup,
#afGuiEditor af-gui-field,
#afGuiEditor af-gui-edit-options {
Expand All @@ -160,10 +195,12 @@

#afGuiEditor .af-gui-container-type-fieldset {
box-shadow: 0 0 5px #bbbbbb;
margin-top: 20px;
margin-bottom: 20px;
}

#afGuiEditor .af-gui-container:hover,
#afGuiEditor.af-gui-dragging .af-gui-container {
.af-gui-dragging #afGuiEditor .af-gui-container {
border: 2px dashed #757575;
}
#afGuiEditor .af-gui-container.af-gui-dragtarget {
Expand Down Expand Up @@ -217,6 +254,20 @@
margin-top: 10px;
}

#afGuiEditor .ui-sortable-helper {
height: 20px !important;
opacity: .5;
overflow: visible;
}
#afGuiEditor .ui-sortable-helper > * {
background-color: #d5d5d5;
}
#afGuiEditor .ui-sortable-helper .af-gui-palette-item {
height: 30px;
width: 300px;
border: 2px dashed #0071bd;
}

#afGuiEditor .af-gui-entity-palette-select-list {
max-height: 400px;
overflow-y: auto;
Expand Down
4 changes: 2 additions & 2 deletions ext/afform/admin/ang/afGuiEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,10 @@
$(this).removeClass('af-gui-dragtarget');
})
.on('sortstart', '#afGuiEditor', function() {
$('#afGuiEditor').addClass('af-gui-dragging');
$('body').addClass('af-gui-dragging');
})
.on('sortstop', function() {
$('.af-gui-dragging').removeClass('af-gui-dragging');
$('body').removeClass('af-gui-dragging');
$('.af-gui-dragtarget').removeClass('af-gui-dragtarget');
});
});
Expand Down
69 changes: 68 additions & 1 deletion ext/afform/admin/ang/afGuiEditor/afGuiEditor.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,24 @@
$scope.saving = false;
$scope.selectedEntityName = null;
this.meta = afGui.meta;
var editor = this;
var editor = this,
sortableOptions = {};

this.$onInit = function() {
// Load the current form plus blocks & fields
afGui.resetMeta();
afGui.addMeta(this.data);
initializeForm();

$timeout(fixEditorHeight);
$timeout(editor.adjustTabWidths);
$(window)
.on('resize.afGuiEditor', fixEditorHeight)
.on('resize.afGuiEditor', editor.adjustTabWidths);
};

this.$onDestroy = function() {
$(window).off('.afGuiEditor');
};

// Initialize the current form
Expand Down Expand Up @@ -140,7 +151,11 @@
delete $scope.entities[type + num].loading;
if (selectTab) {
editor.selectEntity(type + num);
$timeout(function() {
editor.scrollToEntity(type + num);
});
}
$timeout(editor.adjustTabWidths);
}

if (meta.fields) {
Expand All @@ -165,6 +180,7 @@

this.selectEntity = function(entityName) {
$scope.selectedEntityName = entityName;
$timeout(editor.adjustTabWidths);
};

this.getEntity = function(entityName) {
Expand All @@ -175,6 +191,18 @@
return $scope.selectedEntityName;
};

// Scroll an entity's first fieldset into view of the canvas
this.scrollToEntity = function(entityName) {
var $canvas = $('#afGuiEditor-canvas-body'),
$entity = $('.af-gui-container-type-fieldset[data-entity="' + entityName + '"]').first(),
// Scrolltop value needed to place entity's fieldset at top of canvas
scrollValue = $canvas.scrollTop() + ($entity.offset().top - $canvas.offset().top),
// Maximum possible scrollTop (height minus contents height, adjusting for padding)
maxScroll = $('#afGuiEditor-canvas-body > *').height() - $canvas.height() + 20;
// Exceeding the maximum scrollTop breaks the animation so keep it under the limit
$canvas.animate({scrollTop: scrollValue > maxScroll ? maxScroll : scrollValue}, 500);
};

this.getAfform = function() {
return $scope.afform;
};
Expand Down Expand Up @@ -232,6 +260,24 @@
return options;
}

// Options for ui-sortable in field palette
this.getSortableOptions = function(entityName) {
if (!sortableOptions[entityName + '']) {
sortableOptions[entityName + ''] = {
helper: 'clone',
appendTo: '#afGuiEditor-canvas-body > af-gui-container',
containment: '#afGuiEditor-canvas-body',
update: editor.onDrop,
items: '> div:not(.disabled)',
connectWith: '#afGuiEditor-canvas ' + (entityName ? '[data-entity="' + entityName + '"] > ' : '') + '[ui-sortable]',
placeholder: 'af-gui-dropzone',
tolerance: 'pointer',
scrollSpeed: 8
};
}
return sortableOptions[entityName + ''];
};

// Validates that a drag-n-drop action is allowed
this.onDrop = function(event, ui) {
var sort = ui.item.sortable;
Expand Down Expand Up @@ -278,6 +324,27 @@
});
}
});

// Force editor panels to a fixed height, to avoid palette scrolling offscreen
function fixEditorHeight() {
var height = $(window).height() - $('#afGuiEditor').offset().top;
$('#afGuiEditor').height(Math.floor(height));
}

// Compress tabs on small screens
this.adjustTabWidths = function() {
$('#afGuiEditor .panel-heading ul.nav-tabs li.active').css('max-width', '');
$('#afGuiEditor .panel-heading ul.nav-tabs').each(function() {
var remainingSpace = Math.floor($(this).width()) - 1,
inactiveTabs = $(this).children('li.fluid-width-tab').not('.active');
$(this).children('.active,:not(.fluid-width-tab)').each(function() {
remainingSpace -= $(this).width();
});
if (inactiveTabs.length) {
inactiveTabs.css('max-width', Math.floor(remainingSpace / inactiveTabs.length) + 'px');
}
});
};
}
});

Expand Down
2 changes: 2 additions & 0 deletions ext/afform/admin/ang/afGuiEditor/afGuiEditorCanvas.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
<ul class="nav nav-tabs">
<li role="presentation" ng-class="{active: canvasTab === 'layout'}">
<a href ng-click="canvasTab = 'layout'">
<i class="crm-i fa-list-alt"></i>
<span>{{:: ts('Form Layout') }}</span>
</a>
</li>
<li role="presentation" ng-class="{active: canvasTab === 'markup'}">
<a href ng-click="canvasTab = 'markup'; updateLayoutHtml()">
<i class="crm-i fa-code"></i>
<span>{{:: ts('Markup') }}</span>
</a>
</li>
Expand Down
27 changes: 16 additions & 11 deletions ext/afform/admin/ang/afGuiEditor/afGuiEditorPalette.html
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
<div id="afGuiEditor-palette-config" class="panel panel-default">
<ul id="afGuiEditor-palette-tabs" class="panel-heading nav nav-tabs">
<li role="presentation" ng-class="{active: selectedEntityName === null}">
<div class="panel-heading">
<ul id="afGuiEditor-palette-tabs" class="nav nav-tabs">
<li role="presentation" class="fluid-width-tab" ng-class="{active: selectedEntityName === null}" title="{{:: ts('Form Settings') }}">
<a href ng-click="editor.selectEntity(null)">
<i class="crm-i fa-gear"></i>
<span>{{:: ts('Form Settings') }}</span>
</a>
</li>
<li role="presentation" ng-repeat="entity in entities" ng-class="{active: selectedEntityName === entity.name}">
<a href ng-click="editor.selectEntity(entity.name)">
<span ng-if="!entity.loading && editor.allowEntityConfig" crm-ui-editable ng-model="entity.label">{{ entity.label }}</span>
<span ng-if="!entity.loading && !editor.allowEntityConfig">{{ entity.label }}</span>
<li role="presentation" ng-repeat="entity in entities" class="fluid-width-tab" ng-class="{active: selectedEntityName === entity.name}" title="{{ entity.label }}">
<a href ng-click="editor.selectEntity(entity.name); editor.scrollToEntity(entity.name);">
<i class="crm-i {{:: editor.meta.entities[entity.type].icon }}"></i>
<span ng-if="!entity.loading && editor.allowEntityConfig && selectedEntityName === entity.name" crm-ui-editable ng-model="entity.label" ng-change="editor.adjustTabWidths()">{{ entity.label }}</span>
<span ng-if="!entity.loading && !(editor.allowEntityConfig && selectedEntityName === entity.name)">{{ entity.label }}</span>
<i ng-if="entity.loading" class="crm-i fa-spin fa-spinner"></i>
</a>
</li>
<li role="presentation" ng-repeat="(key, searchDisplay) in editor.meta.searchDisplays" ng-class="{active: selectedEntityName === key}">
<li role="presentation" ng-repeat="(key, searchDisplay) in editor.meta.searchDisplays" class="fluid-width-tab" ng-class="{active: selectedEntityName === key}" title="{{ searchDisplay.label }}">
<a href ng-click="editor.selectEntity(key)">
<i class="crm-i {{:: searchDisplay['type:icon'] }}"></i>
<span>{{ searchDisplay.label }}</span>
</a>
</li>
<li role="presentation" class="dropdown" ng-if="editor.allowEntityConfig">
<a href class="dropdown-toggle" data-toggle="dropdown" title="{{ ts('Add Entity') }}">
<span><i class="crm-i fa-plus"></i></span>
<li role="presentation" class="dropdown" ng-if="editor.allowEntityConfig" title="{{:: ts('Add Entity') }}">
<a href class="dropdown-toggle" data-toggle="dropdown">
<i class="crm-i fa-plus"></i>
</a>
<ul class="dropdown-menu">
<ul class="dropdown-menu dropdown-menu-right">
<li ng-repeat="(entityName, entity) in editor.meta.entities" ng-if="entity.defaults">
<a href ng-click="editor.addEntity(entityName, true)">
<i class="crm-i {{:: entity.icon }}"></i>
Expand All @@ -31,6 +35,7 @@
</ul>
</li>
</ul>
</div>
<div class="panel-body" ng-include="'~/afGuiEditor/config-form.html'" ng-if="selectedEntityName === null"></div>
<div class="panel-body" ng-repeat="entity in entities" ng-if="selectedEntityName === entity.name">
<af-gui-entity entity="entity"></af-gui-entity>
Expand Down
12 changes: 6 additions & 6 deletions ext/afform/admin/ang/afGuiEditor/afGuiEntity.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,26 @@
<div class="af-gui-entity-palette-select-list">
<div ng-if="elementList.length">
<label>{{:: ts('Elements') }}</label>
<div ui-sortable="{update: buildPaletteLists, items: '&gt; div:not(.disabled)', connectWith: '[ui-sortable]', placeholder: 'af-gui-dropzone'}" ui-sortable-update="$ctrl.editor.onDrop" ng-model="elementList">
<div ui-sortable="$ctrl.editor.getSortableOptions()" ui-sortable-update="buildPaletteLists" ng-model="elementList">
<div ng-repeat="element in elementList" >
{{:: elementTitles[$index] }}
<div class="af-gui-palette-item">{{:: elementTitles[$index] }}</div>
</div>
</div>
</div>
<div ng-if="blockList.length">
<label>{{:: ts('Blocks') }}</label>
<div ui-sortable="{update: buildPaletteLists, items: '&gt; div:not(.disabled)', connectWith: '[data-entity=\'' + $ctrl.entity.name + '\'] &gt; [ui-sortable]', placeholder: 'af-gui-dropzone'}" ui-sortable-update="$ctrl.editor.onDrop" ng-model="blockList">
<div ui-sortable="$ctrl.editor.getSortableOptions($ctrl.entity.name)" ui-sortable-update="buildPaletteLists" ng-model="blockList">
<div ng-repeat="block in blockList" ng-class="{disabled: blockInUse(block)}">
{{:: blockTitles[$index] }}
<div class="af-gui-palette-item">{{:: blockTitles[$index] }}</div>
</div>
</div>
</div>
<div ng-repeat="fieldGroup in fieldList">
<div ng-if="fieldGroup.fields.length">
<label>{{ fieldGroup.label }}</label>
<div ui-sortable="{update: buildPaletteLists, items: '&gt; div:not(.disabled)', connectWith: '[data-entity=\'' + fieldGroup.entityName + '\'] &gt; [ui-sortable]', placeholder: 'af-gui-dropzone'}" ui-sortable-update="$ctrl.editor.onDrop" ng-model="fieldGroup.fields">
<div ui-sortable="$ctrl.editor.getSortableOptions(fieldGroup.entityName)" ui-sortable-update="buildPaletteLists" ng-model="fieldGroup.fields">
<div ng-repeat="field in fieldGroup.fields" ng-class="{disabled: fieldInUse(field.name)}">
{{:: getField(fieldGroup.entityType, field.name).label }}
<div class="af-gui-palette-item">{{:: getField(fieldGroup.entityType, field.name).label }}</div>
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit a81d027

Please sign in to comment.