From f3cd38527f512885ea5c02cc85adff7fb4ab026f Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 30 Oct 2019 20:08:47 -0400 Subject: [PATCH] WIP canvas --- ext/afform/gui/ang/afGuiEditor.css | 56 ++++++- ext/afform/gui/ang/afGuiEditor.js | 157 +++++++++++++++--- ext/afform/gui/ang/afGuiEditor/block.html | 19 ++- ext/afform/gui/ang/afGuiEditor/canvas.html | 2 +- .../gui/ang/afGuiEditor/config-entity.html | 6 +- ext/afform/gui/ang/afGuiEditor/field.html | 5 +- ext/afform/gui/ang/afGuiEditor/fieldset.html | 6 - ext/afform/gui/ang/afGuiEditor/palette.html | 6 +- ext/afform/gui/ang/afGuiEditor/text.html | 14 ++ ext/afform/mock/ang/testAfform.aff.html | 10 +- 10 files changed, 235 insertions(+), 46 deletions(-) delete mode 100644 ext/afform/gui/ang/afGuiEditor/fieldset.html create mode 100644 ext/afform/gui/ang/afGuiEditor/text.html diff --git a/ext/afform/gui/ang/afGuiEditor.css b/ext/afform/gui/ang/afGuiEditor.css index 93afb919b5..424e5aad54 100644 --- a/ext/afform/gui/ang/afGuiEditor.css +++ b/ext/afform/gui/ang/afGuiEditor.css @@ -12,7 +12,7 @@ } #afGuiEditor #afGuiEditor-canvas { - flex: 1; + flex: 1.5; margin-left: 5px; } @@ -52,3 +52,57 @@ #afGuiEditor-palette-config .form-inline label { min-width: 110px; } + +#afGuiEditor .af-gui-bar { + cursor: move; + visibility: hidden; + background-color: #efefef; + height: 22px; + position: absolute; + top: 0; + left: 0; + width: 100%; + padding-left:15px; +} +/* grip handle */ +#afGuiEditor .af-gui-bar:before { + background-size: cover; + background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1IiBoZWlnaHQ9IjUiPgo8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSIjODg4Ij48L3JlY3Q+Cjwvc3ZnPg=="); + width: 10px; + height: 15px; + content: ' '; + display: block; + position: absolute; + left: 4px; + top: 5px; +} + +#afGuiEditor-canvas:hover .af-gui-bar { + visibility: visible; +} + +#afGuiEditor .af-gui-field, +#afGuiEditor .af-gui-text, +#afGuiEditor .af-gui-block { + position: relative; + padding: 32px 3px 3px; + min-height: 40px; + border: 2px dashed transparent; +} + +#afGuiEditor #afGuiEditor-canvas .af-entity-selected { + border: 2px dashed #0071bd; +} +#afGuiEditor #afGuiEditor-canvas .af-entity-selected > .af-gui-bar { + background-color: #0071bd; + visibility: visible; + color: white; +} + +#afGuiEditor .af-gui-block { + padding-top: 20px; +} + +#afGuiEditor .af-gui-block:hover { + border: 2px dashed #757575; +} diff --git a/ext/afform/gui/ang/afGuiEditor.js b/ext/afform/gui/ang/afGuiEditor.js index d5b38680d9..45ef072bf6 100644 --- a/ext/afform/gui/ang/afGuiEditor.js +++ b/ext/afform/gui/ang/afGuiEditor.js @@ -8,12 +8,13 @@ scope: { afGuiEditor: '=' }, - link: function($scope, $el, $attr) { + controller: function($scope) { $scope.ts = CRM.ts(); $scope.afform = null; $scope.selectedEntity = null; $scope.meta = CRM.afformAdminData; $scope.controls = {}; + $scope.editor = this; var newForm = { title: ts('Untitled Form'), layout: [{ @@ -51,7 +52,7 @@ $scope.fields = getAllFields($scope.layout['#children']); } - $scope.addEntity = function(entityType) { + this.addEntity = function(entityType) { var existingEntitiesofThisType = _.map(_.filter($scope.entities, {type: entityType}), 'name'), num = existingEntitiesofThisType.length + 1; // Give this new entity a unique name @@ -65,21 +66,29 @@ label: entityType + ' ' + num }; $scope.layout['#children'].unshift($scope.entities[entityType + num]); - $scope.selectEntity(entityType + num); + this.selectEntity(entityType + num); }; - $scope.removeEntity = function(entityName) { + this.removeEntity = function(entityName) { delete $scope.entities[entityName]; _.remove($scope.layout['#children'], {'#tag': 'af-entity', name: entityName}); - $scope.selectEntity(null); + this.selectEntity(null); }; - $scope.selectEntity = function(entityName) { + this.selectEntity = function(entityName) { $scope.selectedEntity = entityName; }; - $scope.getField = function(entityName, fieldName) { - return _.filter($scope.meta.fields[entityName], {name: fieldName})[0]; + this.getField = function(entityType, fieldName) { + return _.filter($scope.meta.fields[entityType], {name: fieldName})[0]; + }; + + this.getEntity = function(entityName) { + return $scope.entities[entityName]; + }; + + this.getSelectedEntity = function() { + return $scope.selectedEntity; }; $scope.valuesFields = function() { @@ -152,44 +161,148 @@ return allFields; } + // Turns a space-separated list (e.g. css classes) into an array + function splitClass(str) { + if (_.isArray(str)) { + return str; + } + return str ? _.unique(_.trim(str).split(/\s+/g)) : []; + } + angular.module('afGuiEditor').directive('afGuiBlock', function() { return { restrict: 'A', templateUrl: '~/afGuiEditor/block.html', scope: { - block: '=afGuiBlock' + node: '=afGuiBlock', + entityName: '=' }, - link: function($scope, element, attrs) { - $scope.isItemVisible = function(block) { - return (block['#tag'] === 'af-fieldset') || (block['#tag'] === 'div' && _.contains(block['class'], 'af-block')); + require: '^^afGuiEditor', + link: function($scope, element, attrs, editor) { + $scope.editor = editor; + }, + controller: function($scope) { + $scope.block = this; + this.node = $scope.node; + + this.modifyClasses = function(item, toRemove, toAdd) { + var classes = splitClass(item['class']); + if (toRemove) { + classes = _.difference(classes, splitClass(toRemove)); + } + if (toAdd) { + classes = _.unique(classes.concat(splitClass(toAdd))); + } + item['class'] = classes.join(' '); + }; + + this.getNodeType = function(node) { + if (!node) { + return null; + } + if (node['#tag'] === 'af-field') { + return 'field'; + } + if (node['af-fieldset']) { + return 'fieldset'; + } + var classes = splitClass(node['class']); + if (_.contains(classes, 'af-block')) { + return 'block'; + } + if (_.contains(classes, 'af-text')) { + return 'text'; + } + return null; + }; + + $scope.isSelectedFieldset = function(entityName) { + return entityName === $scope.editor.getSelectedEntity(); + }; + + $scope.selectEntity = function() { + if ($scope.node['af-fieldset']) { + $scope.editor.selectEntity($scope.node['af-fieldset']); + } }; + + $scope.tags = { + div: ts('Block'), + fieldset: ts('Fieldset') + }; + } }; }); - angular.module('afGuiEditor').directive('afGuiFieldset', function() { + angular.module('afGuiEditor').directive('afGuiField', function() { return { restrict: 'A', - templateUrl: '~/afGuiEditor/fieldset.html', + templateUrl: '~/afGuiEditor/field.html', scope: { - fieldset: '=afGuiFieldset' + node: '=afGuiField', + entityName: '=' + }, + require: '^^afGuiEditor', + link: function($scope, element, attrs, editor) { + $scope.editor = editor; }, - link: function($scope, element, attrs) { - $scope.isItemVisible = function(block) { - return (block['#tag'] === 'af-field') || (block['#tag'] === 'div' && _.contains(block['class'], 'af-block')); + controller: function($scope) { + + $scope.getEntity = function() { + return $scope.editor.getEntity($scope.entityName); + }; + + $scope.getDefn = function() { + return $scope.editor.getField($scope.getEntity().type, $scope.node.name); }; } }; }); - angular.module('afGuiEditor').directive('afGuiField', function() { + angular.module('afGuiEditor').directive('afGuiText', function() { return { restrict: 'A', - templateUrl: '~/afGuiEditor/field.html', + templateUrl: '~/afGuiEditor/text.html', scope: { - field: '=afGuiField' + node: '=afGuiText' }, - link: function($scope, element, attrs) { + require: '^^afGuiBlock', + link: { + pre: function($scope, element, attrs, block) { + $scope.block = block; + }, + post: function($scope, element, attrs) { + if ($scope.block.node && $scope.block.node['#tag'] === 'fieldset') { + $scope.tags.legend = ts('Fieldset Legend'); + } + } + }, + controller: function($scope) { + $scope.tags = { + p: ts('Normal Text'), + h1: ts('Heading 1'), + h2: ts('Heading 2'), + h3: ts('Heading 3'), + h4: ts('Heading 4'), + h5: ts('Heading 5'), + h6: ts('Heading 6') + }; + + $scope.alignments = { + 'text-left': ts('Align left'), + 'text-center': ts('Align center'), + 'text-right': ts('Align right'), + 'text-justify': ts('Justify') + }; + + $scope.getAlign = function() { + return _.intersection(splitClass($scope.node['class']), _.keys($scope.alignments))[0]; + }; + + $scope.setAlign = function(val) { + $scope.block.modifyClasses($scope.node, _.keys($scope.alignments), val); + }; } }; }); diff --git a/ext/afform/gui/ang/afGuiEditor/block.html b/ext/afform/gui/ang/afGuiEditor/block.html index 8ceeee2056..6aca722e1d 100644 --- a/ext/afform/gui/ang/afGuiEditor/block.html +++ b/ext/afform/gui/ang/afGuiEditor/block.html @@ -1,6 +1,17 @@ -
-
-
-
+
+ {{ editor.getEntity(entityName).label }} + {{ node['#tag'] }} + +
+
+
+
+
+
+
+
+
diff --git a/ext/afform/gui/ang/afGuiEditor/canvas.html b/ext/afform/gui/ang/afGuiEditor/canvas.html index f1d862d582..8d0eda2044 100644 --- a/ext/afform/gui/ang/afGuiEditor/canvas.html +++ b/ext/afform/gui/ang/afGuiEditor/canvas.html @@ -3,6 +3,6 @@ {{ ts('Form Layout') }}
-
+
diff --git a/ext/afform/gui/ang/afGuiEditor/config-entity.html b/ext/afform/gui/ang/afGuiEditor/config-entity.html index 4dd21cd271..495319c907 100644 --- a/ext/afform/gui/ang/afGuiEditor/config-entity.html +++ b/ext/afform/gui/ang/afGuiEditor/config-entity.html @@ -1,13 +1,13 @@
- +
{{ ts('Values') }}
- - + + diff --git a/ext/afform/gui/ang/afGuiEditor/field.html b/ext/afform/gui/ang/afGuiEditor/field.html index 0f236440c8..b67b7730c2 100644 --- a/ext/afform/gui/ang/afGuiEditor/field.html +++ b/ext/afform/gui/ang/afGuiEditor/field.html @@ -1,3 +1,6 @@ +
+ {{ getEntity().label + ': ' + getDefn().title }} +
- {{ field.name }} + {{ node.name }}
diff --git a/ext/afform/gui/ang/afGuiEditor/fieldset.html b/ext/afform/gui/ang/afGuiEditor/fieldset.html deleted file mode 100644 index b2c85fef13..0000000000 --- a/ext/afform/gui/ang/afGuiEditor/fieldset.html +++ /dev/null @@ -1,6 +0,0 @@ -
-
-
-
-
-
diff --git a/ext/afform/gui/ang/afGuiEditor/palette.html b/ext/afform/gui/ang/afGuiEditor/palette.html index 78ca043334..5278c642fc 100644 --- a/ext/afform/gui/ang/afGuiEditor/palette.html +++ b/ext/afform/gui/ang/afGuiEditor/palette.html @@ -1,12 +1,12 @@