diff --git a/css/style.css b/css/style.css
index 3de453b..3ad4842 100644
--- a/css/style.css
+++ b/css/style.css
@@ -122,7 +122,7 @@ html, body {
}
-.edit img.desktop-app-icon {
+.edit .desktop-app > .app-icon {
height: 50px;
width: 50px;
display: inline-block;
diff --git a/js/actions/edit.js b/js/actions/edit.js
new file mode 100644
index 0000000..c94e344
--- /dev/null
+++ b/js/actions/edit.js
@@ -0,0 +1,29 @@
+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';
+
+// Actions creator
+exports.loadDesktopApps = function() {
+ return function(dispatch, getState) {
+
+ var baseDirs = global.process.env.XDG_DATA_DIRS.split(':').map(function(baseDir){
+ return path.join(baseDir, 'applications');
+ });
+
+ dispatch({ type: LOAD_DESKTOP_APPS });
+
+ return Util.DesktopApps.loadAllDesktopFiles(baseDirs)
+ .then(function(desktopApps) {
+ dispatch({ type: LOAD_DESKTOP_APPS_SUCCESS, desktopApps: desktopApps });
+ })
+ .catch(function(err) {
+ dispatch({ type: LOAD_DESKTOP_APPS_FAILED, error: err });
+ })
+ ;
+
+ };
+};
diff --git a/js/actions/index.js b/js/actions/index.js
new file mode 100644
index 0000000..cbeddd1
--- /dev/null
+++ b/js/actions/index.js
@@ -0,0 +1,2 @@
+exports.mauncher = require('./launcher');
+exports.edit = require('./edit');
diff --git a/js/actions/launcher.js b/js/actions/launcher.js
new file mode 100644
index 0000000..6203d17
--- /dev/null
+++ b/js/actions/launcher.js
@@ -0,0 +1,23 @@
+var Util = require('../util');
+
+var LOAD_PROFILE = exports.LOAD_PROFILE = 'LOAD_PROFILE';
+var LOAD_PROFILE_SUCCESS = exports.LOAD_PROFILE_SUCCESS = 'LOAD_PROFILE_SUCCESS';
+var LOAD_PROFILE_FAILED = exports.LOAD_PROFILE_FAILED = 'LOAD_PROFILE_FAILED';
+
+exports.loadProfile = function(profilePath) {
+
+ return function(dispatch, getState) {
+
+ dispatch({ type: LOAD_PROFILE });
+
+ return Util.System.loadJSONFile(profilePath)
+ .then(function(profile) {
+ dispatch({ type: LOAD_PROFILE_SUCCESS, profile: profile });
+ })
+ .catch(function(err) {
+ dispatch({ type: LOAD_PROFILE_FAILED, error: err });
+ })
+ ;
+
+ };
+};
diff --git a/js/app.jsx b/js/app.jsx
index 43715eb..ae43c89 100644
--- a/js/app.jsx
+++ b/js/app.jsx
@@ -3,12 +3,13 @@ var minimist = require('minimist');
var gui = global.window.require('nw.gui');
var LauncherView = require('./components/launcher-view.jsx');
var EditView = require('./components/edit-view.jsx');
+var Provider = require('react-redux').Provider;
+var stores = require('./stores');
// Internal constants
var DEFAULT_PROFILE = './default-profile.json';
var PROCESS_OPTS = minimist(gui.App.argv);
-
// Main component
var App = React.createClass({
@@ -22,8 +23,12 @@ var App = React.createClass({
render: function() {
var view = this.state.editMode ?
- :
-
+
+ { function() { return ; }.bind(this) }
+ :
+
+ { function() { return ; }.bind(this) }
+
;
return (
diff --git a/js/components/app-icon.jsx b/js/components/app-icon.jsx
new file mode 100644
index 0000000..0490f78
--- /dev/null
+++ b/js/components/app-icon.jsx
@@ -0,0 +1,71 @@
+var React = require('react');
+var Util = require('../util');
+var LazyLoad = require('./mixins/lazy-load');
+
+var LOADING_ICON = 'img/hourglass.svg';
+var DEFAULT_ICON = 'img/default-icon.svg';
+
+module.exports = React.createClass({
+
+ mixins: [LazyLoad],
+
+ getInitialState: function() {
+ return { icon: DEFAULT_ICON, currentTheme: undefined };
+ },
+
+ onInViewport: function() {
+ this.updateIconIfInViewport();
+ },
+
+ updateIconIfInViewport: function() {
+
+ var currentTheme = this.state.currentTheme;
+ var newTheme = this.props.theme;
+
+ if( !this.isInViewport() || newTheme === currentTheme ) return;
+
+ this.setState({ icon: LOADING_ICON, currentTheme: newTheme });
+
+ var desktopEntry = this.props.desktopEntry;
+
+ this._findIcon(this.props.icon, newTheme);
+
+ },
+
+ componentDidUpdate: function() {
+ this.updateIconIfInViewport();
+ },
+
+ render: function() {
+
+ var icon = this.state.icon;
+
+ return (
+
+ );
+
+ },
+
+ _findIcon: function(iconPath, theme) {
+
+ var self = this;
+
+ console.log('Search icon %s:%s', iconPath, theme);
+
+ Util.DesktopApps.findIcon(iconPath || DEFAULT_ICON, theme)
+ .then(function(iconPath) {
+ if( !iconPath || /\.xpm$/.test(iconPath) ) {
+ return Util.DesktopApps.findIcon(DEFAULT_ICON, theme);
+ }
+ return iconPath;
+ })
+ .then(function(iconPath) {
+ global.window.requestAnimationFrame(function() {
+ self.setState({ icon: iconPath });
+ });
+ })
+ ;
+
+ }
+
+});
diff --git a/js/components/desktop-app-item.jsx b/js/components/desktop-app-item.jsx
index f58cda6..d8499ef 100644
--- a/js/components/desktop-app-item.jsx
+++ b/js/components/desktop-app-item.jsx
@@ -1,76 +1,23 @@
var React = require('react');
var Util = require('../util');
-var LazyLoad = require('./mixins/lazy-load');
-
-var LOADING_ICON = 'img/hourglass.svg';
-var DEFAULT_ICON = 'img/default-icon.svg';
+var AppIcon = require('./app-icon.jsx');
module.exports = React.createClass({
- mixins: [LazyLoad],
-
- getInitialState: function() {
- return { icon: LOADING_ICON, currentTheme: undefined };
- },
-
- onInViewport: function() {
- this.updateIconIfInViewport();
- },
-
- updateIconIfInViewport: function() {
-
- var currentTheme = this.state.currentTheme;
- var newTheme = this.props.theme;
-
- if( !this.isInViewport() || newTheme === currentTheme ) return;
-
- this.setState({ icon: LOADING_ICON, currentTheme: newTheme });
-
- var desktopEntry = this.props.desktopEntry;
-
- this._findIcon(desktopEntry.Icon, newTheme);
-
- },
-
- componentDidUpdate: function() {
- this.updateIconIfInViewport();
- },
-
render: function() {
var desktopEntry = this.props.desktopEntry;
var label = desktopEntry.Name;
var category = desktopEntry.Categories;
+ var icon = desktopEntry.Icon;
return (
-
+
{label}
);
- },
-
- _findIcon: function(iconPath, theme) {
-
- var self = this;
-
- console.log('Search icon %s:%s', iconPath, theme);
-
- Util.DesktopApps.findIcon(iconPath || DEFAULT_ICON, theme)
- .then(function(iconPath) {
- if( !iconPath || /\.xpm$/.test(iconPath) ) {
- return Util.DesktopApps.findIcon(DEFAULT_ICON, theme);
- }
- return iconPath;
- })
- .then(function(iconPath) {
- global.window.requestAnimationFrame(function() {
- self.setState({ icon: iconPath });
- });
- })
- ;
-
}
});
diff --git a/js/components/desktop-app-list.jsx b/js/components/desktop-app-list.jsx
index 07b5dca..28a8368 100644
--- a/js/components/desktop-app-list.jsx
+++ b/js/components/desktop-app-list.jsx
@@ -9,30 +9,15 @@ module.exports = React.createClass({
getInitialState: function() {
return {
- desktopFiles: [],
selectedTheme: null
};
},
- componentDidMount: function() {
-
- // Load system desktop apps
- var baseDirs = global.process.env.XDG_DATA_DIRS.split(':').map(function(baseDir){
- return path.join(baseDir, 'applications');
- });
-
- Util.DesktopApps.loadAllDesktopFiles('/usr/share/applications')
- .then(function(desktopFiles) {
- this.setState({ desktopFiles: desktopFiles });
- }.bind(this))
- ;
- },
-
render: function() {
- var items = this.state.desktopFiles.map(function(desktopFile, i) {
- var desktopEntry = desktopFile.content['Desktop Entry'];
- return ;
+ var items = this.props.desktopApps.map(function(desktopApp, i) {
+ var desktopEntry = desktopApp.content['Desktop Entry'];
+ return ;
}.bind(this));
return (
diff --git a/js/components/edit-view.jsx b/js/components/edit-view.jsx
index 96922ca..8e874f0 100644
--- a/js/components/edit-view.jsx
+++ b/js/components/edit-view.jsx
@@ -1,16 +1,30 @@
var React = require('react');
+var connect = require('react-redux').connect;
var DesktopAppList = require('./desktop-app-list.jsx');
+var actions = require('../actions');
-module.exports = React.createClass({
+var EditView = React.createClass({
+
+ componentDidMount: function() {
+ this.props.dispatch(actions.edit.loadDesktopApps());
+ },
render: function() {
return (
-
+
);
}
});
+
+function select(state) {
+ return {
+ desktopApps: state.desktopApps
+ };
+}
+
+module.exports = connect(select)(EditView);
diff --git a/js/stores/index.js b/js/stores/index.js
new file mode 100644
index 0000000..b50fb43
--- /dev/null
+++ b/js/stores/index.js
@@ -0,0 +1,21 @@
+var redux = require('redux');
+var thunkMiddleware = require('redux-thunk');
+var loggerMiddleware = require('redux-logger');
+var reducers = require('./reducers');
+
+var createStoreWithMiddleware = redux.applyMiddleware(
+ thunkMiddleware
+)(redux.createStore);
+
+var launcherReducers = redux.combineReducers({
+ profile: reducers.profile,
+ processOpts: reducers.processOpts
+});
+
+var editReducers = redux.combineReducers({
+ desktopApps: reducers.desktopApps,
+ processOpts: reducers.processOpts
+});
+
+exports.launcherStore = createStoreWithMiddleware(launcherReducers);
+exports.editStore = createStoreWithMiddleware(editReducers);
diff --git a/js/stores/reducers/desktop-apps.js b/js/stores/reducers/desktop-apps.js
new file mode 100644
index 0000000..12f0976
--- /dev/null
+++ b/js/stores/reducers/desktop-apps.js
@@ -0,0 +1,13 @@
+var actions = require('../../actions');
+
+module.exports = function(state, action) {
+
+ var desktopApps = [];
+
+ if( action.type === actions.edit.LOAD_DESKTOP_APPS_SUCCESS ) {
+ desktopApps = action.desktopApps;
+ }
+
+ return desktopApps;
+
+};
diff --git a/js/stores/reducers/index.js b/js/stores/reducers/index.js
new file mode 100644
index 0000000..4927732
--- /dev/null
+++ b/js/stores/reducers/index.js
@@ -0,0 +1,3 @@
+exports.desktopApps = require('./desktop-apps');
+exports.profile = require('./profile');
+exports.processOpts = require('./process-opts');
diff --git a/js/stores/reducers/process-opts.js b/js/stores/reducers/process-opts.js
new file mode 100644
index 0000000..f9b8964
--- /dev/null
+++ b/js/stores/reducers/process-opts.js
@@ -0,0 +1,8 @@
+var minimist = require('minimist');
+var gui = global.window.require('nw.gui');
+
+var opts = minimist(gui.App.argv);
+
+module.exports = function(state, action) {
+ return opts;
+};
diff --git a/js/stores/reducers/profile.js b/js/stores/reducers/profile.js
new file mode 100644
index 0000000..b03e048
--- /dev/null
+++ b/js/stores/reducers/profile.js
@@ -0,0 +1,3 @@
+module.exports = function(state, action) {
+ return {};
+};
diff --git a/package.json b/package.json
index 2b019cf..29ef229 100644
--- a/package.json
+++ b/package.json
@@ -23,12 +23,17 @@
"kiosk": false
},
"dependencies": {
+ "bootstrap": "^3.3.5",
"debug": "^2.2.0",
"glob": "^5.0.14",
"ini": "^1.3.4",
"minimist": "^1.1.3",
"node-jsx": "^0.13.3",
"react": "^0.13.3",
- "react-dnd": "^1.1.5"
+ "react-dnd": "^1.1.5",
+ "react-redux": "^2.0.0",
+ "redux": "^2.0.0",
+ "redux-logger": "^1.0.6",
+ "redux-thunk": "^0.1.0"
}
}