Browse Source

Version React iso-fonctionnelle

feature/profil_enr
William Petit 4 years ago
parent
commit
7fc7f9d10e
12 changed files with 223 additions and 354 deletions
  1. 0
    5
      css/style.css
  2. 1
    31
      index.html
  3. 0
    242
      js/app.js
  4. 113
    3
      js/app.jsx
  5. 28
    0
      js/components/app-item.jsx
  6. 22
    1
      js/components/app-list.jsx
  7. 17
    6
      js/components/category-header.jsx
  8. 0
    53
      js/dom.js
  9. 10
    11
      js/mixins/animate.js
  10. 1
    0
      js/util/index.js
  11. 31
    0
      js/util/system.js
  12. 0
    2
      package.json

+ 0
- 5
css/style.css View File

@@ -28,11 +28,6 @@ html, body {
28 28
 .launcher .category-header {
29 29
   padding: 40px 50px 0;
30 30
   font-size: 50px;
31
-  display: none;
32
-}
33
-
34
-.launcher .category-header.visible {
35
-  display: block;
36 31
 }
37 32
 
38 33
 .launcher .category-header a.goback {

+ 1
- 31
index.html View File

@@ -7,37 +7,7 @@
7 7
 
8 8
     <!-- Application root element -->
9 9
     <div id="pitaya"></div>
10
-
11
-    <!-- Templates -->
12
-
13
-    <script id="launcher-view-tpl" type="text/x-template">
14
-      <div class="launcher">
15
-        {{#unless isRoot}}
16
-          <div class="category-header">
17
-            <a href="#" class="goback" data-item-path="{{currentItemPath}}">&#9668;</a>
18
-            <span class="category-label">{{currentItem.label}}</span>
19
-          </div>
20
-        {{/unless}}
21
-        {{> itemListTpl}}
22
-      </div>
23
-    </script>
24
-
25
-    <script id="items-list-tpl" type="text/x-template">
26
-      <ul class="apps-list">
27
-        {{#each currentItem.items}}
28
-          {{> itemTpl currentItemPath=../currentItemPath}}
29
-        {{/each}}
30
-      </ul>
31
-    </script>
32
-
33
-    <script id="item-tpl" type="text/x-template">
34
-      <li class="app-item"
35
-        data-item-path="{{currentItemPath}}.{{@index}}">
36
-        <img class="app-icon" src="{{icon}}" />
37
-        <span class="app-label">{{label}}</span>
38
-      </li>
39
-    </script>
40
-
10
+    
41 11
     <!-- Scripts -->
42 12
     <script type="text/javascript">
43 13
       // React context detection workaround

+ 0
- 242
js/app.js View File

@@ -1,242 +0,0 @@
1
-(function(Pitaya, window) {
2
-
3
-  "use strict";
4
-
5
-  // Load dependencies
6
-  var path = require('path');
7
-  var fs = require('fs');
8
-  var Handlebars = require('handlebars');
9
-  var cp = require("child_process");
10
-  var gui = require('nw.gui');
11
-  var minimist = require('minimist');
12
-
13
-  // Load templates...
14
-  var launcherViewTpl = Handlebars.compile(Pitaya.DOM.select('#launcher-view-tpl').innerHTML);
15
-
16
-  // ... and partials
17
-  Handlebars.registerPartial('itemListTpl', Pitaya.DOM.select('#items-list-tpl').innerHTML);
18
-  Handlebars.registerPartial('itemTpl', Pitaya.DOM.select('#item-tpl').innerHTML);
19
-
20
-  // Internal constants
21
-  var DEFAULT_PROFILE = './default-profile.json';
22
-
23
-  /**
24
-   * Start the app
25
-   *
26
-   * @param rootEl The application root element selector
27
-   * @return Pitaya
28
-   */
29
-  Pitaya.start = function(rootEl) {
30
-
31
-    Pitaya._opts = minimist(gui.App.argv);
32
-    Pitaya._rootEl = Pitaya.DOM.select(rootEl);
33
-    Pitaya._initListeners();
34
-
35
-    var profilePath = Pitaya._opts.profile || DEFAULT_PROFILE;
36
-
37
-    return Pitaya.loadProfile(profilePath)
38
-      .then(function() {
39
-        return Pitaya;
40
-      })
41
-      .catch(Pitaya._onError)
42
-    ;
43
-
44
-  };
45
-
46
-  /**
47
-   * Load a profile file and render the application
48
-   *
49
-   * @param profilePath The path of the profile file
50
-   * @return Promise
51
-   */
52
-  Pitaya.loadProfile = function(profilePath) {
53
-    return Pitaya._loadJSONFile(profilePath)
54
-      .then(function(profile) {
55
-        Pitaya._profile = profile;
56
-        Pitaya.renderLauncherView();
57
-        return profile;
58
-      })
59
-    ;
60
-  };
61
-
62
-  /**
63
-   * Update the application view
64
-   *
65
-   * @return Pitaya
66
-   */
67
-  Pitaya.renderLauncherView = function(currentItemPath) {
68
-
69
-    currentItemPath = Pitaya._normalizeItemPath(currentItemPath);
70
-    var rootEl = Pitaya._rootEl;
71
-    var currentItem = Pitaya._getItemByPath(currentItemPath);
72
-
73
-    var data = {
74
-      currentItemPath: currentItemPath.join('.'),
75
-      currentItem: currentItem,
76
-      isRoot: currentItemPath.length === 0
77
-    };
78
-
79
-    rootEl.innerHTML = launcherViewTpl(data);
80
-
81
-  };
82
-
83
-
84
-  /**
85
-   * Initialize DOM event listeners
86
-   * @private
87
-   */
88
-  Pitaya._initListeners = function() {
89
-    var rootEl = Pitaya._rootEl;
90
-    rootEl.addEventListener('click', Pitaya._onItemClick);
91
-    rootEl.addEventListener('click', Pitaya._onGoBackClick);
92
-  };
93
-
94
-  /**
95
-   * App item click handler
96
-   * @private
97
-   */
98
-  Pitaya._onItemClick = function(evt) {
99
-
100
-    var appItemEl = evt.srcElement.matches( '.app-item') ? evt.srcElement :
101
-      Pitaya.DOM.getClosestAncestor(evt.srcElement, '.app-item')
102
-    ;
103
-
104
-    if( !appItemEl ) return;
105
-
106
-    var itemPath = appItemEl.dataset.itemPath;
107
-    var item = Pitaya._getItemByPath(itemPath);
108
-
109
-    if(!item) return;
110
-
111
-    if('items' in item) {
112
-      var rootEl = Pitaya._rootEl;
113
-      Pitaya.Anim.play(rootEl, 'slide-out-left 250ms ease-in-out')
114
-        .then(function() {
115
-          Pitaya.renderLauncherView(itemPath);
116
-          return Pitaya.Anim.play(rootEl, 'slide-in-right 250ms ease-in-out');
117
-        })
118
-      ;
119
-    }
120
-
121
-    if(item.exec) {
122
-
123
-      console.info('Launching application "'+item.exec+'"...');
124
-      appItemEl.classList.add('pulse');
125
-
126
-      Pitaya._runApp(item.exec)
127
-        .then(function() {
128
-          appItemEl.classList.remove('pulse');
129
-        })
130
-        .catch(function(err) {
131
-          Pitaya._onError(err);
132
-          appItemEl.classList.remove('pulse');
133
-        })
134
-      ;
135
-
136
-    }
137
-
138
-  };
139
-
140
-  /**
141
-   * GoBack button click handler
142
-   * @private
143
-   */
144
-  Pitaya._onGoBackClick = function(evt) {
145
-
146
-    var goBackEl = evt.srcElement.matches( '.goback') ? evt.srcElement :
147
-      Pitaya.DOM.getClosestAncestor(evt.srcElement, '.goback')
148
-    ;
149
-
150
-    if(!goBackEl) return;
151
-
152
-    var currentItemPath = goBackEl.dataset.itemPath;
153
-    var parentItemPath = Pitaya._normalizeItemPath(currentItemPath);
154
-
155
-    parentItemPath.pop();
156
-
157
-    var rootEl = Pitaya._rootEl;
158
-    Pitaya.Anim.play(rootEl, 'slide-out-right 250ms ease-in-out')
159
-      .then(function() {
160
-        Pitaya.renderLauncherView(parentItemPath);
161
-        return Pitaya.Anim.play(rootEl, 'slide-in-left 250ms ease-in-out');
162
-      })
163
-    ;
164
-
165
-  };
166
-
167
-  Pitaya._normalizeItemPath = function(itemPath) {
168
-
169
-    if( Array.isArray(itemPath) ) return itemPath;
170
-
171
-    if((typeof itemPath === 'string' && itemPath.length === 0) || !itemPath) return [];
172
-
173
-    return itemPath.split('.').reduce(function(arr, index) {
174
-      if(index !== '') {
175
-        arr.push(+index);
176
-      }
177
-      return arr;
178
-    }, []);
179
-
180
-  };
181
-
182
-  Pitaya._getItemByPath = function(itemPath, rootItem) {
183
-
184
-    rootItem = rootItem || Pitaya._profile;
185
-    itemPath = Pitaya._normalizeItemPath(itemPath);
186
-
187
-    var itemIndex = itemPath[0];
188
-
189
-    if(itemIndex === undefined) {
190
-      return rootItem;
191
-    }
192
-
193
-    if(!('items' in rootItem)) {
194
-      return undefined;
195
-    }
196
-
197
-    var subItem = rootItem.items[itemIndex];
198
-
199
-    if(itemPath.length === 0) {
200
-      return subItem;
201
-    }
202
-
203
-    return Pitaya._getItemByPath(itemPath.slice(1), subItem);
204
-
205
-  };
206
-
207
-  Pitaya._runApp = function(execPath) {
208
-    return new Promise(function(resolve, reject) {
209
-      cp.exec(execPath, function(err) {
210
-        if(err) return reject(err);
211
-        return resolve();
212
-      });
213
-    });
214
-  };
215
-
216
-  /**
217
-   * Load a JSON file
218
-   *
219
-   * @private
220
-   * @param filePath The path of the json file
221
-   * @return Promise
222
-   */
223
-  Pitaya._loadJSONFile = function(filePath) {
224
-    return new Promise(function(resolve, reject) {
225
-      fs.readFile(filePath, 'utf8', function(err, fileContent) {
226
-        if(err) return reject(err);
227
-        try {
228
-          var json = JSON.parse(fileContent);
229
-          return resolve(json);
230
-        } catch(err) {
231
-          return reject(err);
232
-        }
233
-      });
234
-    });
235
-  };
236
-
237
-
238
-  Pitaya._onError = function(err) {
239
-    console.error(err.stack ? err.stack : err);
240
-  };
241
-
242
-}(window.Pitaya = window.Pitaya || {}, window));

+ 113
- 3
js/app.jsx View File

@@ -1,26 +1,136 @@
1 1
 var React = require('react');
2
+var minimist = require('minimist');
3
+var gui = global.window.require('nw.gui');
4
+var Util = require('./util');
2 5
 var CategoryHeader = require('./components/category-header.jsx');
3 6
 var AppList = require('./components/app-list.jsx');
7
+var AnimateMixin = require('./mixins/animate');
8
+
9
+// Internal constants
10
+var DEFAULT_PROFILE = './default-profile.json';
11
+var PROCESS_OPTS = minimist(gui.App.argv);
4 12
 
5 13
 var App = React.createClass({
6 14
 
15
+  mixins: [AnimateMixin],
16
+
7 17
   getInitialState: function() {
8 18
     return {
19
+      currentItemPath: '',
9 20
       currentItem: null
10 21
     };
11 22
   },
12 23
 
13 24
   componentDidMount: function() {
14
-
25
+    Util.System.loadJSONFile(PROCESS_OPTS.profile || DEFAULT_PROFILE)
26
+      .then(function(profile) {
27
+        this.setState({ profile: profile, currentItem: profile, currentItemPath: '' });
28
+      }.bind(this))
29
+    ;
15 30
   },
16 31
 
17 32
   render: function() {
33
+
34
+    var currentItem = this.state.currentItem;
35
+    var items = currentItem ? currentItem.items : [];
36
+    var currentItemPath = this.state.currentItemPath;
37
+
38
+    var header = currentItemPath !== '' ?
39
+      ( <CategoryHeader
40
+          onBackClick={this.onBackClick}
41
+          item={currentItem}
42
+          itemPath={currentItemPath} /> ) :
43
+      null
44
+    ;
45
+
18 46
     return (
19 47
       <div className="launcher">
20
-        <CategoryHeader currentItem={this.state.currentItem} />
21
-        <AppList />
48
+        {header}
49
+        <AppList ref="appList" items={items} parentPath={currentItemPath} onItemClick={this.onItemClick} />
22 50
       </div>
23 51
     );
52
+
53
+  },
54
+
55
+  onBackClick: function(itemPath) {
56
+
57
+    var parentPath = this._normalizeItemPath(itemPath).slice(0, -1);
58
+    var parentItem = this._getItemByPath(parentPath);
59
+
60
+    this.play(this.refs.appList, 'slide-out-right 250ms ease-in-out')
61
+      .then(function() {
62
+        this.setState({currentItem: parentItem, currentItemPath: parentPath.join('.')});
63
+        return this.play(this.refs.appList, 'slide-in-left 250ms ease-in-out');
64
+      }.bind(this))
65
+    ;
66
+
67
+  },
68
+
69
+  onItemClick: function(evt, itemPath, item) {
70
+
71
+    if(item.exec) {
72
+
73
+      console.info('Launching application "'+item.exec+'"...');
74
+      evt.currentTarget.classList.add('pulse');
75
+
76
+      Util.System.runApp(item.exec)
77
+        .then(function() {
78
+          evt.currentTarget.classList.remove('pulse');
79
+        })
80
+        .catch(function(err) {
81
+          evt.currentTarget.classList.remove('pulse');
82
+        })
83
+      ;
84
+
85
+    } else {
86
+      this.play(this.refs.appList, 'slide-out-left 250ms ease-in-out')
87
+        .then(function() {
88
+          this.setState({ currentItemPath: itemPath, currentItem: item });
89
+          return this.play(this.refs.appList, 'slide-in-right 250ms ease-in-out');
90
+        }.bind(this))
91
+      ;
92
+    }
93
+
94
+  },
95
+
96
+  _getItemByPath: function(itemPath, rootItem) {
97
+
98
+    rootItem = rootItem || this.state.profile;
99
+    itemPath = this._normalizeItemPath(itemPath);
100
+
101
+    var itemIndex = itemPath[0];
102
+
103
+    if(itemIndex === undefined) {
104
+      return rootItem;
105
+    }
106
+
107
+    if(!('items' in rootItem)) {
108
+      return undefined;
109
+    }
110
+
111
+    var subItem = rootItem.items[itemIndex];
112
+
113
+    if(itemPath.length === 0) {
114
+      return subItem;
115
+    }
116
+
117
+    return this._getItemByPath(itemPath.slice(1), subItem);
118
+
119
+  },
120
+
121
+  _normalizeItemPath: function(itemPath) {
122
+
123
+    if( Array.isArray(itemPath) ) return itemPath;
124
+
125
+    if((typeof itemPath === 'string' && itemPath.length === 0) || !itemPath) return [];
126
+
127
+    return itemPath.split('.').reduce(function(arr, index) {
128
+      if(index !== '') {
129
+        arr.push(+index);
130
+      }
131
+      return arr;
132
+    }, []);
133
+
24 134
   }
25 135
 
26 136
 });

+ 28
- 0
js/components/app-item.jsx View File

@@ -0,0 +1,28 @@
1
+var React = require('react');
2
+
3
+module.exports = React.createClass({
4
+
5
+  propTypes: {
6
+    item: React.PropTypes.object.isRequired,
7
+    itemPath: React.PropTypes.oneOfType([
8
+      React.PropTypes.string,
9
+      React.PropTypes.arrayOf(React.PropTypes.number)
10
+    ]).isRequired,
11
+    onItemClick: React.PropTypes.func.isRequired,
12
+  },
13
+
14
+  _onItemClick: function(evt) {
15
+    evt.preventDefault();
16
+    this.props.onItemClick(evt, this.props.itemPath, this.props.item);
17
+  },
18
+
19
+  render: function() {
20
+    return (
21
+      <li className="app-item" onClick={this._onItemClick}>
22
+        <img className="app-icon" src={this.props.item.icon} />
23
+        <span className="app-label">{this.props.item.label}</span>
24
+      </li>
25
+    );
26
+  }
27
+
28
+});

+ 22
- 1
js/components/app-list.jsx View File

@@ -1,12 +1,33 @@
1 1
 var React = require('react');
2
+var AppItem = require('./app-item.jsx');
2 3
 
3 4
 module.exports = React.createClass({
4 5
 
6
+  propTypes: {
7
+    items: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
8
+    parentPath: React.PropTypes.oneOfType([
9
+      React.PropTypes.string,
10
+      React.PropTypes.arrayOf(React.PropTypes.number)
11
+    ]).isRequired,
12
+    onItemClick: React.PropTypes.func.isRequired,
13
+  },
14
+
5 15
   render: function() {
16
+
17
+    var parentPath = this.props.parentPath;
18
+    var items = (this.props.items).map(function(item, i) {
19
+      var path = parentPath+'.'+i;
20
+      return (
21
+        <AppItem key={path} itemPath={path} item={item} onItemClick={this.props.onItemClick} />
22
+      );
23
+    }.bind(this));
24
+
6 25
     return (
7
-      <ul className="apps-list">
26
+      <ul key={parentPath} className="apps-list">
27
+        {items}
8 28
       </ul>
9 29
     );
30
+
10 31
   }
11 32
 
12 33
 });

+ 17
- 6
js/components/category-header.jsx View File

@@ -2,18 +2,29 @@ var React = require('react');
2 2
 
3 3
 module.exports = React.createClass({
4 4
 
5
-  render: function() {
5
+  propTypes: {
6
+    onBackClick: React.PropTypes.func.isRequired,
7
+    itemPath: React.PropTypes.oneOfType([
8
+      React.PropTypes.string,
9
+      React.PropTypes.arrayOf(React.PropTypes.number)
10
+    ]).isRequired,
11
+    item: React.PropTypes.object.isRequired,
12
+  },
6 13
 
7
-    var classes = 'category-header' + (this.props.currentItem ? 'visible' : '');
8
-    var itemLabel = this.props.currentItem ? this.props.currentItem.label : '';
14
+  render: function() {
9 15
 
10 16
     return (
11
-      <div className={classes}>
12
-        <a href="#" className="goback">&#9668;</a>
13
-        <span className="category-label">{itemLabel}</span>
17
+      <div className="category-header">
18
+        <a href="#" onClick={this._onBackClick} className="goback" >&#9668;</a>
19
+        <span className="category-label">{this.props.item.label}</span>
14 20
       </div>
15 21
     );
16 22
 
23
+  },
24
+
25
+  _onBackClick: function(evt) {
26
+    evt.preventDefault();
27
+    this.props.onBackClick(this.props.itemPath, this.props.item);
17 28
   }
18 29
 
19 30
 });

+ 0
- 53
js/dom.js View File

@@ -1,53 +0,0 @@
1
-(function(Pitaya, window) {
2
-
3
-  "use strict";
4
-
5
-  var DOM = Pitaya.DOM = {};
6
-
7
-  /**
8
-   * Select an element in the DOM by its CSS selector
9
-   *
10
-   * @private
11
-   * @param selector The CSS selector
12
-   * @return The selected element or null
13
-   */
14
-   DOM.select = function(selector) {
15
-    return window.document.querySelector(selector);
16
-  };
17
-
18
-  /**
19
-   * Select all elements in the DOM with a CSS selector
20
-   *
21
-   * @private
22
-   * @param selector The CSS selector
23
-   * @return An array of the selected elements (if any)
24
-   */
25
-  DOM.selectAll = function(selector) {
26
-    return window.document.querySelectorAll(selector);
27
-  };
28
-
29
-  /**
30
-   * Find the closest ancestor matching the CSS selector
31
-   *
32
-   * @private
33
-   * @param selector The CSS selector
34
-   * @return An array of the selected elements (if any)
35
-   */
36
-  DOM.getClosestAncestor = function(el, selector) {
37
-
38
-    var parent = el.parentElement;
39
-
40
-    if(parent) {
41
-      if(parent.matches(selector)) {
42
-        return parent;
43
-      } else {
44
-        return DOM.getClosestAncestor(parent, selector);
45
-      }
46
-    }
47
-
48
-    return false;
49
-
50
-  };
51
-
52
-
53
-}(window.Pitaya = window.Pitaya || {}, window));

js/anim.js → js/mixins/animate.js View File

@@ -1,15 +1,14 @@
1
-(function(Pitaya, window) {
1
+var Events = {
2
+  ANIMATION_END: 'webkitAnimationEnd'
3
+};
2 4
 
3
-  "use strict";
5
+module.exports = {
4 6
 
5
-  var Anim = Pitaya.Anim = {};
6
-  var Events = Anim.Events = {
7
-    ANIMATION_END: 'webkitAnimationEnd'
8
-  };
9
-
10
-  Anim.play = function(el, animation) {
7
+  play: function(component, animation) {
11 8
     return new Promise(function(resolve, reject) {
12
-      
9
+
10
+      var el = component.getDOMNode();
11
+
13 12
       el.addEventListener(Events.ANIMATION_END, onAnimEnd, false);
14 13
       el.style.webkitAnimation = animation;
15 14
 
@@ -19,6 +18,6 @@
19 18
       }
20 19
 
21 20
     });
22
-  };
21
+  }
23 22
 
24
-}(window.Pitaya = window.Pitaya || {}, window));
23
+};

+ 1
- 0
js/util/index.js View File

@@ -0,0 +1 @@
1
+exports.System = require('./system');

+ 31
- 0
js/util/system.js View File

@@ -0,0 +1,31 @@
1
+var fs = require('fs');
2
+var cp = require('child_process');
3
+
4
+/**
5
+ * Load a JSON file
6
+ *
7
+ * @param filePath The path of the json file
8
+ * @return Promise
9
+ */
10
+exports.loadJSONFile = function(filePath) {
11
+  return new Promise(function(resolve, reject) {
12
+    fs.readFile(filePath, 'utf8', function(err, fileContent) {
13
+      if(err) return reject(err);
14
+      try {
15
+        var json = JSON.parse(fileContent);
16
+        return resolve(json);
17
+      } catch(err) {
18
+        return reject(err);
19
+      }
20
+    });
21
+  });
22
+};
23
+
24
+exports.runApp = function(execPath) {
25
+  return new Promise(function(resolve, reject) {
26
+    cp.exec(execPath, function(err) {
27
+      if(err) return reject(err);
28
+      return resolve();
29
+    });
30
+  });
31
+};

+ 0
- 2
package.json View File

@@ -22,11 +22,9 @@
22 22
   },
23 23
   "dependencies": {
24 24
     "glob": "^5.0.14",
25
-    "handlebars": "^3.0.3",
26 25
     "ini": "^1.3.4",
27 26
     "minimist": "^1.1.3",
28 27
     "node-jsx": "^0.13.3",
29
-    "r-dom": "^1.3.0",
30 28
     "react": "^0.13.3"
31 29
   }
32 30
 }

Loading…
Cancel
Save