Merge branch 'feature/enhanced-edit' into develop

This commit is contained in:
wpetit 2015-10-16 15:13:14 +02:00
commit 8478519d20
41 changed files with 244 additions and 83 deletions

View File

@ -1,3 +1,10 @@
@font-face {
font-family: 'sawasdeeregular';
src: url('../fonts/sawasdee-webfont.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
@ -5,7 +12,7 @@
html, body { html, body {
padding: 0; padding: 0;
margin: 0; margin: 0;
font-family: 'Droid Sans', 'Ubuntu Sans', sans-serif; font-family: 'sawasdeeregular';
background-size: contain; background-size: contain;
background-position: center; background-position: center;
background-color: rgb(34, 107, 160); background-color: rgb(34, 107, 160);
@ -15,23 +22,40 @@ html, body {
overflow-x: hidden; overflow-x: hidden;
} }
/* Common */
.alert.alert-default { .alert.alert-default {
border-radius: 4px; border-radius: 4px;
border: 1px solid #ddd; border: 1px solid #ddd;
} }
.app-icon {
background-position: center center;
background-size: contain;
background-repeat: no-repeat;
transition: background-image 250ms ease-in-out;
}
.full-width {
width: 100%;
}
/* Launcher View */ /* Launcher View */
.launcher { .launcher {
display: flex; display: flex;
width: 100%;
height: 100%;
flex-direction: column; flex-direction: column;
background: url('../img/background.png'); background: url('../img/background.png');
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: cover; background-size: cover;
background-position: center center; background-position: center center;
transition: background-image 250ms ease-in-out; transition: background-image 250ms ease-in-out;
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
} }
.launcher .main { .launcher .main {
@ -44,7 +68,7 @@ html, body {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
display: flex; display: flex;
width: 50px; width: 60px;
} }
.launcher .nav a.goback { .launcher .nav a.goback {
@ -52,6 +76,7 @@ html, body {
color: white; color: white;
font-size: 60px; font-size: 60px;
text-shadow: 1px 1px #444; text-shadow: 1px 1px #444;
font-family: sans-serif;
} }
.launcher .nav a.goback:hover { .launcher .nav a.goback:hover {
@ -75,7 +100,7 @@ html, body {
.launcher ul.apps-list { .launcher ul.apps-list {
margin: 0; margin: 0;
padding: 0; padding: 0 20%;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
list-style: none; list-style: none;
@ -87,7 +112,7 @@ html, body {
} }
.launcher .nav ~ ul.apps-list { .launcher .nav ~ ul.apps-list {
margin-left: -50px; margin-left: -60px;
} }
.launcher li.app-item { .launcher li.app-item {
@ -123,8 +148,9 @@ html, body {
} }
.launcher li.app-item > .app-icon { .launcher li.app-item > .app-icon {
width: 70%; width: 90px;
height: auto; height: 90px;
margin: auto;
} }
.launcher li.app-item > .app-label { .launcher li.app-item > .app-label {
@ -147,21 +173,47 @@ html, body {
flex-direction: column; flex-direction: column;
} }
.edit .title {
margin-top: 5px;
}
.edit .menu-bar { .edit .menu-bar {
padding: 5px 10px; padding: 5px 10px;
display: flex;
flex-direction: row;
} }
.edit .menu-bar button { .edit .menu-bar button {
margin-right: 3px; margin-right: 3px;
} }
.edit .left {
flex: 1;
display: flex;
}
.edit .main {
flex: 3;
display: flex;
}
.edit .right {
flex: 1;
display: flex;
}
.edit .workspace { .edit .workspace {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
padding: 10px; padding: 10px;
flex: 3; flex: 1;
} }
.edit .workspace .main {
flex-direction: column;
}
.edit .left-menu { .edit .left-menu {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -169,6 +221,15 @@ html, body {
overflow-y: auto; overflow-y: auto;
} }
.edit .workspace .left .apps-menu {
display: flex;
flex-direction: column;
}
.edit .workspace .right {
flex-direction: column;
}
.edit .item-form { .edit .item-form {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -179,6 +240,11 @@ html, body {
width: 100%; width: 100%;
} }
.edit .apps-list {
overflow: auto;
margin-top: 5px;
}
.edit .apps-list .icon-theme-selector > select { .edit .apps-list .icon-theme-selector > select {
width: 100%; width: 100%;
} }
@ -187,25 +253,25 @@ html, body {
list-style: none; list-style: none;
padding: 0; padding: 0;
height: 100%; height: 100%;
margin: 10px 0 0 0; display: block;
padding: 0 10px 0 0; margin-right: 5px;
}
.edit .apps-list li.desktop-app {
} }
.edit .desktop-app > .app-icon { .edit .desktop-app > .app-icon {
height: 35px; height: 20px;
width: 35px; width: 20px;
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
margin-right: 10px; margin-right: 10px;
} }
.edit .desktop-app.list-group-item {
padding: 5px 10px;
}
.edit .profile-tree { .edit .profile-tree {
flex: 3;
padding: 0 5px; padding: 0 5px;
width: 100%;
} }
.edit .profile-tree ul { .edit .profile-tree ul {
@ -224,11 +290,13 @@ html, body {
.edit .profile-tree .tree-item .app-icon { .edit .profile-tree .tree-item .app-icon {
height: 25px; height: 25px;
width: 25px;
margin-right: 5px; margin-right: 5px;
display: inline-block;
vertical-align: middle;
} }
.edit .app-item-edit { .edit .app-item-edit {
flex: 1;
padding: 0 5px; padding: 0 5px;
} }

View File

@ -19,7 +19,7 @@
"_key": "item_1444480285022_2" "_key": "item_1444480285022_2"
}, },
{ {
"label": "Level 2-2", "label": "Level 2-3",
"icon": "chromium-browser", "icon": "chromium-browser",
"items": [ "items": [
{ {
@ -36,7 +36,15 @@
"label": "Atom", "label": "Atom",
"icon": "atom", "icon": "atom",
"exec": "/usr/share/atom/atom %U", "exec": "/usr/share/atom/atom %U",
"_key": "item_1444480288996_7" "_key": "item_1444480288996_7",
"items": []
},
{
"label": "Firefox Developer Edition Web Browser",
"icon": "firefox",
"exec": "firefox %u",
"_key": "item_1444761351301_2",
"selected": true
} }
], ],
"_key": "item_1444480285022_5" "_key": "item_1444480285022_5"

Binary file not shown.

View File

@ -2,7 +2,6 @@
<head> <head>
<title>Lanceur - Pitaya</title> <title>Lanceur - Pitaya</title>
<link rel="stylesheet" href="css/style.css" /> <link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
</head> </head>
<body> <body>
@ -20,12 +19,11 @@
if(isDev) { if(isDev) {
// Auto transform JSX // Auto transform JSX
require('node-jsx').install({extension: '.js'}); require('node-jsx').install({extension: '.js'});
// Launch application
require('./js/app.js');
} else {
require('./js-compiled/app.js');
} }
// Launch app
require('./'+(isDev ? 'src' : 'dist')+'/app.js');
</script> </script>
</body> </body>

View File

@ -1,7 +1,8 @@
var app = require('app'); // Module to control application life. var app = require('app'); // Module to control application life.
var BrowserWindow = require('browser-window'); // Module to create native browser window. var BrowserWindow = require('browser-window'); // Module to create native browser window.
var Menu = require('menu');
var isDev = process.env.NODE_ENV === 'development'; var isDev = process.env.NODE_ENV === 'development';
var constants = require('./'+(isDev ? 'js': 'js-compiled')+'/util/const'); var constants = require('./'+(isDev ? 'src': 'dist')+'/util/const');
var mainWindow = null; var mainWindow = null;
@ -13,7 +14,7 @@ app.on('window-all-closed', function() {
app.on('ready', function() { app.on('ready', function() {
// Create the browser window. // Create the browser window.
var electronScreen = require('screen'); var electronScreen = require('screen');
var size = electronScreen.getPrimaryDisplay().workAreaSize; var size = electronScreen.getPrimaryDisplay().size;
var asDesktop = process.env.PITAYA_AS_DESKTOP == 1; var asDesktop = process.env.PITAYA_AS_DESKTOP == 1;
@ -23,11 +24,12 @@ app.on('ready', function() {
frame: !asDesktop, frame: !asDesktop,
width: asDesktop ? size.width : undefined, width: asDesktop ? size.width : undefined,
height: asDesktop ? size.height : undefined, height: asDesktop ? size.height : undefined,
'auto-hide-menu-bar': true,
x: asDesktop ? 0 : undefined, x: asDesktop ? 0 : undefined,
y: asDesktop ? 0 : undefined, y: asDesktop ? 0 : undefined,
}); });
if(process.env.NODE_ENV === 'development') { if(isDev) {
mainWindow.openDevTools(); mainWindow.openDevTools();
} }

View File

@ -13,9 +13,9 @@
"scripts": { "scripts": {
"test": "./node_modules/.bin/nodeunit test", "test": "./node_modules/.bin/nodeunit test",
"start": "./node_modules/.bin/electron .", "start": "./node_modules/.bin/electron .",
"compile": "./node_modules/.bin/jsx -x js js js-compiled", "compile": "./node_modules/.bin/jsx -x js src dist",
"clean": "rm -rf js-compiled/* build/*", "clean": "rm -rf dist/* build/*",
"package": "./node_modules/.bin/electron-packager ./ pitaya --prune --ignore=js/ --platform=linux --arch=ia32 --version=0.33.6 --out=build --overwrite --app-version 0.0.0", "package": "./node_modules/.bin/electron-packager ./ pitaya --prune --ignore=res/ --platform=linux --arch=ia32,x64 --version=0.33.6 --out=build --overwrite --app-version 0.0.0",
"build": "npm run clean && npm run compile && npm run package" "build": "npm run clean && npm run compile && npm run package"
}, },
"dependencies": { "dependencies": {

View File

@ -41,8 +41,12 @@ module.exports = React.createClass({
var icon = this.state.icon; var icon = this.state.icon;
var style = {
backgroundImage: 'url('+icon+')'
};
return ( return (
<img src={icon} className="app-icon" /> <div className="app-icon" style={style}></div>
); );
}, },
@ -60,6 +64,13 @@ module.exports = React.createClass({
} }
return iconPath; return iconPath;
}) })
.then(function(iconPath) {
return Util.System.exists(iconPath)
.then(function(exists) {
return exists ? iconPath : DEFAULT_ICON;
})
;
})
.then(function(iconPath) { .then(function(iconPath) {
self.setState({ icon: iconPath }); self.setState({ icon: iconPath });
}) })

View File

@ -21,21 +21,40 @@ var EditView = React.createClass({
return ( return (
<div className="edit"> <div className="edit">
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css" />
<div className="menu-bar"> <div className="menu-bar">
<div className="left">
<ProfileMenu /> <ProfileMenu />
</div> </div>
<div className="main">
<div className="full-width">
<button className="btn btn-primary pull-right btn-sm" onClick={this.handleAddNewNode}>Ajouter un noeud</button>
</div>
</div>
<div className="right"></div>
</div>
<div className="workspace"> <div className="workspace">
<div className="left-menu"> <div className="left">
<div className="apps-menu">
<b className="title">Thème</b>
<IconThemeSelector onThemeSelected={this.handleThemeSelect} /> <IconThemeSelector onThemeSelected={this.handleThemeSelect} />
<b className="title">Applications</b>
<DesktopAppList <DesktopAppList
theme={this.props.theme} theme={this.props.theme}
desktopApps={this.props.desktopApps} desktopApps={this.props.desktopApps}
onItemDropped={this.handleItemDrop} /> onItemDropped={this.handleItemDrop} />
</div> </div>
</div>
<div className="main">
<b className="title">Arbre de profil</b>
<ProfileTree /> <ProfileTree />
</div>
<div className="right">
<b className="title">Édition</b>
<ItemForm item={this.props.selectedItem} onItemChange={this.handleItemChange} /> <ItemForm item={this.props.selectedItem} onItemChange={this.handleItemChange} />
</div> </div>
</div> </div>
</div>
); );
}, },
@ -58,6 +77,16 @@ var EditView = React.createClass({
handleItemChange: function(item, key, value) { handleItemChange: function(item, key, value) {
this.props.dispatch(actions.edit.updateProfileItem(item, key, value)); this.props.dispatch(actions.edit.updateProfileItem(item, key, value));
},
handleAddNewNode: function() {
var newItem = {
label: 'Nouveau noeud',
icon: '',
exec: '',
background: ''
};
this.props.dispatch(actions.edit.addProfileItem(newItem, this.props.profile));
} }
}); });

View File

@ -17,7 +17,8 @@ var ItemForm = React.createClass({
this.setState({ this.setState({
label: props.item.label, label: props.item.label,
icon: props.item.icon, icon: props.item.icon,
exec: props.item.exec exec: props.item.exec,
background: props.item.background
}); });
} }
@ -31,25 +32,27 @@ var ItemForm = React.createClass({
<div className="item-form"> <div className="item-form">
<form> <form>
<div className="form-group"> <div className="form-group">
<label>Label</label>
<input type="text" className="form-control" <input type="text" className="form-control"
placeholder="Label" placeholder="Label"
value={state.label} value={state.label}
onChange={this.handleChange.bind(this, 'label')} /> onChange={this.handleChange.bind(this, 'label')} />
</div> </div>
<div className="form-group"> <div className="form-group">
<label>Icon</label>
<input type="text" className="form-control" <input type="text" className="form-control"
placeholder="Icon" placeholder="Icône"
value={state.icon} value={state.icon}
onChange={this.handleChange.bind(this, 'icon')} /> onChange={this.handleChange.bind(this, 'icon')} />
</div> </div>
<div className="form-group"> <div className="form-group">
<label>Exec</label>
<input type="text" className="form-control" <input type="text" className="form-control"
placeholder="Exec" value={state.exec} placeholder="Chemin d'exécution" value={state.exec}
onChange={this.handleChange.bind(this, 'exec')} /> onChange={this.handleChange.bind(this, 'exec')} />
</div> </div>
<div className="form-group">
<input type="text" className="form-control"
placeholder="Fond d'écran" value={state.background}
onChange={this.handleChange.bind(this, 'background')} />
</div>
</form> </form>
</div> </div>
); );

View File

@ -10,8 +10,8 @@ var ProfileMenu = React.createClass({
return ( return (
<div className="profile-menu"> <div className="profile-menu">
<button className="btn btn-default" onClick={this.handleOpenClick}>Ouvrir</button> <button className="btn btn-default btn-sm" onClick={this.handleOpenClick}>Ouvrir</button>
<button className="btn btn-primary" onClick={this.handleSaveClick}>Enregistrer</button> <button className="btn btn-primary btn-sm" onClick={this.handleSaveClick}>Enregistrer</button>
</div> </div>
); );

View File

@ -1,5 +1,6 @@
var Util = require('../../util'); var Util = require('../../util');
var path = require('path'); var path = require('path');
var _ = require('lodash');
// Action types // Action types
var LOAD_DESKTOP_APPS = exports.LOAD_PROFILE = 'LOAD_DESKTOP_APPS'; var LOAD_DESKTOP_APPS = exports.LOAD_PROFILE = 'LOAD_DESKTOP_APPS';
@ -45,7 +46,14 @@ exports.saveProfile = function(destPath, profile) {
dispatch({ type: SAVE_PROFILE, profile: profile, path: destPath }); dispatch({ type: SAVE_PROFILE, profile: profile, path: destPath });
return Util.System.saveJSON(destPath, profile) var cleanedProfile = _.cloneDeep(profile);
Util.Tree.walk(cleanedProfile, function(item) {
delete item.selected;
delete item._key;
});
return Util.System.saveJSON(destPath, cleanedProfile)
.then(function() { .then(function() {
dispatch({ type: SAVE_PROFILE_SUCCESS, profile: profile, path: destPath }); dispatch({ type: SAVE_PROFILE_SUCCESS, profile: profile, path: destPath });
}) })

View File

@ -4,33 +4,40 @@ var tree = require('../../util/tree');
module.exports = function(oldProfile, action) { module.exports = function(oldProfile, action) {
var newProfile = oldProfile || { items: [] };
switch(action.type) { switch(action.type) {
case actions.common.LOAD_PROFILE_SUCCESS: case actions.common.LOAD_PROFILE_SUCCESS:
var newProfile = _.cloneDeep(action.profile); newProfile = _.cloneDeep(action.profile);
tree.walk(newProfile, ensureItemKey); break;
return newProfile;
case actions.edit.MOVE_PROFILE_ITEM: case actions.edit.MOVE_PROFILE_ITEM:
return moveProfileItem(oldProfile, action.movedItem, action.targetItem); newProfile = moveProfileItem(oldProfile, action.movedItem, action.targetItem);
break;
case actions.edit.REMOVE_PROFILE_ITEM: case actions.edit.REMOVE_PROFILE_ITEM:
return removeProfileItem(oldProfile, action.removedItem); newProfile = removeProfileItem(oldProfile, action.removedItem);
break;
case actions.edit.ADD_PROFILE_ITEM: case actions.edit.ADD_PROFILE_ITEM:
return addProfileItem(oldProfile, action.newItem, action.targetItem); newProfile = addProfileItem(oldProfile, action.newItem, action.targetItem);
break;
case actions.edit.UPDATE_PROFILE_ITEM: case actions.edit.UPDATE_PROFILE_ITEM:
return updateProfileItem(oldProfile, action.item, action.key, action.value); newProfile = updateProfileItem(oldProfile, action.item, action.key, action.value);
break;
case actions.edit.SELECT_PROFILE_ITEM: case actions.edit.SELECT_PROFILE_ITEM:
return selectProfileItem(oldProfile, action.item); newProfile = selectProfileItem(oldProfile, action.item);
break;
default:
return oldProfile || null;
} }
if(newProfile) tree.walk(newProfile, ensureItemKey);
return newProfile;
}; };
function selectProfileItem(oldProfile, item) { function selectProfileItem(oldProfile, item) {

View File

@ -2,6 +2,7 @@ var path = require('path');
var System = require('./system'); var System = require('./system');
var debug = require('./debug')('desktop-apps'); var debug = require('./debug')('desktop-apps');
var Cache = require('./cache'); var Cache = require('./cache');
var promises = require('./promises');
// Constants // Constants
var ICON_REALPATH_REGEX = /\..+$/; var ICON_REALPATH_REGEX = /\..+$/;
@ -19,11 +20,9 @@ exports.loadAllDesktopFiles = function(rootDirs) {
return exports.findAllDesktopFiles(rootDirs) return exports.findAllDesktopFiles(rootDirs)
.then(function(filePaths) { .then(function(filePaths) {
var promises = filePaths.map(function(path) { return promises.seq(filePaths, function(path) {
return exports.loadDesktopFile(path); return exports.loadDesktopFile(path);
}); })
return Promise.all(promises)
.then(function(contents) { .then(function(contents) {
return contents.map(function(content, i) { return contents.map(function(content, i) {
return { content: content, path: filePaths[i] }; return { content: content, path: filePaths[i] };
@ -48,11 +47,9 @@ exports.findAllDesktopFiles = function(baseDirs) {
baseDirs = [baseDirs]; baseDirs = [baseDirs];
} }
var promises = baseDirs.map(function(baseDir) { return promises.seq(baseDirs, function(baseDir) {
return System.findFiles('**/*.desktop', {cwd: baseDir, realpath: true}); return System.findFiles('**/*.desktop', {cwd: baseDir, realpath: true});
}); })
return Promise.all(promises)
.then(function(apps) { .then(function(apps) {
return uniq(flatten(apps)); return uniq(flatten(apps));
}) })
@ -104,10 +101,9 @@ exports.findIcon = function(iconName, themeName, size, themeIgnore) {
return exports.findIconThemes() return exports.findIconThemes()
.then(function(themes) { .then(function(themes) {
themeIgnore = themeIgnore || []; themeIgnore = themeIgnore || [];
var promises = themes.map(function(theme) { return promises.seq(themes, function(theme) {
return exports.findIcon(iconName, theme, size, themeIgnore); return exports.findIcon(iconName, theme, size, themeIgnore);
}); })
return Promise.all(promises)
.then(exports._selectBestIcon) .then(exports._selectBestIcon)
; ;
}) })
@ -160,11 +156,9 @@ exports.findParentsThemeIcon = function(iconName, themeName, size, themeIgnore)
debug('Found parents %j', parents); debug('Found parents %j', parents);
var promises = parents.map(function(themeName) { return promises.seq(parents, function(themeName) {
return exports.findIcon(iconName, themeName, size, themeIgnore); return exports.findIcon(iconName, themeName, size, themeIgnore);
}); })
return Promise.all(promises)
.then(exports._selectBestIcon) .then(exports._selectBestIcon)
; ;
@ -278,7 +272,6 @@ exports._selectBestIcon = function(iconPaths) {
return iconSelection.scalable || iconSelection.bitmap; return iconSelection.scalable || iconSelection.bitmap;
}; };
// Array helpers // Array helpers
function clean(arr) { function clean(arr) {

View File

@ -2,3 +2,6 @@ exports.System = require('./system');
exports.DesktopApps = require('./desktop-apps'); exports.DesktopApps = require('./desktop-apps');
exports.Cache = require('./cache'); exports.Cache = require('./cache');
exports.Debug = require('./debug'); exports.Debug = require('./debug');
exports.Tree = require('./tree');
exports.Const = require('./const');
exports.Promises = require('./promises');

31
src/util/promises.js Normal file
View File

@ -0,0 +1,31 @@
exports.seq = function(items, generator) {
var results = [];
var p = Promise.resolve();
for(var i = 0, len = items.length; i < len; ++i) {
p = p.then(generateNextHandler(items[i], i === 0))
}
return p.then(function(lastResult) {
results.push(lastResult);
return results;
});
// Internal helper
function generateNextHandler(item, ignoreResult) {
return function(prevResult) {
if(!ignoreResult) results.push(prevResult);
return generator(item, prevResult);
};
}
};
exports.delay = function(delay) {
return new Promise(function(resolve) {
setTimeout(resolve, delay);
});
};