Explorar el Código

Meilleure gestion arbre immutable

upgrade-electron
wpetit hace 4 años
padre
commit
519ec99264
Se han modificado 11 ficheros con 221 adiciones y 99 borrados
  1. +1
    -0
      css/style.css
  2. +2
    -0
      package.json
  3. +1
    -2
      src/components/edit/edit-view.js
  4. +3
    -2
      src/components/edit/profile-tree.js
  5. +29
    -5
      src/store/actions/edit.js
  6. +1
    -1
      src/store/index.js
  7. +1
    -0
      src/store/reducers/index.js
  8. +46
    -54
      src/store/reducers/profile.js
  9. +17
    -0
      src/store/reducers/selected-item.js
  10. +55
    -35
      src/util/tree.js
  11. +65
    -0
      test/tree.js

+ 1
- 0
css/style.css Ver fichero

@@ -294,6 +294,7 @@ html, body {
padding: 0 5px;
width: 100%;
height: 100%;
overflow-y: auto;
}

.edit .profile-tree ul {

+ 2
- 0
package.json Ver fichero

@@ -27,9 +27,11 @@
"lodash": "^3.10.1",
"react": "^0.14.0",
"react-addons-css-transition-group": "^0.14.0",
"react-addons-update": "^0.14.2",
"react-dnd": "^1.1.5",
"react-dom": "^0.14.0",
"react-redux": "^2.0.0",
"recursive-iterator": "^2.0.0",
"redux": "^2.0.0",
"redux-thunk": "^0.1.0",
"winston": "^1.1.2"

+ 1
- 2
src/components/edit/edit-view.js Ver fichero

@@ -5,7 +5,6 @@ var DesktopAppList = require('./desktop-app-list.js');
var ItemForm = require('./item-form.js');
var IconThemeSelector = require('./icon-theme-selector.js');
var ProfileMenu = require('./profile-menu.js');
var tree = require('../../util/tree');

var actions = require('../../store/actions');
var DragDropContext = require('react-dnd').DragDropContext;
@@ -96,7 +95,7 @@ function select(state) {
desktopApps: state.desktopApps,
profile: state.profile,
theme: state.theme,
selectedItem: tree.matches(state.profile, {selected: true})[0]
selectedItem: state.selectedItem
};
}


+ 3
- 2
src/components/edit/profile-tree.js Ver fichero

@@ -44,7 +44,7 @@ var TreeNode = React.createClass({
renderTreeItem: function(data) {
return (
<TreeItem data={data}
selected={data.selected}
selected={data === this.props.selectedItem}
{...this.props} />
);
}
@@ -93,7 +93,8 @@ var ProfileTree = React.createClass({
function select(state) {
return {
profile: state.profile,
theme: state.theme
theme: state.theme,
selectedItem: state.selectedItem
};
}


+ 29
- 5
src/store/actions/edit.js Ver fichero

@@ -1,4 +1,5 @@
var Util = require('../../util');
var Tree = require('../../util/tree');
var logger = Util.Logger;
var path = require('path');
var _ = require('lodash');
@@ -111,10 +112,33 @@ exports.selectProfileItem = function(item) {
};

exports.updateProfileItem = function(item, key, value) {
return {
type: UPDATE_PROFILE_ITEM,
item: item,
key: key,
value: value
return function(dispatch, getState) {

var state = getState();
var selectedPath, tree;

// If the item is selected, save its path
if(state.selectedItem === item) {
tree = new Tree(state.profile);
var result = tree.find(item);
selectedPath = result.path;
}

dispatch({
type: UPDATE_PROFILE_ITEM,
item: item,
key: key,
value: value
});

// Re-select item if needed
if(selectedPath) {
state = getState();
tree = new Tree(state.profile);
var selectedItem = tree.get(selectedPath);
dispatch(exports.selectProfileItem(selectedItem));
}

};

};

+ 1
- 1
src/store/index.js Ver fichero

@@ -10,7 +10,7 @@ var createStore = redux.applyMiddleware(

var appReducer = redux.combineReducers({
profile: reducers.profile,
processOpts: reducers.processOpts,
selectedItem: reducers.selectedItem,
desktopApps: reducers.desktopApps,
theme: reducers.theme
});

+ 1
- 0
src/store/reducers/index.js Ver fichero

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

+ 46
- 54
src/store/reducers/profile.js Ver fichero

@@ -1,6 +1,6 @@
var _ = require('lodash');
var actions = require('../actions');
var tree = require('../../util/tree');
var Tree = require('../../util/tree');

module.exports = function(oldProfile, action) {

@@ -28,84 +28,76 @@ module.exports = function(oldProfile, action) {
newProfile = updateProfileItem(oldProfile, action.item, action.key, action.value);
break;

case actions.edit.SELECT_PROFILE_ITEM:
newProfile = selectProfileItem(oldProfile, action.item);
break;

}

if(newProfile) tree.walk(newProfile, ensureItemKey);

return newProfile;

};

function selectProfileItem(oldProfile, item) {
var newProfile = _.cloneDeep(oldProfile);
tree.walk(newProfile, function(currentItem) {
delete currentItem.selected;
if( _.isEqual(currentItem, item) ) {
currentItem.selected = true;
}
});
return newProfile;
function updateProfileItem(profile, targetItem, key, value) {
var tree = new Tree(profile);
var result = tree.find(targetItem);
var itemPath = result.path;
tree.update(itemPath.concat(key), {$set: value});
return tree.getState();
}

function updateProfileItem(oldProfile, targetItem, key, value) {
var newProfile = _.cloneDeep(oldProfile);
var item = tree.find(newProfile, targetItem).item;
item[key] = value;
return newProfile;
function removeProfileItem(profile, removedItem) {
var tree = new Tree(profile);
var result = tree.find(removedItem);
tree.del(result.path);
return tree.getState();
}

function removeProfileItem(oldProfile, removedItem) {

var newProfile = _.cloneDeep(oldProfile);
var parent = tree.find(newProfile, removedItem).parent;
function moveProfileItem(profile, movedItem, targetItem) {

parent.items = _.reject(parent.items, function(item) {
return _.isEqual(item, removedItem);
});
var tree = new Tree(profile);

return newProfile;
var movedResult = tree.find(movedItem);
var targetResult = tree.find(targetItem);

}
// Remove item from current location
tree.del(movedResult.path);

function moveProfileItem(oldProfile, movedItem, targetItem) {
var targetPath = targetResult ? targetResult.path : [];
var targetItemsPath = targetPath.concat('items');

var newProfile = _.cloneDeep(oldProfile);
var previousParent = tree.find(newProfile, movedItem).parent;
var newParent = tree.find(newProfile, targetItem).item;
// Create "items" collection if not defined on target item
if( !('items' in targetItem) ) {
tree.update(targetItemsPath, {
"$set": []
});
}

previousParent.items = _.reject(previousParent.items, function(item) {
return _.isEqual(item, movedItem);
// Add moved item to target
tree.update(targetItemsPath, {
"$push": [movedItem]
});

newParent.items = newParent.items || [];
newParent.items.push(_.cloneDeep(movedItem));

return newProfile;
return tree.getState();

}

function addProfileItem(oldProfile, newItem, targetItem) {
function addProfileItem(profile, newItem, targetItem) {

var newProfile = _.cloneDeep(oldProfile);
var newParent = tree.find(newProfile, targetItem).item;
var tree = new Tree(profile);

newParent.items = newParent.items || [];
var targetResult = tree.find(targetItem);
var targetPath = targetResult ? targetResult.path : [];
var targetItemsPath = targetPath.concat('items');

newItem = _.cloneDeep(newItem);
ensureItemKey(newItem);
// Create "items" collection if not defined on target item
if( !('items' in targetItem) ) {
tree.update(targetItemsPath, {
"$set": []
});
}

newParent.items.push(newItem);
// Add moved item to target
tree.update(targetItemsPath, {
"$push": [newItem]
});

return newProfile;
}
return tree.getState();

var _inc = 0;
function ensureItemKey(item) {
if( item && !('_key' in item) ) {
item._key = 'item_'+Date.now()+'_'+_inc++;
}
}

+ 17
- 0
src/store/reducers/selected-item.js Ver fichero

@@ -0,0 +1,17 @@
var actions = require('../actions');

module.exports = function(selectedItem, action) {

switch(action.type) {

case actions.edit.SELECT_PROFILE_ITEM:
selectedItem = action.item;
break;

default:
return selectedItem || null;
}

return selectedItem;
};

+ 55
- 35
src/util/tree.js Ver fichero

@@ -1,53 +1,73 @@
var _ = require('lodash');
var update = require('react-addons-update');
var RecursiveIterator = require('recursive-iterator');

// Tree manipulation helpers
function Tree(srcState) {
this._keyCount = 0;
this._state = srcState || {};
}

exports.walk = function(branch, func, parent) {
var p = Tree.prototype;

if(!branch) return;

var breakHere = func(branch, parent);

if(breakHere) return breakHere;

var items = branch.items;

if(!items) return;

for( var i = 0, item = items[i]; (item = items[i]); i++ ) {
breakHere = exports.walk(item, func, branch);
if(breakHere) return breakHere;
p.get = function(pathArr) {
var obj = this._state;
for(var i = 0, len = pathArr.length; i < len; ++i) {
obj = obj[pathArr[i]];
if(!obj) throw new Error('Unexistant tree path: "'+pathArr.join('.')+'"');
}
return obj;
};

exports.find = function(tree, obj) {

var result;
p.update = function(pathArr, updateCommands) {
var updateQuery = this._createUpdateQueryForPath(pathArr, updateCommands);
this._state = update(this._state, updateQuery);
return this;
};

exports.walk(tree, function(item, parent) {
if( _.isEqual(item, obj) ) {
result = {item: item, parent: parent};
return true;
p.del = function(pathArr) {
var prop = pathArr[pathArr.length-1];
var parentPath = pathArr.slice(0, -1);
this.update(parentPath, {
"$apply": function(item) {
delete item[prop];
return item;
}
});

return result;

return this;
};

exports.matches = function(tree, obj) {
p.find = function(obj) {
var iterator = this._getStateIterator();
for(var item = iterator.next(); !item.done; item = iterator.next()) {
var state = item.value;
if(state.node === obj) {
return state;
}
}
};

var results = [];
p.getState = function() {
return this._state;
};

var matches = _.matches(obj);
p.toJSON = function() {
return this._state;
};

exports.walk(tree, function(item) {
if( matches(item) ) {
results.push(item);
}
});

return results;
p._getStateIterator = function() {
var iterator = new RecursiveIterator(this._state);
return iterator;
};

p._createUpdateQueryForPath = function(pathArr, updateCommands) {
var cursor, query = {};
cursor = query;
for(var i = 0, len = pathArr.length; i < len-1; ++i) {
cursor = cursor[pathArr[i]] = {};
}
cursor[pathArr[pathArr.length-1]] = updateCommands;
return query;
};

module.exports = Tree;

+ 65
- 0
test/tree.js Ver fichero

@@ -0,0 +1,65 @@
var Tree = require('../src/util/tree');

var TreeSuite = module.exports = {};

TreeSuite.getPropertyByPath = function(test) {
var tree = new Tree(this.treeState);
var label = tree.get(['items', 1, 'label']);
test.ok(label === 'root.child2', 'The property value should be "root.child2" !');
test.done();
};

TreeSuite.updatePropertyByPath = function(test) {
var tree = new Tree(this.treeState);
var path = ['items', 1, 'label'];
tree.update(path, {$set: 'foo'});
var label = tree.get(path);
test.ok(label === 'foo', 'The property value should be "foo" !');
test.done();
};

TreeSuite.findItem = function(test) {
var tree = new Tree(this.treeState);
var result = tree.find(this.treeItem);
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" !');
test.done();
};

TreeSuite.setUp = function(done) {

this.treeItem = {
label: "root.child2.child1.child1",
items: []
};

this.treeState = {
label: "root",
items: [
{
label: "root.child1",
items: [
{
label: "root.child1.child1",
items: [

]
}
]
},
{
label: "root.child2",
items: [
{
label: "root.child2.child1",
items: [
this.treeItem
]
}
]
}
]
};

done();

};

Cargando…
Cancelar
Guardar