diff --git a/.gitignore b/.gitignore index 0c88cd5..7cce162 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ dist build nwjs my-profile.json +js-compiled diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index fccf38b..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,118 +0,0 @@ -/* jshint node: true */ -var _ = require('lodash'); -var path = require('path'); - -module.exports = function(grunt) { - - var NW_VERSION = '0.12.3'; - var BUILD_DIR = 'build'; - var BUILD_TARGETS = { - linux_ia32: true, - linux_x64: true, - win: false, - osx: false - }; - var PKG = grunt.file.readJSON('package.json'); - var PKG_OVERWRITE = { - window: { - toolbar: false, - kiosk: true - } - }; - - // Create build tasks options - var buildOptions = _.merge({ - runtimeVersion: NW_VERSION - }, BUILD_TARGETS); - - // Define copy:build tasks files - var appFiles = []; - - _.forEach(BUILD_TARGETS, function(isEnabled, target) { - - if(!isEnabled) return; - - var arch = 'ia32'; - var platform = target; - if(platform.indexOf('linux') !== -1) { - arch = platform.split('_')[1]; - platform = 'linux'; - } - var dirName = PKG.name + '-' + PKG.version + '-' + platform + '-' + arch; - var destPath = path.join(BUILD_DIR, dirName + '/'); - - // Retreive NPM dependencies - var npmDeps = _.keys(PKG.dependencies).map(function(moduleName) { - return path.join('node_modules', moduleName, '**'); - }); - appFiles.push({ src: npmDeps, dest: destPath }); - - // Add main files, licence, & config - appFiles.push({ - src: [ - 'index.html', - 'package.json', - 'default-profile.json', - 'LICENCE', - 'css/**', - 'js/**', - 'img/**' - ], - dest: destPath - }); - - }); - - // Configure tasks - grunt.initConfig({ - - pkg: PKG, - - download: { - options: { - runtimeVersion: NW_VERSION - } - }, - - run: { - options: { - nwArgs: ['.'].concat(process.argv.slice(3)), - runtimeVersion: NW_VERSION - } - }, - - build: { - options: buildOptions - }, - - clean: { - build: [BUILD_DIR] - }, - - copy: { - build: { - files: appFiles, - options: { - noProcess: ['**','!package.json'], - process: function() { - var pkg = _.merge(PKG, PKG_OVERWRITE); - return JSON.stringify(pkg, null, 2); - } - } - } - } - - }); - - grunt.registerTask('pitaya:run', ['download', 'run']); - grunt.registerTask( - 'pitaya:build', - ['download', 'build', 'copy:build'] - ); - grunt.registerTask('default', ['pitaya:run']); - - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-copy'); - grunt.loadNpmTasks('grunt-nw'); - -}; diff --git a/README.md b/README.md index 7b28bee..a34adf1 100644 --- a/README.md +++ b/README.md @@ -9,33 +9,25 @@ Lanceur d'application pour GNU/Linux - [NodeJS](https://nodejs.org/) - Dernière version stable, testé sur la 0.12.* - [NPM](https://www.npmjs.com/) - Normalement automatiquement installé avec NodeJS. -### Initialisation du projet +### Initialisation du projet & lancement de Pitaya ``` git clone https://forge.cadoles.com/wpetit/pitaya.git cd pitaya git checkout develop npm install -DEBUG=pitaya* npm start +DEBUG=pitaya* NODE_ENV=development npm start ``` -## Options +## Variables d'environnement -``` ---profile= Chemin vers le fichier de profil à charger dans l'application. ---edit Ouvrir l'application en mode édition. -``` +Vous pouvez configurer le comportement de Pitaya en passant des variables d'environnement: -### Passer des options en développement - -``` -npm start -- [options...] -``` - -Exemple: -``` -npm start -- --profile=my-profile.json -``` +| Variable | Description | Valeurs possibles | Valeur par défaut | +|-------------------|------------------------------------|-------------------|------------------------| +| PITAYA_MODE | Mode d'exécution de Pitaya | launcher, edit | launcher | +| PITAYA_PROFILE | Chemin du fichier profil à charger | -- | ./default-profile.json | +| PITAYA_AS_DESKTOP | Afficher Pitaya en mode "Bureau" | 1, 0 | 0 | ## Comment construire l'application depuis les sources @@ -43,7 +35,7 @@ npm start -- --profile=my-profile.json npm run build ``` -Un dossier `pitaya---` sera créé dans le répertoire `./build`. Celui ci contient tous les fichiers nécessaires à l'application. +Un dossier `pitaya--` sera créé dans le répertoire `./build`. Celui ci contient tous les fichiers nécessaires à l'application. ## Comment contribuer diff --git a/css/style.css b/css/style.css index 63e9fea..a2cc2c4 100644 --- a/css/style.css +++ b/css/style.css @@ -27,21 +27,42 @@ html, body { width: 100%; height: 100%; flex-direction: column; - background: url('../img/background.png') no-repeat; + background: url('../img/background.png'); + background-repeat: no-repeat; + background-size: cover; + background-position: center center; + transition: background-image 250ms ease-in-out; +} + +.launcher .main { + flex-direction: row; + display: flex; + flex-grow: 1; +} + +.launcher .nav { + justify-content: center; + align-items: center; + display: flex; + width: 50px; +} + +.launcher .nav a.goback { + text-decoration: none; + color: white; + font-size: 60px; + text-shadow: 1px 1px #444; +} + +.launcher .nav a.goback:hover { + -webkit-animation: 500ms pulse-large infinite; } .launcher .category-header { - padding: 40px 50px 0; + padding: 25px 40px 0; font-size: 50px; -} - -.launcher .category-header a.goback { - text-decoration: none; - color: white; -} - -.launcher .category-header a.goback:hover { - -webkit-animation: 500ms pulse-large infinite; + color: #fff; + text-shadow: 1px 1px #444; } .launcher .category-header > .category-label { @@ -65,6 +86,10 @@ html, body { flex-grow: 1; } +.launcher .nav ~ ul.apps-list { + margin-left: -50px; +} + .launcher li.app-item { margin: 5px; border-radius: 5px; @@ -221,7 +246,7 @@ html, body { @-webkit-keyframes pulse-large { 0% { transform: scale(1); } - 50% { transform: scale(1.5); } + 50% { transform: scale(1.3); } 100% { transform: scale(1); } } diff --git a/default-profile.json b/default-profile.json index 76d3279..f9779d0 100644 --- a/default-profile.json +++ b/default-profile.json @@ -3,6 +3,7 @@ { "label": "Level 1", "icon": "chromium-browser", + "background": "./img/background2.jpg", "items": [ { "label": "Level 2-1", @@ -11,9 +12,11 @@ { "label": "Chromium Browser 1", "icon": "chromium-browser", - "exec": "/usr/bin/chromium-browser" + "exec": "/usr/bin/chromium-browser", + "_key": "item_1444480285022_3" } - ] + ], + "_key": "item_1444480285022_2" }, { "label": "Level 2-2", @@ -26,13 +29,24 @@ { "label": "Chromium Browser 2", "icon": "chromium-browser", - "exec": "/usr/bin/chromium-browser" + "exec": "/usr/bin/chromium-browser", + "_key": "item_1444480285022_6" + }, + { + "label": "Atom", + "icon": "atom", + "exec": "/usr/share/atom/atom %U", + "_key": "item_1444480288996_7" } - ] + ], + "_key": "item_1444480285022_5" } - ] + ], + "_key": "item_1444480285022_4" } - ] + ], + "_key": "item_1444480285022_1" } - ] + ], + "_key": "item_1444480285021_0" } diff --git a/img/background2.jpg b/img/background2.jpg new file mode 100644 index 0000000..c609f3f Binary files /dev/null and b/img/background2.jpg differ diff --git a/index.html b/index.html index af268f4..32aad08 100644 --- a/index.html +++ b/index.html @@ -15,11 +15,16 @@ global.document = global.window.document; global.navigator = global.window.navigator; - // Auto transform JSX - require('node-jsx').install(); + var isDev = process.env.NODE_ENV === 'development'; - // Launch application - require('./js/app.jsx'); + if(isDev) { + // Auto transform JSX + require('node-jsx').install({extension: '.js'}); + // Launch application + require('./js/app.js'); + } else { + require('./js-compiled/app.js'); + } diff --git a/js/app.jsx b/js/app.js similarity index 85% rename from js/app.jsx rename to js/app.js index fb7e2a8..00103b2 100644 --- a/js/app.jsx +++ b/js/app.js @@ -1,6 +1,6 @@ var React = require('react'); -var LauncherView = require('./components/launcher/launcher-view.jsx'); -var EditView = require('./components/edit/edit-view.jsx'); +var LauncherView = require('./components/launcher/launcher-view.js'); +var EditView = require('./components/edit/edit-view.js'); var Provider = require('react-redux').Provider; var connect = require('react-redux').connect; var store = require('./store'); @@ -10,7 +10,7 @@ var App = React.createClass({ render: function() { - var editMode = this.props.processOpts.edit || false; + var editMode = process.env.PITAYA_MODE === 'edit'; var view = editMode ? : ; diff --git a/js/components/common/app-icon.jsx b/js/components/common/app-icon.js similarity index 100% rename from js/components/common/app-icon.jsx rename to js/components/common/app-icon.js diff --git a/js/components/edit/desktop-app-item.jsx b/js/components/edit/desktop-app-item.js similarity index 96% rename from js/components/edit/desktop-app-item.jsx rename to js/components/edit/desktop-app-item.js index 9436cfd..488ed7f 100644 --- a/js/components/edit/desktop-app-item.jsx +++ b/js/components/edit/desktop-app-item.js @@ -1,6 +1,6 @@ var React = require('react'); var Util = require('../../util'); -var AppIcon = require('../common/app-icon.jsx'); +var AppIcon = require('../common/app-icon.js'); var DragSource = require('react-dnd').DragSource; var DesktopAppItem = React.createClass({ diff --git a/js/components/edit/desktop-app-list.jsx b/js/components/edit/desktop-app-list.js similarity index 93% rename from js/components/edit/desktop-app-list.jsx rename to js/components/edit/desktop-app-list.js index f8c2ce1..e080b33 100644 --- a/js/components/edit/desktop-app-list.jsx +++ b/js/components/edit/desktop-app-list.js @@ -1,6 +1,6 @@ var React = require('react'); var Util = require('../../util'); -var DesktopAppItem = require('./desktop-app-item.jsx'); +var DesktopAppItem = require('./desktop-app-item.js'); var path = require('path'); var debug = require('../../util/debug')('pitaya:desktop-app-list'); diff --git a/js/components/edit/edit-view.jsx b/js/components/edit/edit-view.js similarity index 87% rename from js/components/edit/edit-view.jsx rename to js/components/edit/edit-view.js index 50a8952..eb29ffc 100644 --- a/js/components/edit/edit-view.jsx +++ b/js/components/edit/edit-view.js @@ -1,10 +1,10 @@ var React = require('react'); var connect = require('react-redux').connect; -var ProfileTree = require('./profile-tree.jsx'); -var DesktopAppList = require('./desktop-app-list.jsx'); -var ItemForm = require('./item-form.jsx'); -var IconThemeSelector = require('./icon-theme-selector.jsx'); -var ProfileMenu = require('./profile-menu.jsx'); +var ProfileTree = require('./profile-tree.js'); +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'); diff --git a/js/components/edit/icon-theme-selector.jsx b/js/components/edit/icon-theme-selector.js similarity index 100% rename from js/components/edit/icon-theme-selector.jsx rename to js/components/edit/icon-theme-selector.js diff --git a/js/components/edit/item-form.jsx b/js/components/edit/item-form.js similarity index 100% rename from js/components/edit/item-form.jsx rename to js/components/edit/item-form.js diff --git a/js/components/edit/profile-menu.js b/js/components/edit/profile-menu.js new file mode 100644 index 0000000..e98e15a --- /dev/null +++ b/js/components/edit/profile-menu.js @@ -0,0 +1,85 @@ +/* jhsint node:true, jsx: true */ +var React = require('react'); +var connect = require('react-redux').connect; +var actions = require('../../store/actions'); +var dialog = require('remote').require('dialog'); + +var ProfileMenu = React.createClass({ + + render: function() { + + return ( +
+ + +
+ ); + + }, + + handleOpenClick: function() { + var dispatch = this.props.dispatch; + this.showOpenProfileDialog() + .then(function(profilePath) { + if(profilePath) dispatch(actions.common.loadProfile(profilePath)); + }) + ; + }, + + handleSaveClick: function() { + + var dispatch = this.props.dispatch; + var profile = this.props.profile; + var profilePath = this.props.profilePath; + + this.showSaveProfileDialog(profilePath) + .then(function(profilePath) { + if(profilePath) dispatch(actions.edit.saveProfile(profilePath, profile)); + }); + + }, + + showOpenProfileDialog: function() { + + return new Promise(function(resolve) { + dialog.showOpenDialog( + { + title: 'Éditer un profil', + filters: [ {name: 'Profils Pitaya', extensions: ['json'] } ], + properties: ['openFile'] + }, + function(files) { + return resolve(files ? files[0] : null); + } + ) + }); + + }, + + showSaveProfileDialog: function(defaultPath) { + + return new Promise(function(resolve) { + dialog.showSaveDialog( + { + defaultPath: defaultPath, + title: 'Enregistrer un profil', + filters: [ {name: 'Profils Pitaya', extensions: ['json'] } ] + }, + function(file) { + return resolve(file); + } + ) + }); + + } + +}); + +function select(state) { + return { + profile: state.profile, + profilePath: state.profilePath + }; +} + +module.exports = connect(select)(ProfileMenu); diff --git a/js/components/edit/profile-menu.jsx b/js/components/edit/profile-menu.jsx deleted file mode 100644 index e5a0586..0000000 --- a/js/components/edit/profile-menu.jsx +++ /dev/null @@ -1,79 +0,0 @@ -var React = require('react'); -var connect = require('react-redux').connect; -var actions = require('../../store/actions'); - -var ProfileMenu = React.createClass({ - - render: function() { - - return ( -
- - - -
- ); - - }, - - handleOpenClick: function() { - var dispatch = this.props.dispatch; - this.showFileDialog() - .then(function(profilePath) { - dispatch(actions.common.loadProfile(profilePath)); - }) - ; - }, - - handleSaveClick: function() { - - var dispatch = this.props.dispatch; - var profile = this.props.profile; - var profilePath = this.props.profilePath; - - var promise = profilePath ? Promise.resolve(profilePath) : this.showFileDialog(true); - - promise.then(function(profilePath) { - dispatch(actions.edit.saveProfile(profilePath, profile)); - }); - - }, - - showFileDialog: function(saveAs) { - - var fileInput = this.refs.fileInput.getDOMNode(); - - // Toggle 'save as' feature - if(saveAs) { - fileInput.nwsaveas = true; - } else { - fileInput.removeAttribute('nwsaveas'); - } - - return new Promise(function(resolve, reject) { - - fileInput.addEventListener('change', handleChange, false); - fileInput.click(); - - function handleChange(evt) { - fileInput.removeEventListener('change', handleChange); - var value = this.value; - this.value = null; - resolve(value); - } - - }); - - - } - -}); - -function select(state) { - return { - profile: state.profile, - profilePath: state.profilePath - }; -} - -module.exports = connect(select)(ProfileMenu); diff --git a/js/components/edit/profile-tree.jsx b/js/components/edit/profile-tree.js similarity index 87% rename from js/components/edit/profile-tree.jsx rename to js/components/edit/profile-tree.js index 3e870b7..98f867e 100644 --- a/js/components/edit/profile-tree.jsx +++ b/js/components/edit/profile-tree.js @@ -1,7 +1,7 @@ var React = require('react'); var connect = require('react-redux').connect; var actions = require('../../store/actions'); -var TreeItem = require('./tree-item.jsx'); +var TreeItem = require('./tree-item.js'); var TreeNode = React.createClass({ @@ -17,6 +17,7 @@ var TreeNode = React.createClass({ selectedItem={this.props.selectedItem} onItemClicked={this.props.onItemClicked} onItemMoved={this.props.onItemMoved} + onItemRemoved={this.props.onItemRemoved} theme={this.props.theme} /> ); @@ -66,6 +67,7 @@ var ProfileTree = React.createClass({ selectedItem={this.props.selectedItem} onItemClicked={this.onItemSelected} onItemMoved={this.onItemMoved} + onItemRemoved={this.onItemRemoved} theme={this.props.theme} /> ); }, @@ -76,6 +78,10 @@ var ProfileTree = React.createClass({ onItemSelected: function(selectedItem) { this.props.dispatch(actions.edit.selectProfileItem(selectedItem)); + }, + + onItemRemoved: function(selectedItem) { + this.props.dispatch(actions.edit.removeProfileItem(selectedItem)); } }); diff --git a/js/components/edit/tree-item.jsx b/js/components/edit/tree-item.js similarity index 87% rename from js/components/edit/tree-item.jsx rename to js/components/edit/tree-item.js index 0be6d6f..4ac8af6 100644 --- a/js/components/edit/tree-item.jsx +++ b/js/components/edit/tree-item.js @@ -1,6 +1,6 @@ var React = require('react/addons'); var classNames = require('classnames'); -var AppIcon = require('../common/app-icon.jsx'); +var AppIcon = require('../common/app-icon.js'); var DragSource = require('react-dnd').DragSource; var DropTarget = require('react-dnd').DropTarget; var _ = require('lodash'); @@ -30,6 +30,9 @@ var TreeItem = React.createClass({
{appIcon} {data.label} +
)); @@ -38,6 +41,11 @@ var TreeItem = React.createClass({ handleClick: function(evt) { evt.preventDefault(); this.props.onItemClicked(this.props.data); + }, + + handleRemoveClick: function(evt) { + evt.preventDefault(); + this.props.onItemRemoved(this.props.data); } }); diff --git a/js/components/launcher/app-item.jsx b/js/components/launcher/app-item.js similarity index 93% rename from js/components/launcher/app-item.jsx rename to js/components/launcher/app-item.js index 8ffb14a..0734d03 100644 --- a/js/components/launcher/app-item.jsx +++ b/js/components/launcher/app-item.js @@ -1,5 +1,5 @@ var React = require('react'); -var AppIcon = require('../common/app-icon.jsx'); +var AppIcon = require('../common/app-icon.js'); module.exports = React.createClass({ diff --git a/js/components/launcher/app-list.jsx b/js/components/launcher/app-list.js similarity index 96% rename from js/components/launcher/app-list.jsx rename to js/components/launcher/app-list.js index 4c13fed..e103083 100644 --- a/js/components/launcher/app-list.jsx +++ b/js/components/launcher/app-list.js @@ -1,5 +1,5 @@ var React = require('react'); -var AppItem = require('./app-item.jsx'); +var AppItem = require('./app-item.js'); module.exports = React.createClass({ diff --git a/js/components/launcher/category-header.js b/js/components/launcher/category-header.js new file mode 100644 index 0000000..3b602a3 --- /dev/null +++ b/js/components/launcher/category-header.js @@ -0,0 +1,19 @@ +var React = require('react'); + +module.exports = React.createClass({ + + propTypes: { + item: React.PropTypes.object.isRequired, + }, + + render: function() { + + return ( +
+ {this.props.item.label} +
+ ); + + } + +}); diff --git a/js/components/launcher/launcher-view.jsx b/js/components/launcher/launcher-view.js similarity index 80% rename from js/components/launcher/launcher-view.jsx rename to js/components/launcher/launcher-view.js index cedecc9..5b42d4f 100644 --- a/js/components/launcher/launcher-view.jsx +++ b/js/components/launcher/launcher-view.js @@ -1,12 +1,14 @@ var React = require('react'); -var CategoryHeader = require('./category-header.jsx'); -var AppList = require('./app-list.jsx'); +var CategoryHeader = require('./category-header.js'); +var AppList = require('./app-list.js'); +var Nav = require('./nav.js'); var AnimateMixin = require('../mixins/animate'); var actions = require('../../store/actions'); var connect = require('react-redux').connect; var debug = require('../../util/debug')('launcher-view'); +var path = require('path'); -var DEFAULT_PROFILE = './default-profile.json'; +var DEFAULT_PROFILE = path.join(__dirname, '..', '..', '..', 'default-profile.json'); var LauncherView = React.createClass({ @@ -20,7 +22,7 @@ var LauncherView = React.createClass({ }, componentDidMount: function() { - var profilePath = this.props.processOpts.profile || DEFAULT_PROFILE; + var profilePath = process.env.PITAYA_PROFILE || DEFAULT_PROFILE; this.props.dispatch(actions.common.loadProfile(profilePath)); }, @@ -38,19 +40,34 @@ var LauncherView = React.createClass({ var header = currentItemPath !== '' ? ( ) : + null + ; + + var nav = currentItemPath !== '' ? + (