promethee 1.0.20 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/promethee-editor.sass +26 -57
  3. data/app/assets/stylesheets/promethee-editor/_move.sass +102 -0
  4. data/app/assets/stylesheets/promethee-editor/_preview.sass +2 -3
  5. data/app/assets/stylesheets/promethee-editor/_write.sass +58 -0
  6. data/app/views/promethee/_edit.html.erb +34 -99
  7. data/app/views/promethee/_localize.html.erb +1 -1
  8. data/app/views/promethee/_show.html.erb +1 -2
  9. data/app/views/promethee/edit/_move.html.erb +17 -38
  10. data/app/views/promethee/edit/_preview.html.erb +4 -36
  11. data/app/views/promethee/edit/_write.html.erb +9 -25
  12. data/app/views/promethee/edit/inspector/_inspector.html.erb +11 -0
  13. data/app/views/promethee/edit/inspector/component/_column.html.erb +2 -2
  14. data/app/views/promethee/edit/inspector/component/_cover.html.erb +1 -1
  15. data/app/views/promethee/edit/inspector/component/_details.html.erb +1 -1
  16. data/app/views/promethee/edit/inspector/component/_image.html.erb +3 -3
  17. data/app/views/promethee/edit/inspector/component/_text.html.erb +2 -11
  18. data/app/views/promethee/edit/inspector/component/_video.html.erb +1 -1
  19. data/app/views/promethee/edit/move/_component.html.erb +3 -1
  20. data/app/views/promethee/edit/move/_components.html.erb +10 -4
  21. data/app/views/promethee/edit/move/component/_column.html.erb +11 -9
  22. data/app/views/promethee/edit/move/component/_cover.html.erb +1 -1
  23. data/app/views/promethee/edit/move/component/_details.html.erb +1 -1
  24. data/app/views/promethee/edit/move/component/_row.html.erb +2 -2
  25. data/app/views/promethee/edit/shared/_data.html.erb +1 -0
  26. data/app/views/promethee/edit/shared/_navbar.html.erb +13 -19
  27. data/app/views/promethee/edit/write/_add_button.html.erb +23 -31
  28. data/app/views/promethee/edit/write/_toolbar.html.erb +1 -1
  29. data/app/views/promethee/edit/write/component/_column.html.erb +5 -37
  30. data/app/views/promethee/edit/write/component/_cover.html.erb +3 -22
  31. data/app/views/promethee/edit/write/component/_details.html.erb +3 -22
  32. data/app/views/promethee/edit/write/component/_image.html.erb +4 -23
  33. data/app/views/promethee/edit/write/component/_row.html.erb +4 -25
  34. data/app/views/promethee/edit/write/component/_slider.html.erb +3 -20
  35. data/app/views/promethee/edit/write/component/_text.html.erb +5 -24
  36. data/app/views/promethee/edit/write/component/_video.html.erb +7 -6
  37. data/lib/promethee/data.rb +56 -100
  38. data/lib/promethee/rails/version.rb +1 -1
  39. metadata +6 -4
  40. data/app/assets/stylesheets/promethee-editor/_component.sass +0 -27
  41. data/app/assets/stylesheets/promethee-editor/_mover.sass +0 -85
@@ -1,5 +1,5 @@
1
1
  <script type="text/ng-template" id="promethee/write/toolbar">
2
2
  <div class="pull-right">
3
- <span ng-click="remove(component, components)" class="promethee-editor__button"><%= fa_icon 'window-close' %></span>
3
+ <span ng-click="remove(component, components)" class="promethee-editor__button"><%= fa_icon :times %></span>
4
4
  </div>
5
5
  </script>
@@ -1,5 +1,6 @@
1
1
  <script type="text/ng-template" id="promethee/write/component/column">
2
2
  <div
3
+ ng-click="inspect(component, $event)"
3
4
  class="
4
5
  col
5
6
  col-md-{{component.attributes.size}}
@@ -7,47 +8,14 @@
7
8
  promethee-editor__component
8
9
  promethee-editor__component--column
9
10
  "
10
- ng-controller="ColumnController"
11
- ng-click="edit($event);"
12
- >
13
- <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': inspected.component === component}">
11
+ >
12
+ <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': promethee.inspected === component}">
14
13
  <div class="promethee-editor__toolbar">
15
14
  Column
16
15
  <ng-include src="'promethee/write/toolbar'"></ng-include>
17
16
  </div>
18
-
19
17
  <ng-include src="'promethee/write/components'"></ng-include>
20
-
21
- <span
22
- type="button"
23
- class="btn btn-default btn-block"
24
- ng-click="addComponentTo(component.children)"
25
- >
26
- Add component
27
- </span>
18
+ <ng-include src="'promethee/write/add_button'"></ng-include>
28
19
  </div>
29
20
  </div>
30
- </script>
31
-
32
- <script>
33
- promethee.controller('ColumnController', ['$scope', 'inspected', function($scope, inspected) {
34
- Object.defineProperty($scope, 'editing', {
35
- get: function() {
36
- return inspected.component == $scope.component;
37
- }
38
- });
39
-
40
- $scope.edit = function(event) {
41
- event.stopPropagation();
42
- inspected.component = $scope.component;
43
- };
44
-
45
- $scope.complete = function() {
46
- inspected.component = null;
47
- };
48
-
49
- $scope.toggleEdit = function() {
50
- $scope[$scope.editing ? 'complete' : 'edit']();
51
- };
52
- }]);
53
- </script>
21
+ </script>
@@ -1,10 +1,10 @@
1
1
  <script type="text/ng-template" id="promethee/write/component/cover">
2
2
  <div
3
3
  ng-controller="CoverController"
4
+ ng-click="inspect(component, $event)"
4
5
  class="promethee-editor__component promethee-editor__component--cover"
5
- ng-click="edit($event);"
6
6
  >
7
- <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': inspected.component === component}">
7
+ <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': promethee.inspected === component}">
8
8
  <div class="promethee-editor__toolbar">
9
9
  Cover
10
10
  <ng-include src="'promethee/write/toolbar'"></ng-include>
@@ -42,26 +42,7 @@
42
42
  }
43
43
  });
44
44
 
45
- promethee.controller('CoverController', ['$scope', 'inspected', function($scopen, inspected) {
46
- Object.defineProperty($scope, 'editing', {
47
- get: function() {
48
- return inspected.component == $scope.component;
49
- }
50
- });
51
-
52
- $scope.edit = function(event) {
53
- event.stopPropagation();
54
- inspected.component = $scope.component;
55
- };
56
-
57
- $scope.complete = function() {
58
- inspected.component = null;
59
- };
60
-
61
- $scope.toggleEdit = function() {
62
- $scope[$scope.editing ? 'complete' : 'edit']();
63
- };
64
-
45
+ promethee.controller('CoverController', ['$scope', function($scopen) {
65
46
  $scope.options = {
66
47
  toolbar: [
67
48
  ['headline', ['style']],
@@ -1,10 +1,10 @@
1
1
  <script type="text/ng-template" id="promethee/write/component/details">
2
2
  <div
3
3
  ng-controller="DetailsController"
4
+ ng-click="inspect(component, $event)"
4
5
  class="promethee-editor__component promethee-editor__component--details"
5
- ng-click="edit($event);"
6
6
  >
7
- <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': inspected.component === component}">
7
+ <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': promethee.inspected === component}">
8
8
  <div class="promethee-editor__toolbar">
9
9
  Details
10
10
  <ng-include src="'promethee/write/toolbar'"></ng-include>
@@ -39,26 +39,7 @@
39
39
  }
40
40
  });
41
41
 
42
- promethee.controller('DetailsController', ['$scope', 'inspected', function($scope, inspected) {
43
- Object.defineProperty($scope, 'editing', {
44
- get: function() {
45
- return inspected.component == $scope.component;
46
- }
47
- });
48
-
49
- $scope.edit = function(event) {
50
- event.stopPropagation();
51
- inspected.component = $scope.component;
52
- };
53
-
54
- $scope.complete = function() {
55
- inspected.component = null;
56
- };
57
-
58
- $scope.toggleEdit = function() {
59
- $scope[$scope.editing ? 'complete' : 'edit']();
60
- };
61
-
42
+ promethee.controller('DetailsController', ['$scope', function($scope) {
62
43
  $scope.options = {
63
44
  toolbar: [
64
45
  ['headline', ['style']],
@@ -1,10 +1,10 @@
1
1
  <script type="text/ng-template" id="promethee/write/component/image">
2
2
  <div
3
3
  ng-controller="ImageController"
4
+ ng-click="inspect(component, $event)"
4
5
  class="promethee-editor__component promethee-editor__component--image"
5
- ng-click="edit($event);"
6
- >
7
- <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': inspected.component === component}">
6
+ >
7
+ <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': promethee.inspected === component}">
8
8
  <div class="promethee-editor__toolbar">
9
9
  Image
10
10
  <ng-include src="'promethee/write/toolbar'"></ng-include>
@@ -36,26 +36,7 @@
36
36
  }
37
37
  });
38
38
 
39
- promethee.controller('ImageController', ['$scope', 'inspected', function($scope, inspected) {
40
- Object.defineProperty($scope, 'editing', {
41
- get: function() {
42
- return inspected.component == $scope.component;
43
- }
44
- });
45
-
46
- $scope.edit = function(event) {
47
- event.stopPropagation();
48
- inspected.component = $scope.component;
49
- };
50
-
51
- $scope.complete = function() {
52
- inspected.component = null;
53
- };
54
-
55
- $scope.toggleEdit = function() {
56
- $scope[$scope.editing ? 'complete' : 'edit']();
57
- };
58
-
39
+ promethee.controller('ImageController', ['$scope', function($scope) {
59
40
  $scope.options = {
60
41
  toolbar: [
61
42
  ['headline', ['style']],
@@ -1,17 +1,17 @@
1
1
  <script type="text/ng-template" id="promethee/write/component/row">
2
2
  <div
3
3
  ng-controller="RowController"
4
+ ng-click="inspect(component, $event)"
4
5
  class="row promethee-editor__component promethee-editor__component--row"
5
- ng-click="edit($event);"
6
6
  >
7
- <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': inspected.component === component}">
7
+ <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': promethee.inspected === component}">
8
8
  <div class="promethee-editor__toolbar">
9
9
  Row
10
10
  <ng-include src="'promethee/write/toolbar'"></ng-include>
11
11
  </div>
12
12
  <ng-include src="'promethee/write/components'"></ng-include>
13
13
  <div class="clearfix"></div>
14
- <span class="btn btn-default btn-block" ng-click="addColumn()" style="margin: 0 6px 6px 6px;width: auto">Add column</span>
14
+ <a ng-click="addColumn()" class="promethee-editor__adder__button"><%= fa_icon :columns, class: "fa-2x" %></a>
15
15
  </div>
16
16
  </div>
17
17
  </script>
@@ -27,28 +27,7 @@
27
27
  }
28
28
  });
29
29
 
30
- promethee.controller('RowController', ['$scope', 'inspected', function($scope, inspected) {
31
- Object.defineProperty($scope, 'editing', {
32
- get: function() {
33
- return inspected.component == $scope.component;
34
- }
35
- });
36
-
37
- $scope.edit = function(event) {
38
- event.stopPropagation();
39
- inspected.component = $scope.component;
40
- };
41
-
42
- $scope.complete = function() {
43
- inspected.component = null;
44
- };
45
-
46
- $scope.toggleEdit = function() {
47
- $scope[$scope.editing ? 'complete' : 'edit']();
48
- };
49
-
50
- $scope.allowedTypes = ['column'];
51
-
30
+ promethee.controller('RowController', ['$scope', function($scope) {
52
31
  $scope.addColumn = function() {
53
32
  this.component.children.push({
54
33
  type: 'column',
@@ -1,10 +1,10 @@
1
1
  <script type="text/ng-template" id="promethee/write/component/slider">
2
2
  <div
3
3
  ng-controller="SliderController"
4
+ ng-click="inspect(component, $event)"
4
5
  class="row promethee-editor__component promethee-editor__component--slider"
5
- ng-click="edit($event);"
6
6
  >
7
- <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': inspected.component === component}">
7
+ <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': promethee.inspected === component}">
8
8
  <div class="promethee-editor__toolbar">
9
9
  Slider
10
10
 
@@ -53,24 +53,7 @@
53
53
  }
54
54
  });
55
55
 
56
- promethee.controller('SliderController', ['$scope', '$element', 'inspected', function($scope, $element, inspected) {
57
- $scope.editing = false;
58
-
59
- $scope.edit = function(event) {
60
- event.stopPropagation();
61
- $scope.editing = true;
62
- inspected.component = $scope.component;
63
- };
64
-
65
- $scope.complete = function() {
66
- $scope.editing = false;
67
- inspected.component = null;
68
- };
69
-
70
- $scope.toggleEdit = function() {
71
- $scope[$scope.editing ? 'complete' : 'edit']();
72
- };
73
-
56
+ promethee.controller('SliderController', ['$scope', '$element', function($scope, $element) {
74
57
  $scope.sliderId = 'slider-' + Date.now() + '-' + Math.floor(Math.random()*1000000);
75
58
 
76
59
  $scope.$watch('editing', function(newVal) {
@@ -1,18 +1,18 @@
1
1
  <script type="text/ng-template" id="promethee/write/component/text">
2
2
  <div
3
3
  ng-controller="TextController"
4
+ ng-click="inspect(component, $event)"
4
5
  class="promethee-editor__component promethee-editor__component--text"
5
- ng-click="edit($event);"
6
6
  >
7
- <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': inspected.component === component}">
7
+ <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': promethee.inspected === component}">
8
8
  <div class="promethee-editor__toolbar">
9
9
  Text
10
10
  <ng-include src="'promethee/write/toolbar'"></ng-include>
11
11
  </div>
12
- <div ng-show="editing">
12
+ <div ng-show="promethee.inspected === component">
13
13
  <summernote config="options" ng-model="component.attributes.body"></summernote>
14
14
  </div>
15
- <div ng-hide="editing">
15
+ <div ng-hide="promethee.inspected === component">
16
16
  <div class="promethee-editor__wrapper" ng-bind-html="component.attributes.body | htmlSafe"></div>
17
17
  </div>
18
18
  </div>
@@ -31,26 +31,7 @@
31
31
  }
32
32
  });
33
33
 
34
- promethee.controller('TextController', ['$scope', 'inspected', function($scope, inspected) {
35
- Object.defineProperty($scope, 'editing', {
36
- get: function() {
37
- return inspected.component == $scope.component;
38
- }
39
- });
40
-
41
- $scope.edit = function(event) {
42
- event.stopPropagation();
43
- inspected.component = $scope.component;
44
- };
45
-
46
- $scope.complete = function() {
47
- inspected.component = null;
48
- };
49
-
50
- $scope.toggleEdit = function() {
51
- $scope[$scope.editing ? 'complete' : 'edit']();
52
- };
53
-
34
+ promethee.controller('TextController', ['$scope', function($scope) {
54
35
  $scope.options = {
55
36
  toolbar: [
56
37
  ['headline', ['style']],
@@ -1,10 +1,10 @@
1
1
  <script type="text/ng-template" id="promethee/write/component/video">
2
2
  <div
3
3
  ng-controller="VideoController"
4
+ ng-click="inspect(component, $event)"
4
5
  class="promethee-editor__component promethee-editor__component--video"
5
- ng-click="edit($event);"
6
6
  >
7
- <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': inspected.component === component}">
7
+ <div class="promethee-editor__component-selected" ng-class="{'promethee-editor__component-selected--visible': promethee.inspected === component}">
8
8
  <div class="promethee-editor__toolbar">
9
9
  Video
10
10
  <ng-include src="'promethee/write/toolbar'"></ng-include>
@@ -12,6 +12,7 @@
12
12
 
13
13
  <div class="embed-responsive embed-responsive-16by9">
14
14
  <iframe ng-if="embed" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen="allowfullscreen" ng-src="{{embed | urlSafe}}"></iframe>
15
+ <div class="video-neutralizing-overlay"></div>
15
16
  </div>
16
17
  </div>
17
18
  </div>
@@ -29,20 +30,20 @@
29
30
  }
30
31
  });
31
32
 
32
- promethee.controller('VideoController', ['$scope', 'inspected', function($scope, inspected) {
33
+ promethee.controller('VideoController', ['$scope', function($scope) {
33
34
  Object.defineProperty($scope, 'editing', {
34
35
  get: function() {
35
- return inspected.component == $scope.component;
36
+ return inspected == $scope.component;
36
37
  }
37
38
  });
38
39
 
39
40
  $scope.edit = function(event) {
40
41
  event.stopPropagation();
41
- inspected.component = $scope.component;
42
+ inspected = $scope.component;
42
43
  };
43
44
 
44
45
  $scope.complete = function() {
45
- inspected.component = null;
46
+ inspected = null;
46
47
  };
47
48
 
48
49
  $scope.toggleEdit = function() {
@@ -2,130 +2,86 @@
2
2
  module Promethee
3
3
  class Data
4
4
  def initialize(data, options = {})
5
- @master_data = convert_if_necessary data
6
- @master_data_unlocalized = deep_clone @master_data
7
- localization_data = options[:localization_data]
8
- unless localization_data.nil?
9
- @localization_data = convert_if_necessary localization_data
10
- localize!
11
- end
12
- end
13
-
14
- def localization_data
15
- prepare_localization
16
- @localization_data
5
+ @master_data = hashify data
6
+ @localization_data_raw = hashify options[:localization_data] if options.include? :localization_data
7
+ prepare_localization_data
8
+ localize_master_data
17
9
  end
18
10
 
19
11
  def localization_data_to_json
20
- localization_data.to_json
12
+ @localization_data.to_json
21
13
  end
22
14
 
23
15
  def to_json
24
16
  @master_data.to_json
25
17
  end
26
18
 
27
- def to_h
28
- @master_data
29
- end
19
+ # The data acts as a hash
20
+ def [](value)
21
+ @master_data[value]
22
+ end
30
23
 
31
24
  protected
32
25
 
33
- def convert_if_necessary(string_or_hash_or_anything)
34
- # string_or_hash_or_anything could be a string a hash or anything so we need to handle potential exception to keep this method safe
35
- begin
36
- converted = string_or_hash_or_anything.is_a?(String) ? JSON.parse(string_or_hash_or_anything, symbolize_names: true) : string_or_hash_or_anything
37
- rescue
38
- converted = {}
26
+ def hashify(string_or_hash)
27
+ string_or_hash.is_a?(String) ? JSON.parse(string_or_hash, symbolize_names: true) : string_or_hash
28
+ end
29
+
30
+ # We merge the localization data in the components from the master data.
31
+ # This will :
32
+ # 1 get what's already localized, based on the component id
33
+ # 2 add new components
34
+ # 3 remove components not in the master anymore
35
+ # 4 take the order from the master
36
+ def prepare_localization_data
37
+ @localization_data = {
38
+ version: @master_data[:version],
39
+ components: []
40
+ }
41
+ # We create a flat list of the children and all their child, in the master's order
42
+ master_components_flat = Promethee::Data.flatten_components @master_data.deep_dup[:children]
43
+ master_components_flat.each do |component|
44
+ localized_component = Promethee::Data.find_localized_component component[:id], @localization_data_raw
45
+ # We take either the localized, or the master component
46
+ data = localized_component || component
47
+ # We add master reference
48
+ data[:master] = component.deep_dup
49
+ # We add it to the list of localized components
50
+ @localization_data[:components] << data
39
51
  end
40
-
41
- # The parsed json could be anything: "\"hey\"" => "hey", "[\"hey\"]" => ["hey"], "null" => nil, ...
42
- converted = {} unless converted.is_a? Hash
43
-
44
- # The data might not be a json string so the "symbolize_names" options wouldn't do anything on a ruby hash because JSON.parse wouldn't be called
45
- converted.deep_symbolize_keys!
46
-
47
- # The children array is required in order to make this class work
48
- converted[:children] = [] unless converted[:children].is_a?(Array)
49
-
50
- # The type is required in order to make this class work
51
- converted[:type] = converted[:type].is_a?(String) || converted[:type].is_a?(Symbol) ? converted[:type].to_s : 'page'
52
-
53
- converted
54
- end
55
-
56
- def deep_clone(hash)
57
- JSON.parse(hash.to_json, symbolize_names: true)
58
- end
59
-
60
- # This will recursively merge the localization_data into the master_data
61
- def localize!
62
- merge!
63
- localize_children!
64
- end
65
-
66
- def merge!
67
- @master_data[:attributes].merge! localized_component[:attributes] if localized?
68
- end
69
-
70
- # Look for localized component with the same id
71
- def localized_component
72
- @localized_component ||= components.select { |component| component[:id] == @master_data[:id] }.first
73
- end
74
-
75
- def components
76
- @components ||= @localization_data.include?(:components) ? @localization_data[:components] : []
77
52
  end
78
53
 
79
- def localized?
80
- !localized_component.nil?
54
+ # Recursively merge the localization_data into the master_data
55
+ def localize_master_data
56
+ Promethee::Data.localize_component @master_data, @localization_data
81
57
  end
82
58
 
83
- def localize_children!
84
- children.each do |child_hash|
85
- child_data = Promethee::Data.new child_hash, localization_data: @localization_data
86
- child_data.localize!
87
- end
59
+ def self.find_localized_component(id, array)
60
+ return nil if array.nil?
61
+ return nil unless array.include? :components
62
+ array[:components].select { |component| component[:id] == id }.first
88
63
  end
89
64
 
90
- def children
91
- return [] if !@master_data.include? :children or @master_data[:children].nil?
92
- @master_data[:children]
65
+ def self.localize_component(component, localization_data)
66
+ localize_component_attributes component, localization_data
67
+ localize_component_children component, localization_data
93
68
  end
94
69
 
95
- def flat_children
96
- self.class.flatten_components @master_data_unlocalized[:children]
70
+ def self.localize_component_attributes(component, localization_data)
71
+ return unless component.include? :attributes
72
+ localized_component = Promethee::Data.find_localized_component component[:id], localization_data
73
+ return if localized_component.nil?
74
+ component[:attributes].merge! localized_component[:attributes]
97
75
  end
98
76
 
99
- def prepare_localization
100
- # TODO extract localizable components from master data, in the correct order, merged with existing ones
101
- # TODO update master_version
102
-
103
- # Selects only text components within the children components flat array
104
- # TODO think about a better way to handle localized attributes among all the components (not only text type)
105
- flat_master_data = flat_children.select { |component| component[:type].to_sym === :text }
106
-
107
- if @localization_data
108
- # If localization_data has been provided, we merge flat_master_data components with its components
109
- @localization_data[:components] = flat_master_data.map do |component|
110
- localized_component = @localization_data[:components].find do |localized_component|
111
- localized_component[:id] == component[:id]
112
- end
113
-
114
- # If the localized_component isn't found, we create it with the master (component)
115
- # Eventually, we add a reference to the master in the localized component
116
- (localized_component || component).merge master: component.deep_dup
117
- end
118
- else
119
- # In the other case, localization_data components are flat_master_data components
120
- @localization_data = {
121
- version: @master_data[:version],
122
- components: flat_master_data
123
- }
124
- end
77
+ def self.localize_component_children(component, localization_data)
78
+ return unless component.include? :children
79
+ component[:children].each { |child| localize_component child, localization_data }
125
80
  end
126
81
 
127
- # This method take an array of components and puts every components and their children into a unique flat array
128
- def self.flatten_components components
82
+ # Takes an array of components and puts every component and their children into a unique flat array
83
+ def self.flatten_components(components)
84
+ return [] if components.nil?
129
85
  components.reduce [] do |flat_components, component|
130
86
  flat_components +
131
87
  [component.except(:children)] +
@@ -133,4 +89,4 @@ module Promethee
133
89
  end
134
90
  end
135
91
  end
136
- end
92
+ end