Edition via drag & drop des items
This commit is contained in:
@ -12,14 +12,7 @@ var App = React.createClass({
|
||||
|
||||
var editMode = this.props.processOpts.edit || false;
|
||||
|
||||
var view = editMode ?
|
||||
<Provider store={store}>
|
||||
{ function() { return <EditView />; }.bind(this) }
|
||||
</Provider> :
|
||||
<Provider store={store}>
|
||||
{ function() { return <LauncherView />; }.bind(this) }
|
||||
</Provider>
|
||||
;
|
||||
var view = editMode ? <EditView /> : <LauncherView />;
|
||||
|
||||
return (
|
||||
<div id="pitaya">
|
||||
|
@ -11,7 +11,7 @@ module.exports = React.createClass({
|
||||
mixins: [LazyLoad],
|
||||
|
||||
getInitialState: function() {
|
||||
return { icon: DEFAULT_ICON, currentTheme: undefined };
|
||||
return { icon: DEFAULT_ICON, currentTheme: '' };
|
||||
},
|
||||
|
||||
onInViewport: function() {
|
||||
@ -23,7 +23,7 @@ module.exports = React.createClass({
|
||||
var currentTheme = this.state.currentTheme;
|
||||
var newTheme = this.props.theme;
|
||||
|
||||
if( !this.isInViewport() || newTheme === currentTheme ) return;
|
||||
if( !this.isInViewport() || newTheme === currentTheme ) return;
|
||||
|
||||
this.setState({ icon: LOADING_ICON, currentTheme: newTheme });
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
var React = require('react');
|
||||
var Util = require('../../util');
|
||||
var AppIcon = require('../common/app-icon.jsx');
|
||||
var DragSource = require('react-dnd').DragSource;
|
||||
|
||||
module.exports = React.createClass({
|
||||
var DesktopAppItem = React.createClass({
|
||||
|
||||
render: function() {
|
||||
|
||||
@ -11,8 +12,10 @@ module.exports = React.createClass({
|
||||
var category = desktopEntry.Categories;
|
||||
var icon = desktopEntry.Icon;
|
||||
|
||||
return (
|
||||
<li className="desktop-app">
|
||||
var connectDragSource = this.props.connectDragSource;
|
||||
|
||||
return connectDragSource(
|
||||
<li className="desktop-app list-group-item">
|
||||
<AppIcon className="desktop-app-icon" icon={icon} theme={this.props.theme} />
|
||||
<span className="desktop-app-label">{label}</span>
|
||||
</li>
|
||||
@ -21,3 +24,32 @@ module.exports = React.createClass({
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var dragSourceSpec = {
|
||||
|
||||
beginDrag: function(props) {
|
||||
return props;
|
||||
},
|
||||
|
||||
endDrag: function(props, monitor) {
|
||||
|
||||
if (!monitor.didDrop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var dropResult = monitor.getDropResult();
|
||||
|
||||
return props.onItemDropped(props.desktopEntry, dropResult);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function dragSourceCollect(connect, monitor) {
|
||||
return {
|
||||
connectDragSource: connect.dragSource(),
|
||||
isDragging: monitor.isDragging()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = DragSource('NEW_ITEM', dragSourceSpec, dragSourceCollect)(DesktopAppItem);
|
||||
|
@ -1,39 +1,33 @@
|
||||
var React = require('react');
|
||||
var Util = require('../../util');
|
||||
var DesktopAppItem = require('./desktop-app-item.jsx');
|
||||
var IconThemeSelector = require('./icon-theme-selector.jsx');
|
||||
var path = require('path');
|
||||
var debug = require('debug')('pitaya:desktop-app-list');
|
||||
var debug = require('../../util/debug')('pitaya:desktop-app-list');
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
selectedTheme: null
|
||||
};
|
||||
},
|
||||
var DesktopAppList = React.createClass({
|
||||
|
||||
render: function() {
|
||||
|
||||
var items = this.props.desktopApps.map(function(desktopApp, i) {
|
||||
var desktopEntry = desktopApp.content['Desktop Entry'];
|
||||
return <DesktopAppItem theme={this.state.selectedTheme} key={desktopApp.path} desktopEntry={desktopEntry} />;
|
||||
return (
|
||||
<DesktopAppItem theme={this.props.selectedTheme}
|
||||
key={desktopApp.path}
|
||||
desktopEntry={desktopEntry}
|
||||
onItemDropped={this.props.onItemDropped} />
|
||||
);
|
||||
}.bind(this));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<IconThemeSelector onThemeSelected={this.onThemeSelected} />
|
||||
<ul className="desktop-apps">
|
||||
<div className="apps-list">
|
||||
<ul className="desktop-apps list-group">
|
||||
{items}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
},
|
||||
|
||||
onThemeSelected: function(theme) {
|
||||
console.log('Selected theme %s', theme);
|
||||
this.setState({ selectedTheme: theme });
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = DesktopAppList;
|
||||
|
@ -1,7 +1,13 @@
|
||||
var React = require('react');
|
||||
var connect = require('react-redux').connect;
|
||||
var ProfileTree = require('./profile-tree.jsx');
|
||||
var DesktopAppList = require('./desktop-app-list.jsx');
|
||||
var actions = require('../../actions');
|
||||
var ItemForm = require('./item-form.jsx');
|
||||
var IconThemeSelector = require('./icon-theme-selector.jsx');
|
||||
|
||||
var actions = require('../../store/actions');
|
||||
var DragDropContext = require('react-dnd').DragDropContext;
|
||||
var HTML5Backend = require('react-dnd/modules/backends/HTML5');
|
||||
|
||||
var EditView = React.createClass({
|
||||
|
||||
@ -13,18 +19,49 @@ var EditView = React.createClass({
|
||||
|
||||
return (
|
||||
<div className="edit">
|
||||
<DesktopAppList desktopApps={this.props.desktopApps} />
|
||||
<div className="menu-bar">
|
||||
|
||||
</div>
|
||||
<div className="workspace">
|
||||
<div className="left-menu">
|
||||
<IconThemeSelector onThemeSelected={this.onThemeSelected} />
|
||||
<DesktopAppList
|
||||
theme={this.props.theme}
|
||||
desktopApps={this.props.desktopApps}
|
||||
onItemDropped={this.onItemDropped} />
|
||||
</div>
|
||||
<ProfileTree />
|
||||
<ItemForm />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
},
|
||||
|
||||
onItemDropped: function(desktopEntry, targetItem) {
|
||||
|
||||
var newProfileItem = {
|
||||
label: desktopEntry.Name,
|
||||
icon: desktopEntry.Icon,
|
||||
exec: desktopEntry.Exec
|
||||
};
|
||||
|
||||
this.props.dispatch(actions.edit.addProfileItem(newProfileItem, targetItem));
|
||||
|
||||
},
|
||||
|
||||
onThemeSelected: function(theme) {
|
||||
this.props.dispatch(actions.edit.selectTheme(theme));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function select(state) {
|
||||
return {
|
||||
desktopApps: state.desktopApps
|
||||
desktopApps: state.desktopApps,
|
||||
profile: state.profile,
|
||||
theme: state.theme
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = connect(select)(EditView);
|
||||
module.exports = DragDropContext(HTML5Backend)(connect(select)(EditView));
|
||||
|
@ -39,9 +39,11 @@ module.exports = React.createClass({
|
||||
);
|
||||
|
||||
return (
|
||||
<select value={selectedTheme} onChange={this.onChange}>
|
||||
{options}
|
||||
</select>
|
||||
<div className="icon-theme-selector">
|
||||
<select className="form-control" value={selectedTheme} onChange={this.onChange}>
|
||||
{options}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
16
js/components/edit/item-form.jsx
Normal file
16
js/components/edit/item-form.jsx
Normal file
@ -0,0 +1,16 @@
|
||||
var React = require('react');
|
||||
|
||||
var ItemForm = React.createClass({
|
||||
|
||||
render: function() {
|
||||
|
||||
return (
|
||||
<div className="item-form">
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = ItemForm;
|
67
js/components/edit/profile-tree.jsx
Normal file
67
js/components/edit/profile-tree.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
var React = require('react');
|
||||
var connect = require('react-redux').connect;
|
||||
var actions = require('../../store/actions');
|
||||
var TreeItem = require('./tree-item.jsx');
|
||||
|
||||
var TreeNode = React.createClass({
|
||||
|
||||
render: function() {
|
||||
|
||||
var data = this.props.data || {};
|
||||
var subItems = data.items || [];
|
||||
|
||||
var listElements = subItems.map(function(subItem, i) {
|
||||
return (
|
||||
<li key={i} >
|
||||
<TreeNode data={subItem} onItemMoved={this.props.onItemMoved} />
|
||||
</li>
|
||||
);
|
||||
}.bind(this));
|
||||
|
||||
var appEntry = data.icon || data.label ?
|
||||
<TreeItem data={data} onItemMoved={this.props.onItemMoved} /> :
|
||||
null
|
||||
;
|
||||
|
||||
return (
|
||||
<div className="tree-item">
|
||||
{appEntry}
|
||||
<ul>
|
||||
{listElements}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var ProfileTree = React.createClass({
|
||||
|
||||
componentDidMount: function() {
|
||||
this.props.dispatch(actions.launcher.loadProfile('./default-profile.json'));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
return (
|
||||
<div className="profile-tree">
|
||||
<TreeNode data={this.props.profile} onItemMoved={this.onItemMoved} />
|
||||
</div>
|
||||
);
|
||||
|
||||
},
|
||||
|
||||
onItemMoved: function(movedItem, targetItem) {
|
||||
this.props.dispatch(actions.edit.moveProfileItem(movedItem, targetItem));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function select(state) {
|
||||
return {
|
||||
profile: state.profile
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = connect(select)(ProfileTree);
|
86
js/components/edit/tree-item.jsx
Normal file
86
js/components/edit/tree-item.jsx
Normal file
@ -0,0 +1,86 @@
|
||||
var React = require('react/addons');
|
||||
var classNames = require('classnames');
|
||||
var AppIcon = require('../common/app-icon.jsx');
|
||||
var DragSource = require('react-dnd').DragSource;
|
||||
var DropTarget = require('react-dnd').DropTarget;
|
||||
var _ = require('lodash');
|
||||
|
||||
var TreeItem = React.createClass({
|
||||
|
||||
render: function() {
|
||||
|
||||
var data = this.props.data;
|
||||
var appIcon = data.icon ? <AppIcon icon={data.icon} /> : null;
|
||||
|
||||
var connectDragSource = this.props.connectDragSource;
|
||||
var connectDropTarget = this.props.connectDropTarget;
|
||||
|
||||
var classes = classNames({
|
||||
'alert': true,
|
||||
'alert-default': !this.props.isDragging && !this.props.isOver,
|
||||
'alert-info': this.props.isDragging,
|
||||
'alert-success': this.props.isOver
|
||||
});
|
||||
|
||||
return connectDropTarget(connectDragSource(
|
||||
<div className={classes}>
|
||||
{appIcon}
|
||||
<span className="app-label">{data.label}</span>
|
||||
</div>
|
||||
));
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var dragSourceSpec = {
|
||||
|
||||
beginDrag: function(props) {
|
||||
return props.data;
|
||||
},
|
||||
|
||||
endDrag: function(props, monitor) {
|
||||
|
||||
if (!monitor.didDrop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var dropResult = monitor.getDropResult();
|
||||
|
||||
return props.onItemMoved(props.data, dropResult);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var dropTargetSpec = {
|
||||
|
||||
drop: function(props, monitor, component) {
|
||||
return props.data;
|
||||
},
|
||||
|
||||
canDrop: function(props, monitor) {
|
||||
var draggedItem = monitor.getItem();
|
||||
return !_.isEqual(draggedItem, props.data);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function dragSourceCollect(connect, monitor) {
|
||||
return {
|
||||
connectDragSource: connect.dragSource(),
|
||||
isDragging: monitor.isDragging()
|
||||
};
|
||||
}
|
||||
|
||||
function dropTargetCollect(connect, monitor) {
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
module.exports = DropTarget(['ITEM', 'NEW_ITEM'], dropTargetSpec, dropTargetCollect)(
|
||||
DragSource('ITEM', dragSourceSpec, dragSourceCollect)(TreeItem)
|
||||
);
|
@ -2,7 +2,7 @@ var React = require('react');
|
||||
var CategoryHeader = require('./category-header.jsx');
|
||||
var AppList = require('./app-list.jsx');
|
||||
var AnimateMixin = require('../mixins/animate');
|
||||
var actions = require('../../actions');
|
||||
var actions = require('../../store/actions');
|
||||
var connect = require('react-redux').connect;
|
||||
var debug = require('../../util/debug')('launcher-view');
|
||||
|
||||
|
@ -46,7 +46,7 @@ module.exports = {
|
||||
};
|
||||
|
||||
|
||||
var computeComponentsVisibilityDebounced = debounce(computeComponentsVisibility, 250);
|
||||
var computeComponentsVisibilityDebounced = debounce(computeComponentsVisibility, 100);
|
||||
|
||||
// Start listening for changes
|
||||
window.document.addEventListener('scroll', computeComponentsVisibilityDebounced, true);
|
||||
|
@ -1,12 +1,15 @@
|
||||
var Util = require('../util');
|
||||
var Util = require('../../util');
|
||||
var path = require('path');
|
||||
|
||||
// Action types
|
||||
var LOAD_DESKTOP_APPS = exports.LOAD_PROFILE = 'LOAD_DESKTOP_APPS';
|
||||
var LOAD_DESKTOP_APPS_SUCCESS = exports.LOAD_DESKTOP_APPS_SUCCESS = 'LOAD_DESKTOP_APPS_SUCCESS';
|
||||
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 ADD_PROFILE_ITEM = exports.ADD_PROFILE_ITEM = 'ADD_PROFILE_ITEM';
|
||||
|
||||
// Actions creators
|
||||
|
||||
// Actions creator
|
||||
exports.loadDesktopApps = function() {
|
||||
return function(dispatch, getState) {
|
||||
|
||||
@ -27,3 +30,20 @@ exports.loadDesktopApps = function() {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
exports.moveProfileItem = function(movedItem, targetItem) {
|
||||
return {
|
||||
type: 'MOVE_PROFILE_ITEM',
|
||||
movedItem: movedItem,
|
||||
targetItem: targetItem
|
||||
};
|
||||
};
|
||||
|
||||
exports.addProfileItem = function(newItem, targetItem) {
|
||||
return {
|
||||
type: 'ADD_PROFILE_ITEM',
|
||||
newItem: newItem,
|
||||
targetItem: targetItem
|
||||
};
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
var Util = require('../util');
|
||||
var Util = require('../../util');
|
||||
|
||||
var LOAD_PROFILE = exports.LOAD_PROFILE = 'LOAD_PROFILE';
|
||||
var LOAD_PROFILE_SUCCESS = exports.LOAD_PROFILE_SUCCESS = 'LOAD_PROFILE_SUCCESS';
|
@ -2,9 +2,11 @@ var redux = require('redux');
|
||||
var thunkMiddleware = require('redux-thunk');
|
||||
var loggerMiddleware = require('redux-logger');
|
||||
var reducers = require('./reducers');
|
||||
var loggerMiddleware = require('./middlewares/logger');
|
||||
|
||||
var createStore = redux.applyMiddleware(
|
||||
thunkMiddleware
|
||||
thunkMiddleware,
|
||||
loggerMiddleware
|
||||
)(redux.createStore);
|
||||
|
||||
var appReducer = redux.combineReducers({
|
||||
|
15
js/store/middlewares/logger.js
Normal file
15
js/store/middlewares/logger.js
Normal file
@ -0,0 +1,15 @@
|
||||
var debug = require('../../util/debug')('store:logger');
|
||||
|
||||
module.exports = function loggerMiddleware(store) {
|
||||
return function(next) {
|
||||
return function(action) {
|
||||
debug('Action %j', action);
|
||||
debug('Store current state %j', store.getState());
|
||||
next(action);
|
||||
debug('Store new state %j', store.getState());
|
||||
if(action.error) {
|
||||
console.error(action.error.stack || action.error);
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
@ -1,8 +1,8 @@
|
||||
var actions = require('../../actions');
|
||||
var actions = require('../actions');
|
||||
|
||||
module.exports = function(state, action) {
|
||||
|
||||
var desktopApps = [];
|
||||
var desktopApps = state || [];
|
||||
|
||||
if( action.type === actions.edit.LOAD_DESKTOP_APPS_SUCCESS ) {
|
||||
desktopApps = action.desktopApps;
|
||||
|
@ -1,17 +1,72 @@
|
||||
var actions = require('../../actions');
|
||||
var _ = require('lodash');
|
||||
var actions = require('../actions');
|
||||
|
||||
module.exports = function(oldProfile, action) {
|
||||
|
||||
var newProfile = oldProfile || null;
|
||||
|
||||
switch(action.type) {
|
||||
|
||||
case actions.launcher.LOAD_PROFILE_SUCCESS:
|
||||
newProfile = action.profile;
|
||||
break;
|
||||
return _.cloneDeep(action.profile);
|
||||
|
||||
case actions.edit.MOVE_PROFILE_ITEM:
|
||||
return moveProfileItem(oldProfile, action.movedItem, action.targetItem);
|
||||
|
||||
case actions.edit.ADD_PROFILE_ITEM:
|
||||
return addProfileItem(oldProfile, action.newItem, action.targetItem);
|
||||
|
||||
default:
|
||||
return oldProfile || null;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
function moveProfileItem(oldProfile, movedItem, targetItem) {
|
||||
|
||||
var newProfile = _.cloneDeep(oldProfile);
|
||||
var previousParent = treeFind(newProfile, movedItem).parent;
|
||||
var newParent = treeFind(newProfile, targetItem).item;
|
||||
|
||||
previousParent.items = _.reject(previousParent.items, function(item) {
|
||||
return _.isEqual(item, movedItem);
|
||||
});
|
||||
|
||||
newParent.items = newParent.items || [];
|
||||
newParent.items.push(_.cloneDeep(movedItem));
|
||||
|
||||
return newProfile;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
function addProfileItem(oldProfile, newItem, targetItem) {
|
||||
|
||||
var newProfile = _.cloneDeep(oldProfile);
|
||||
var newParent = treeFind(newProfile, targetItem).item;
|
||||
|
||||
newParent.items = newParent.items || [];
|
||||
newParent.items.push(_.cloneDeep(newItem));
|
||||
|
||||
return newProfile;
|
||||
}
|
||||
|
||||
function treeFind(branch, obj) {
|
||||
|
||||
var items = branch.items;
|
||||
|
||||
if(!items) return;
|
||||
|
||||
for( var i = 0, item = items[i]; (item = items[i]); i++ ) {
|
||||
|
||||
if( _.isEqual(item, obj) ) {
|
||||
return {item: item, parent: branch};
|
||||
}
|
||||
|
||||
if(item.items) {
|
||||
var result = treeFind(item, obj);
|
||||
if(result) return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,10 +3,15 @@ var util = require('util');
|
||||
|
||||
module.exports = function createLogger(namespace) {
|
||||
var logger = debug('pitaya:'+namespace);
|
||||
var console = global.window ? global.window.console : global.console;
|
||||
var isNWContext = 'window' in global;
|
||||
var console = isNWContext ? global.window.console : global.console;
|
||||
logger.log = function() {
|
||||
var str = util.format.apply(util, arguments);
|
||||
console.log(str);
|
||||
if(isNWContext) {
|
||||
console.log.apply(console, arguments);
|
||||
} else {
|
||||
var str = util.format.apply(util, arguments);
|
||||
console.log(str);
|
||||
}
|
||||
};
|
||||
return logger;
|
||||
};
|
||||
|
Reference in New Issue
Block a user