Просмотр исходного кода

Meilleure gestion arbre immutable

feature/remote-launch
William Petit 3 лет назад
Родитель
Сommit
519ec99264

+ 1
- 0
css/style.css Просмотреть файл

@@ -294,6 +294,7 @@ html, body {
294 294
   padding: 0 5px;
295 295
   width: 100%;
296 296
   height: 100%;
297
+  overflow-y: auto;
297 298
 }
298 299
 
299 300
 .edit .profile-tree ul {

+ 2
- 0
package.json Просмотреть файл

@@ -27,9 +27,11 @@
27 27
     "lodash": "^3.10.1",
28 28
     "react": "^0.14.0",
29 29
     "react-addons-css-transition-group": "^0.14.0",
30
+    "react-addons-update": "^0.14.2",
30 31
     "react-dnd": "^1.1.5",
31 32
     "react-dom": "^0.14.0",
32 33
     "react-redux": "^2.0.0",
34
+    "recursive-iterator": "^2.0.0",
33 35
     "redux": "^2.0.0",
34 36
     "redux-thunk": "^0.1.0",
35 37
     "winston": "^1.1.2"

+ 1
- 2
src/components/edit/edit-view.js Просмотреть файл

@@ -5,7 +5,6 @@ var DesktopAppList = require('./desktop-app-list.js');
5 5
 var ItemForm = require('./item-form.js');
6 6
 var IconThemeSelector = require('./icon-theme-selector.js');
7 7
 var ProfileMenu = require('./profile-menu.js');
8
-var tree = require('../../util/tree');
9 8
 
10 9
 var actions = require('../../store/actions');
11 10
 var DragDropContext = require('react-dnd').DragDropContext;
@@ -96,7 +95,7 @@ function select(state) {
96 95
     desktopApps: state.desktopApps,
97 96
     profile: state.profile,
98 97
     theme: state.theme,
99
-    selectedItem: tree.matches(state.profile, {selected: true})[0]
98
+    selectedItem: state.selectedItem
100 99
   };
101 100
 }
102 101
 

+ 3
- 2
src/components/edit/profile-tree.js Просмотреть файл

@@ -44,7 +44,7 @@ var TreeNode = React.createClass({
44 44
   renderTreeItem: function(data) {
45 45
     return (
46 46
       <TreeItem data={data}
47
-        selected={data.selected}
47
+        selected={data === this.props.selectedItem}
48 48
         {...this.props} />
49 49
     );
50 50
   }
@@ -93,7 +93,8 @@ var ProfileTree = React.createClass({
93 93
 function select(state) {
94 94
   return {
95 95
     profile: state.profile,
96
-    theme: state.theme
96
+    theme: state.theme,
97
+    selectedItem: state.selectedItem
97 98
   };
98 99
 }
99 100
 

+ 29
- 5
src/store/actions/edit.js Просмотреть файл

@@ -1,4 +1,5 @@
1 1
 var Util = require('../../util');
2
+var Tree = require('../../util/tree');
2 3
 var logger = Util.Logger;
3 4
 var path = require('path');
4 5
 var _ = require('lodash');
@@ -111,10 +112,33 @@ exports.selectProfileItem = function(item) {
111 112
 };
112 113
 
113 114
 exports.updateProfileItem = function(item, key, value) {
114
-  return {
115
-    type: UPDATE_PROFILE_ITEM,
116
-    item: item,
117
-    key: key,
118
-    value: value
115
+  return function(dispatch, getState) {
116
+
117
+    var state = getState();
118
+    var selectedPath, tree;
119
+
120
+    // If the item is selected, save its path
121
+    if(state.selectedItem === item) {
122
+      tree = new Tree(state.profile);
123
+      var result = tree.find(item);
124
+      selectedPath = result.path;
125
+    }
126
+
127
+    dispatch({
128
+      type: UPDATE_PROFILE_ITEM,
129
+      item: item,
130
+      key: key,
131
+      value: value
132
+    });
133
+
134
+    // Re-select item if needed
135
+    if(selectedPath) {
136
+      state = getState();
137
+      tree = new Tree(state.profile);
138
+      var selectedItem = tree.get(selectedPath);
139
+      dispatch(exports.selectProfileItem(selectedItem));
140
+    }
141
+
119 142
   };
143
+
120 144
 };

+ 1
- 1
src/store/index.js Просмотреть файл

@@ -10,7 +10,7 @@ var createStore = redux.applyMiddleware(
10 10
 
11 11
 var appReducer = redux.combineReducers({
12 12
   profile: reducers.profile,
13
-  processOpts: reducers.processOpts,
13
+  selectedItem: reducers.selectedItem,
14 14
   desktopApps: reducers.desktopApps,
15 15
   theme: reducers.theme
16 16
 });

+ 1
- 0
src/store/reducers/index.js Просмотреть файл

@@ -1,3 +1,4 @@
1 1
 exports.desktopApps = require('./desktop-apps');
2 2
 exports.profile = require('./profile');
3 3
 exports.theme = require('./theme');
4
+exports.selectedItem = require('./selected-item');

+ 46
- 54
src/store/reducers/profile.js Просмотреть файл

@@ -1,6 +1,6 @@
1 1
 var _ = require('lodash');
2 2
 var actions = require('../actions');
3
-var tree = require('../../util/tree');
3
+var Tree = require('../../util/tree');
4 4
 
5 5
 module.exports = function(oldProfile, action) {
6 6
 
@@ -28,84 +28,76 @@ module.exports = function(oldProfile, action) {
28 28
       newProfile = updateProfileItem(oldProfile, action.item, action.key, action.value);
29 29
       break;
30 30
 
31
-    case actions.edit.SELECT_PROFILE_ITEM:
32
-      newProfile = selectProfileItem(oldProfile, action.item);
33
-      break;
34
-
35 31
   }
36 32
 
37
-  if(newProfile) tree.walk(newProfile, ensureItemKey);
38
-
39 33
   return newProfile;
40 34
 
41 35
 };
42 36
 
43
-function selectProfileItem(oldProfile, item) {
44
-  var newProfile = _.cloneDeep(oldProfile);
45
-  tree.walk(newProfile, function(currentItem) {
46
-    delete currentItem.selected;
47
-    if( _.isEqual(currentItem, item) ) {
48
-      currentItem.selected = true;
49
-    }
50
-  });
51
-  return newProfile;
37
+function updateProfileItem(profile, targetItem, key, value) {
38
+  var tree = new Tree(profile);
39
+  var result = tree.find(targetItem);
40
+  var itemPath = result.path;
41
+  tree.update(itemPath.concat(key), {$set: value});
42
+  return tree.getState();
52 43
 }
53 44
 
54
-function updateProfileItem(oldProfile, targetItem, key, value) {
55
-  var newProfile = _.cloneDeep(oldProfile);
56
-  var item = tree.find(newProfile, targetItem).item;
57
-  item[key] = value;
58
-  return newProfile;
45
+function removeProfileItem(profile, removedItem) {
46
+  var tree = new Tree(profile);
47
+  var result = tree.find(removedItem);
48
+  tree.del(result.path);
49
+  return tree.getState();
59 50
 }
60 51
 
61
-function removeProfileItem(oldProfile, removedItem) {
62
-
63
-  var newProfile = _.cloneDeep(oldProfile);
64
-  var parent = tree.find(newProfile, removedItem).parent;
52
+function moveProfileItem(profile, movedItem, targetItem) {
65 53
 
66
-  parent.items = _.reject(parent.items, function(item) {
67
-    return _.isEqual(item, removedItem);
68
-  });
54
+  var tree = new Tree(profile);
69 55
 
70
-  return newProfile;
56
+  var movedResult = tree.find(movedItem);
57
+  var targetResult = tree.find(targetItem);
71 58
 
72
-}
59
+  // Remove item from current location
60
+  tree.del(movedResult.path);
73 61
 
74
-function moveProfileItem(oldProfile, movedItem, targetItem) {
62
+  var targetPath = targetResult ? targetResult.path : [];
63
+  var targetItemsPath = targetPath.concat('items');
75 64
 
76
-  var newProfile = _.cloneDeep(oldProfile);
77
-  var previousParent = tree.find(newProfile, movedItem).parent;
78
-  var newParent = tree.find(newProfile, targetItem).item;
65
+  // Create "items" collection if not defined on target item
66
+  if( !('items' in targetItem) ) {
67
+    tree.update(targetItemsPath, {
68
+      "$set": []
69
+    });
70
+  }
79 71
 
80
-  previousParent.items = _.reject(previousParent.items, function(item) {
81
-    return _.isEqual(item, movedItem);
72
+  // Add moved item to target
73
+  tree.update(targetItemsPath, {
74
+    "$push": [movedItem]
82 75
   });
83 76
 
84
-  newParent.items = newParent.items || [];
85
-  newParent.items.push(_.cloneDeep(movedItem));
86
-
87
-  return newProfile;
77
+  return tree.getState();
88 78
 
89 79
 }
90 80
 
91
-function addProfileItem(oldProfile, newItem, targetItem) {
81
+function addProfileItem(profile, newItem, targetItem) {
92 82
 
93
-  var newProfile = _.cloneDeep(oldProfile);
94
-  var newParent = tree.find(newProfile, targetItem).item;
83
+  var tree = new Tree(profile);
95 84
 
96
-  newParent.items = newParent.items || [];
85
+  var targetResult = tree.find(targetItem);
86
+  var targetPath = targetResult ? targetResult.path : [];
87
+  var targetItemsPath = targetPath.concat('items');
97 88
 
98
-  newItem = _.cloneDeep(newItem);
99
-  ensureItemKey(newItem);
89
+  // Create "items" collection if not defined on target item
90
+  if( !('items' in targetItem) ) {
91
+    tree.update(targetItemsPath, {
92
+      "$set": []
93
+    });
94
+  }
100 95
 
101
-  newParent.items.push(newItem);
96
+  // Add moved item to target
97
+  tree.update(targetItemsPath, {
98
+    "$push": [newItem]
99
+  });
102 100
 
103
-  return newProfile;
104
-}
101
+  return tree.getState();
105 102
 
106
-var _inc = 0;
107
-function ensureItemKey(item) {
108
-  if( item && !('_key' in item) ) {
109
-    item._key = 'item_'+Date.now()+'_'+_inc++;
110
-  }
111 103
 }

+ 17
- 0
src/store/reducers/selected-item.js Просмотреть файл

@@ -0,0 +1,17 @@
1
+var actions = require('../actions');
2
+
3
+module.exports = function(selectedItem, action) {
4
+
5
+  switch(action.type) {
6
+
7
+    case actions.edit.SELECT_PROFILE_ITEM:
8
+      selectedItem = action.item;
9
+      break;
10
+
11
+    default:
12
+      return selectedItem || null;
13
+  }
14
+
15
+  return selectedItem;
16
+  
17
+};

+ 55
- 35
src/util/tree.js Просмотреть файл

@@ -1,53 +1,73 @@
1 1
 var _ = require('lodash');
2
+var update = require('react-addons-update');
3
+var RecursiveIterator = require('recursive-iterator');
2 4
 
3
-// Tree manipulation helpers
5
+function Tree(srcState) {
6
+  this._keyCount = 0;
7
+  this._state = srcState || {};
8
+}
4 9
 
5
-exports.walk = function(branch, func, parent) {
10
+var p = Tree.prototype;
6 11
 
7
-  if(!branch) return;
8
-
9
-  var breakHere = func(branch, parent);
10
-
11
-  if(breakHere) return breakHere;
12
-
13
-  var items = branch.items;
14
-
15
-  if(!items) return;
16
-
17
-  for( var i = 0, item = items[i]; (item = items[i]); i++ ) {
18
-    breakHere = exports.walk(item, func, branch);
19
-    if(breakHere) return breakHere;
12
+p.get = function(pathArr) {
13
+  var obj = this._state;
14
+  for(var i = 0, len = pathArr.length; i < len; ++i) {
15
+    obj = obj[pathArr[i]];
16
+    if(!obj) throw new Error('Unexistant tree path: "'+pathArr.join('.')+'"');
20 17
   }
21
-
18
+  return obj;
22 19
 };
23 20
 
24
-exports.find = function(tree, obj) {
25
-
26
-  var result;
21
+p.update = function(pathArr, updateCommands) {
22
+  var updateQuery = this._createUpdateQueryForPath(pathArr, updateCommands);
23
+  this._state = update(this._state, updateQuery);
24
+  return this;
25
+};
27 26
 
28
-  exports.walk(tree, function(item, parent) {
29
-    if( _.isEqual(item, obj) ) {
30
-      result = {item: item, parent: parent};
31
-      return true;
27
+p.del = function(pathArr) {
28
+  var prop = pathArr[pathArr.length-1];
29
+  var parentPath = pathArr.slice(0, -1);
30
+  this.update(parentPath, {
31
+    "$apply": function(item) {
32
+      delete item[prop];
33
+      return item;
32 34
     }
33 35
   });
34
-
35
-  return result;
36
-
36
+  return this;
37 37
 };
38 38
 
39
-exports.matches = function(tree, obj) {
39
+p.find = function(obj) {
40
+  var iterator = this._getStateIterator();
41
+  for(var item = iterator.next(); !item.done; item = iterator.next()) {
42
+    var state = item.value;
43
+    if(state.node === obj) {
44
+      return state;
45
+    }
46
+  }
47
+};
40 48
 
41
-  var results = [];
49
+p.getState = function() {
50
+  return this._state;
51
+};
42 52
 
43
-  var matches = _.matches(obj);
53
+p.toJSON = function() {
54
+  return this._state;
55
+};
44 56
 
45
-  exports.walk(tree, function(item) {
46
-    if( matches(item) ) {
47
-      results.push(item);
48
-    }
49
-  });
50 57
 
51
-  return results;
58
+p._getStateIterator = function() {
59
+  var iterator = new RecursiveIterator(this._state);
60
+  return iterator;
61
+};
52 62
 
63
+p._createUpdateQueryForPath = function(pathArr, updateCommands) {
64
+  var cursor, query = {};
65
+  cursor = query;
66
+  for(var i = 0, len = pathArr.length; i < len-1; ++i) {
67
+    cursor = cursor[pathArr[i]] = {};
68
+  }
69
+  cursor[pathArr[pathArr.length-1]] = updateCommands;
70
+  return query;
53 71
 };
72
+
73
+module.exports = Tree;

+ 65
- 0
test/tree.js Просмотреть файл

@@ -0,0 +1,65 @@
1
+var Tree = require('../src/util/tree');
2
+
3
+var TreeSuite = module.exports = {};
4
+
5
+TreeSuite.getPropertyByPath = function(test) {
6
+  var tree = new Tree(this.treeState);
7
+  var label = tree.get(['items', 1, 'label']);
8
+  test.ok(label === 'root.child2', 'The property value should be "root.child2" !');
9
+  test.done();
10
+};
11
+
12
+TreeSuite.updatePropertyByPath = function(test) {
13
+  var tree = new Tree(this.treeState);
14
+  var path = ['items', 1, 'label'];
15
+  tree.update(path, {$set: 'foo'});
16
+  var label = tree.get(path);
17
+  test.ok(label === 'foo', 'The property value should be "foo" !');
18
+  test.done();
19
+};
20
+
21
+TreeSuite.findItem = function(test) {
22
+  var tree = new Tree(this.treeState);
23
+  var result = tree.find(this.treeItem);
24
+  test.ok(result.path.join('.') === 'items.1.items.0.items.0', 'The result should have a path equal to "items.1.items.0.items.0" !');
25
+  test.done();
26
+};
27
+
28
+TreeSuite.setUp = function(done) {
29
+
30
+  this.treeItem = {
31
+    label: "root.child2.child1.child1",
32
+    items: []
33
+  };
34
+
35
+  this.treeState = {
36
+    label: "root",
37
+    items: [
38
+      {
39
+        label: "root.child1",
40
+        items: [
41
+          {
42
+            label: "root.child1.child1",
43
+            items: [
44
+
45
+            ]
46
+          }
47
+        ]
48
+      },
49
+      {
50
+        label: "root.child2",
51
+        items: [
52
+          {
53
+            label: "root.child2.child1",
54
+            items: [
55
+              this.treeItem
56
+            ]
57
+          }
58
+        ]
59
+      }
60
+    ]
61
+  };
62
+
63
+  done();
64
+
65
+};

Загрузка…
Отмена
Сохранить