Refactoring store

This commit is contained in:
wpetit 2015-09-16 17:26:56 +02:00
parent 1136b693fd
commit a2f0a03671
11 changed files with 208 additions and 22 deletions

View File

@ -126,6 +126,7 @@ html, body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
overflow-y: auto;
} }
.edit .item-form { .edit .item-form {
@ -141,7 +142,6 @@ html, body {
.edit .apps-list ul.desktop-apps { .edit .apps-list ul.desktop-apps {
list-style: none; list-style: none;
padding: 0; padding: 0;
overflow-y: auto;
height: 100%; height: 100%;
margin: 10px 0 0 0; margin: 10px 0 0 0;
padding: 0 10px 0 0; padding: 0 10px 0 0;

View File

@ -11,7 +11,7 @@ var DesktopAppList = React.createClass({
var items = this.props.desktopApps.map(function(desktopApp, i) { var items = this.props.desktopApps.map(function(desktopApp, i) {
var desktopEntry = desktopApp.content['Desktop Entry']; var desktopEntry = desktopApp.content['Desktop Entry'];
return ( return (
<DesktopAppItem theme={this.props.selectedTheme} <DesktopAppItem theme={this.props.theme}
key={desktopApp.path} key={desktopApp.path}
desktopEntry={desktopEntry} desktopEntry={desktopEntry}
onItemDropped={this.props.onItemDropped} /> onItemDropped={this.props.onItemDropped} />

View File

@ -24,21 +24,21 @@ var EditView = React.createClass({
</div> </div>
<div className="workspace"> <div className="workspace">
<div className="left-menu"> <div className="left-menu">
<IconThemeSelector onThemeSelected={this.onThemeSelected} /> <IconThemeSelector onThemeSelected={this.handleThemeSelect} />
<DesktopAppList <DesktopAppList
theme={this.props.theme} theme={this.props.theme}
desktopApps={this.props.desktopApps} desktopApps={this.props.desktopApps}
onItemDropped={this.onItemDropped} /> onItemDropped={this.handleItemDrop} />
</div> </div>
<ProfileTree /> <ProfileTree />
<ItemForm /> <ItemForm item={this.props.selectedItem} onItemChange={this.handleItemChange} />
</div> </div>
</div> </div>
); );
}, },
onItemDropped: function(desktopEntry, targetItem) { handleItemDrop: function(desktopEntry, targetItem) {
var newProfileItem = { var newProfileItem = {
label: desktopEntry.Name, label: desktopEntry.Name,
@ -50,8 +50,12 @@ var EditView = React.createClass({
}, },
onThemeSelected: function(theme) { handleThemeSelect: function(theme) {
this.props.dispatch(actions.edit.selectTheme(theme)); this.props.dispatch(actions.edit.useIconTheme(theme));
},
handleItemChange: function(item, key, value) {
this.props.dispatch(actions.edit.updateProfileItem(item, key, value));
} }
}); });
@ -60,7 +64,8 @@ function select(state) {
return { return {
desktopApps: state.desktopApps, desktopApps: state.desktopApps,
profile: state.profile, profile: state.profile,
theme: state.theme theme: state.theme,
selectedItem: state.selectedItem
}; };
} }

View File

@ -2,13 +2,74 @@ var React = require('react');
var ItemForm = React.createClass({ var ItemForm = React.createClass({
getInitialState: function() {
return {
label: '',
icon: '',
exec: ''
};
},
componentWillReceiveProps: function(props) {
if(props.item) {
this.setState({
label: props.item.label,
icon: props.item.icon,
exec: props.item.exec
});
}
},
render: function() { render: function() {
var state = this.state;
return ( return (
<div className="item-form"> <div className="item-form">
<form>
<div className="form-group">
<label>Label</label>
<input type="text" className="form-control"
placeholder="Label"
value={state.label}
onChange={this.handleChange.bind(this, 'label')} />
</div>
<div className="form-group">
<label>Icon</label>
<input type="text" className="form-control"
placeholder="Icon"
value={state.icon}
onChange={this.handleChange.bind(this, 'icon')} />
</div>
<div className="form-group">
<label>Exec</label>
<input type="text" className="form-control"
placeholder="Exec" value={state.exec}
onChange={this.handleChange.bind(this, 'exec')} />
</div>
</form>
</div> </div>
); );
},
handleChange: function(key, evt) {
evt.preventDefault();
var newState = {};
var value = evt.currentTarget.value;
newState[key] = value;
this.setState(newState);
if(typeof this.props.onItemChange === 'function') {
this.props.onItemChange(this.props.item, key, value);
}
} }
}); });

View File

@ -13,13 +13,17 @@ var TreeNode = React.createClass({
var listElements = subItems.map(function(subItem, i) { var listElements = subItems.map(function(subItem, i) {
return ( return (
<li key={i} > <li key={i} >
<TreeNode data={subItem} onItemMoved={this.props.onItemMoved} /> <TreeNode data={subItem}
selectedItem={this.props.selectedItem}
onItemClicked={this.props.onItemClicked}
onItemMoved={this.props.onItemMoved}
theme={this.props.theme} />
</li> </li>
); );
}.bind(this)); }.bind(this));
var appEntry = data.icon || data.label ? var appEntry = data.icon || data.label ?
<TreeItem data={data} onItemMoved={this.props.onItemMoved} /> : this.renderTreeItem(data):
null null
; ;
@ -32,6 +36,14 @@ var TreeNode = React.createClass({
</div> </div>
); );
},
renderTreeItem: function(data) {
return (
<TreeItem data={data}
selected={data.selected}
{...this.props} />
);
} }
}); });
@ -46,21 +58,36 @@ var ProfileTree = React.createClass({
return ( return (
<div className="profile-tree"> <div className="profile-tree">
<TreeNode data={this.props.profile} onItemMoved={this.onItemMoved} /> {this.renderTreeNode(this.props.profile)}
</div> </div>
); );
}, },
renderTreeNode: function(data) {
return (
<TreeNode data={data}
selectedItem={this.props.selectedItem}
onItemClicked={this.onItemSelected}
onItemMoved={this.onItemMoved}
theme={this.props.theme} />
);
},
onItemMoved: function(movedItem, targetItem) { onItemMoved: function(movedItem, targetItem) {
this.props.dispatch(actions.edit.moveProfileItem(movedItem, targetItem)); this.props.dispatch(actions.edit.moveProfileItem(movedItem, targetItem));
},
onItemSelected: function(selectedItem) {
this.props.dispatch(actions.edit.selectProfileItem(selectedItem));
} }
}); });
function select(state) { function select(state) {
return { return {
profile: state.profile profile: state.profile,
theme: state.theme
}; };
} }

View File

@ -10,20 +10,24 @@ var TreeItem = React.createClass({
render: function() { render: function() {
var data = this.props.data; var data = this.props.data;
var appIcon = data.icon ? <AppIcon icon={data.icon} /> : null; var appIcon = data.icon ? <AppIcon icon={data.icon} theme={this.props.theme} /> : null;
var connectDragSource = this.props.connectDragSource; var connectDragSource = this.props.connectDragSource;
var connectDropTarget = this.props.connectDropTarget; var connectDropTarget = this.props.connectDropTarget;
var classes = classNames({ var classes = classNames({
'alert': true, 'alert': true,
'alert-default': !this.props.isDragging && !this.props.isOver, 'alert-default': !this.props.isOver,
'alert-info': this.props.isDragging, 'alert-info': this.props.isOver && this.props.canDrop,
'alert-success': this.props.isOver 'alert-success': this.props.selected
}); });
var style = {
opacity: this.props.isDragging ? 0.5 : 1
};
return connectDropTarget(connectDragSource( return connectDropTarget(connectDragSource(
<div className={classes}> <div className={classes} style={style} onClick={this.handleClick}>
{appIcon} {appIcon}
<span className="app-label">{data.label}</span> <span className="app-label">{data.label}</span>
</div> </div>
@ -31,6 +35,11 @@ var TreeItem = React.createClass({
}, },
handleClick: function(evt) {
evt.preventDefault();
this.props.onItemClicked(this.props.data);
}
}); });
var dragSourceSpec = { var dragSourceSpec = {
@ -76,7 +85,8 @@ function dragSourceCollect(connect, monitor) {
function dropTargetCollect(connect, monitor) { function dropTargetCollect(connect, monitor) {
return { return {
connectDropTarget: connect.dropTarget(), connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver() isOver: monitor.isOver(),
canDrop: monitor.canDrop()
}; };
} }

View File

@ -7,6 +7,9 @@ var LOAD_DESKTOP_APPS_SUCCESS = exports.LOAD_DESKTOP_APPS_SUCCESS = 'LOAD_DESKTO
var LOAD_DESKTOP_APPS_FAILED = exports.LOAD_DESKTOP_APPS_FAILED = 'LOAD_DESKTOP_APPS_FAILED'; var LOAD_DESKTOP_APPS_FAILED = exports.LOAD_DESKTOP_APPS_FAILED = 'LOAD_DESKTOP_APPS_FAILED';
var MOVE_PROFILE_ITEM = exports.MOVE_PROFILE_ITEM = 'MOVE_PROFILE_ITEM'; var MOVE_PROFILE_ITEM = exports.MOVE_PROFILE_ITEM = 'MOVE_PROFILE_ITEM';
var ADD_PROFILE_ITEM = exports.ADD_PROFILE_ITEM = 'ADD_PROFILE_ITEM'; var ADD_PROFILE_ITEM = exports.ADD_PROFILE_ITEM = 'ADD_PROFILE_ITEM';
var USE_ICON_THEME = exports.USE_ICON_THEME = 'USE_ICON_THEME';
var SELECT_PROFILE_ITEM = exports.SELECT_PROFILE_ITEM = 'SELECT_PROFILE_ITEM';
var UPDATE_PROFILE_ITEM = exports.UPDATE_PROFILE_ITEM = 'UPDATE_PROFILE_ITEM';
// Actions creators // Actions creators
@ -31,10 +34,16 @@ exports.loadDesktopApps = function() {
}; };
}; };
exports.useIconTheme = function(theme) {
return {
type: USE_ICON_THEME,
theme: theme
};
};
exports.moveProfileItem = function(movedItem, targetItem) { exports.moveProfileItem = function(movedItem, targetItem) {
return { return {
type: 'MOVE_PROFILE_ITEM', type: MOVE_PROFILE_ITEM,
movedItem: movedItem, movedItem: movedItem,
targetItem: targetItem targetItem: targetItem
}; };
@ -42,8 +51,24 @@ exports.moveProfileItem = function(movedItem, targetItem) {
exports.addProfileItem = function(newItem, targetItem) { exports.addProfileItem = function(newItem, targetItem) {
return { return {
type: 'ADD_PROFILE_ITEM', type: ADD_PROFILE_ITEM,
newItem: newItem, newItem: newItem,
targetItem: targetItem targetItem: targetItem
}; };
}; };
exports.selectProfileItem = function(item) {
return {
type: SELECT_PROFILE_ITEM,
item: item
};
};
exports.updateProfileItem = function(item, key, value) {
return {
type: UPDATE_PROFILE_ITEM,
item: item,
key: key,
value: value
};
};

View File

@ -12,7 +12,8 @@ var createStore = redux.applyMiddleware(
var appReducer = redux.combineReducers({ var appReducer = redux.combineReducers({
profile: reducers.profile, profile: reducers.profile,
processOpts: reducers.processOpts, processOpts: reducers.processOpts,
desktopApps: reducers.desktopApps desktopApps: reducers.desktopApps,
theme: reducers.theme
}); });
module.exports = createStore(appReducer); module.exports = createStore(appReducer);

View File

@ -1,3 +1,4 @@
exports.desktopApps = require('./desktop-apps'); exports.desktopApps = require('./desktop-apps');
exports.profile = require('./profile'); exports.profile = require('./profile');
exports.processOpts = require('./process-opts'); exports.processOpts = require('./process-opts');
exports.theme = require('./theme');

View File

@ -14,6 +14,12 @@ module.exports = function(oldProfile, action) {
case actions.edit.ADD_PROFILE_ITEM: case actions.edit.ADD_PROFILE_ITEM:
return addProfileItem(oldProfile, action.newItem, action.targetItem); return addProfileItem(oldProfile, action.newItem, action.targetItem);
case actions.edit.UPDATE_PROFILE_ITEM:
return updateProfileItem(oldProfile, action.item, action.key, action.value);
case actions.edit.SELECT_PROFILE_ITEM:
return selectProfileItem(oldProfile, action.item);
default: default:
return oldProfile || null; return oldProfile || null;
@ -21,6 +27,19 @@ module.exports = function(oldProfile, action) {
}; };
function selectProfileItem(oldProfile, item) {
var newProfile = _.cloneDeep(oldProfile);
return newProfile;
}
function updateProfileItem(oldProfile, targetItem, key, value) {
var newProfile = _.cloneDeep(oldProfile);
var result = treeFind(newProfile, targetItem);
result.item[key] = value;
return newProfile;
}
function moveProfileItem(oldProfile, movedItem, targetItem) { function moveProfileItem(oldProfile, movedItem, targetItem) {
@ -50,6 +69,29 @@ function addProfileItem(oldProfile, newItem, targetItem) {
return newProfile; return newProfile;
} }
// Tree manipulation helpers
function treeWalk(branch, func) {
var items = branch.items;
if(!items) return;
for( var i = 0, item = items[i]; (item = items[i]); i++ ) {
var breakHere = func(item, parent);
if(breakHere) return breakHere;
if(item.items) {
breakHere = treeWalk(item, func);
if(breakHere) return breakHere;
}
}
}
function treeFind(branch, obj) { function treeFind(branch, obj) {
var items = branch.items; var items = branch.items;

View File

@ -0,0 +1,14 @@
var actions = require('../actions');
module.exports = function(currentTheme, action) {
switch(action.type) {
case actions.edit.USE_ICON_THEME:
return action.theme;
default:
return currentTheme || null;
}
};