Browse Source

Edition via drag & drop des items

upgrade-electron
wpetit 4 years ago
parent
commit
1136b693fd

+ 1
- 1
README.md View File

@@ -16,7 +16,7 @@ git clone https://forge.cadoles.com/wpetit/pitaya.git
16 16
 cd pitaya
17 17
 git checkout develop
18 18
 npm install
19
-npm start
19
+DEBUG=pitaya* npm start
20 20
 ```
21 21
 
22 22
 ## Options

+ 64
- 6
css/style.css View File

@@ -6,7 +6,6 @@ html, body {
6 6
   padding: 0;
7 7
   margin: 0;
8 8
   font-family: 'Droid Sans', 'Ubuntu Sans', sans-serif;
9
-  background: url('../img/background.png') no-repeat;
10 9
   background-size: contain;
11 10
   background-position: center;
12 11
   background-color: rgb(34, 107, 160);
@@ -16,6 +15,11 @@ html, body {
16 15
   overflow-x: hidden;
17 16
 }
18 17
 
18
+.alert.alert-default {
19
+  border-radius: 4px;
20
+  border: 1px solid #ddd;
21
+}
22
+
19 23
 /* Launcher View */
20 24
 
21 25
 .launcher {
@@ -23,6 +27,7 @@ html, body {
23 27
   width: 100%;
24 28
   height: 100%;
25 29
   flex-direction: column;
30
+  background: url('../img/background.png') no-repeat;
26 31
 }
27 32
 
28 33
 .launcher .category-header {
@@ -109,27 +114,80 @@ html, body {
109 114
   width: 100%;
110 115
   height: 100%;
111 116
   flex-direction: column;
112
-  align-items: flex-start;
113 117
 }
114 118
 
115
-.edit ul.desktop-apps {
119
+.edit .workspace {
120
+  display: flex;
121
+  flex-direction: row;
122
+  padding: 10px;
123
+}
124
+
125
+.edit .left-menu {
126
+  display: flex;
127
+  flex-direction: column;
128
+  flex: 1;
129
+}
130
+
131
+.edit .item-form {
132
+  display: flex;
133
+  flex-direction: row;
134
+  flex: 1;
135
+}
136
+
137
+.edit .apps-list .icon-theme-selector > select {
138
+  width: 100%;
139
+}
140
+
141
+.edit .apps-list ul.desktop-apps {
116 142
   list-style: none;
117 143
   padding: 0;
118 144
   overflow-y: auto;
145
+  height: 100%;
146
+  margin: 10px 0 0 0;
147
+  padding: 0 10px 0 0;
119 148
 }
120 149
 
121
-.edit li.desktop-app {
150
+.edit .apps-list li.desktop-app {
122 151
 
123 152
 }
124 153
 
125 154
 .edit .desktop-app > .app-icon {
126
-  height: 50px;
127
-  width: 50px;
155
+  height: 35px;
156
+  width: 35px;
128 157
   display: inline-block;
129 158
   vertical-align: middle;
130 159
   margin-right: 10px;
131 160
 }
132 161
 
162
+.edit .profile-tree {
163
+  flex: 3;
164
+  padding: 0 5px;
165
+}
166
+
167
+.edit .profile-tree ul {
168
+  list-style: none;
169
+  margin: 0;
170
+}
171
+
172
+.edit .profile-tree > .tree-item > ul {
173
+  padding: 0;
174
+}
175
+
176
+.edit .profile-tree .tree-item .alert {
177
+  padding: 5px;
178
+  margin-bottom: 2px;
179
+}
180
+
181
+.edit .profile-tree .tree-item .app-icon {
182
+  height: 25px;
183
+  margin-right: 5px;
184
+}
185
+
186
+.edit .app-item-edit {
187
+  flex: 1;
188
+  padding: 0 5px;
189
+}
190
+
133 191
 /* Animations */
134 192
 
135 193
 .pulse {

+ 2
- 2
default-profile.json View File

@@ -9,7 +9,7 @@
9 9
           "icon": "chromium-browser",
10 10
           "items": [
11 11
             {
12
-              "label": "Chromium Browser",
12
+              "label": "Chromium Browser 1",
13 13
               "icon": "chromium-browser",
14 14
               "exec": "/usr/bin/chromium-browser"
15 15
             }
@@ -24,7 +24,7 @@
24 24
               "icon": "chromium-browser",
25 25
               "items": [
26 26
                 {
27
-                  "label": "Chromium Browser",
27
+                  "label": "Chromium Browser 2",
28 28
                   "icon": "chromium-browser",
29 29
                   "exec": "/usr/bin/chromium-browser"
30 30
                 }

+ 61
- 1
img/default-icon.svg View File

@@ -1 +1,61 @@
1
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" ><path d="M0 0h512v512H0z" fill="transparent" stroke="#fff" stroke-width="0"></path><path d="M256 16C123.45 16 16 123.45 16 256s107.45 240 240 240 240-107.45 240-240S388.55 16 256 16zm0 60c99.41 0 180 80.59 180 180s-80.59 180-180 180S76 355.41 76 256 156.59 76 256 76zm-80.625 60c-.97-.005-2.006.112-3.063.313v-.032c-18.297 3.436-45.264 34.743-33.375 46.626l73.157 73.125-73.156 73.126c-14.63 14.625 29.275 58.534 43.906 43.906L256 299.906l73.156 73.156c14.63 14.628 58.537-29.28 43.906-43.906l-73.156-73.125 73.156-73.124c14.63-14.625-29.275-58.5-43.906-43.875L256 212.157l-73.156-73.125c-2.06-2.046-4.56-3.015-7.47-3.03z" fill="#fff"></path></svg>
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<svg
3
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
4
+   xmlns:cc="http://creativecommons.org/ns#"
5
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
+   xmlns:svg="http://www.w3.org/2000/svg"
7
+   xmlns="http://www.w3.org/2000/svg"
8
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10
+   viewBox="0 0 512 512"
11
+   id="svg2"
12
+   version="1.1"
13
+   inkscape:version="0.48.4 r9939"
14
+   width="100%"
15
+   height="100%"
16
+   sodipodi:docname="default-icon.svg">
17
+  <metadata
18
+     id="metadata12">
19
+    <rdf:RDF>
20
+      <cc:Work
21
+         rdf:about="">
22
+        <dc:format>image/svg+xml</dc:format>
23
+        <dc:type
24
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
25
+      </cc:Work>
26
+    </rdf:RDF>
27
+  </metadata>
28
+  <defs
29
+     id="defs10" />
30
+  <sodipodi:namedview
31
+     pagecolor="#ffffff"
32
+     bordercolor="#666666"
33
+     borderopacity="1"
34
+     objecttolerance="10"
35
+     gridtolerance="10"
36
+     guidetolerance="10"
37
+     inkscape:pageopacity="0"
38
+     inkscape:pageshadow="2"
39
+     inkscape:window-width="1600"
40
+     inkscape:window-height="850"
41
+     id="namedview8"
42
+     showgrid="false"
43
+     inkscape:zoom="0.65186406"
44
+     inkscape:cx="59.383922"
45
+     inkscape:cy="222.95767"
46
+     inkscape:window-x="0"
47
+     inkscape:window-y="0"
48
+     inkscape:window-maximized="1"
49
+     inkscape:current-layer="svg2" />
50
+  <path
51
+     d="M0 0h512v512H0z"
52
+     fill="transparent"
53
+     stroke="#fff"
54
+     stroke-width="0"
55
+     id="path4" />
56
+  <path
57
+     d="M256 16C123.45 16 16 123.45 16 256s107.45 240 240 240 240-107.45 240-240S388.55 16 256 16zm0 60c99.41 0 180 80.59 180 180s-80.59 180-180 180S76 355.41 76 256 156.59 76 256 76zm-80.625 60c-.97-.005-2.006.112-3.063.313v-.032c-18.297 3.436-45.264 34.743-33.375 46.626l73.157 73.125-73.156 73.126c-14.63 14.625 29.275 58.534 43.906 43.906L256 299.906l73.156 73.156c14.63 14.628 58.537-29.28 43.906-43.906l-73.156-73.125 73.156-73.124c14.63-14.625-29.275-58.5-43.906-43.875L256 212.157l-73.156-73.125c-2.06-2.046-4.56-3.015-7.47-3.03z"
58
+     fill="#fff"
59
+     id="path6"
60
+     style="fill:#a4a4a4;fill-opacity:1" />
61
+</svg>

+ 154
- 1
img/hourglass.svg View File

@@ -1 +1,154 @@
1
-<?xml version="1.0" encoding="utf-8"?><svg width='50px' height='50px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-hourglass"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><g><path fill="none" stroke="#ffffff" stroke-width="5" stroke-miterlimit="10" d="M58.4,51.7c-0.9-0.9-1.4-2-1.4-2.3s0.5-0.4,1.4-1.4 C70.8,43.8,79.8,30.5,80,15.5H70H30H20c0.2,15,9.2,28.1,21.6,32.3c0.9,0.9,1.4,1.2,1.4,1.5s-0.5,1.6-1.4,2.5 C29.2,56.1,20.2,69.5,20,85.5h10h40h10C79.8,69.5,70.8,55.9,58.4,51.7z" class="glass"></path><clipPath id="uil-hourglass-clip1"><rect x="15" y="20" width="70" height="25" class="clip"><animate attributeName="height" from="25" to="0" dur="1s" repeatCount="indefinite" vlaues="25;0;0" keyTimes="0;0.5;1"></animate><animate attributeName="y" from="20" to="45" dur="1s" repeatCount="indefinite" vlaues="20;45;45" keyTimes="0;0.5;1"></animate></rect></clipPath><clipPath id="uil-hourglass-clip2"><rect x="15" y="55" width="70" height="25" class="clip"><animate attributeName="height" from="0" to="25" dur="1s" repeatCount="indefinite" vlaues="0;25;25" keyTimes="0;0.5;1"></animate><animate attributeName="y" from="80" to="55" dur="1s" repeatCount="indefinite" vlaues="80;55;55" keyTimes="0;0.5;1"></animate></rect></clipPath><path d="M29,23c3.1,11.4,11.3,19.5,21,19.5S67.9,34.4,71,23H29z" clip-path="url(#uil-hourglass-clip1)" fill="#d2d2d2" class="sand"></path><path d="M71.6,78c-3-11.6-11.5-20-21.5-20s-18.5,8.4-21.5,20H71.6z" clip-path="url(#uil-hourglass-clip2)" fill="#d2d2d2" class="sand"></path><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="180 50 50" repeatCount="indefinite" dur="1s" values="0 50 50;0 50 50;180 50 50" keyTimes="0;0.7;1"></animateTransform></g></svg>
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<svg
3
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
4
+   xmlns:cc="http://creativecommons.org/ns#"
5
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
+   xmlns:svg="http://www.w3.org/2000/svg"
7
+   xmlns="http://www.w3.org/2000/svg"
8
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10
+   width="50px"
11
+   height="50px"
12
+   viewBox="0 0 100 100"
13
+   preserveAspectRatio="xMidYMid"
14
+   class="uil-hourglass"
15
+   id="svg2"
16
+   version="1.1"
17
+   inkscape:version="0.48.4 r9939"
18
+   sodipodi:docname="hourglass.svg">
19
+  <metadata
20
+     id="metadata34">
21
+    <rdf:RDF>
22
+      <cc:Work
23
+         rdf:about="">
24
+        <dc:format>image/svg+xml</dc:format>
25
+        <dc:type
26
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
27
+      </cc:Work>
28
+    </rdf:RDF>
29
+  </metadata>
30
+  <defs
31
+     id="defs32" />
32
+  <sodipodi:namedview
33
+     pagecolor="#ffffff"
34
+     bordercolor="#666666"
35
+     borderopacity="1"
36
+     objecttolerance="10"
37
+     gridtolerance="10"
38
+     guidetolerance="10"
39
+     inkscape:pageopacity="0"
40
+     inkscape:pageshadow="2"
41
+     inkscape:window-width="1600"
42
+     inkscape:window-height="850"
43
+     id="namedview30"
44
+     showgrid="false"
45
+     inkscape:zoom="13.350176"
46
+     inkscape:cx="-10.530194"
47
+     inkscape:cy="27.889305"
48
+     inkscape:window-x="0"
49
+     inkscape:window-y="0"
50
+     inkscape:window-maximized="1"
51
+     inkscape:current-layer="g6" />
52
+  <rect
53
+     x="0"
54
+     y="0"
55
+     width="100"
56
+     height="100"
57
+     fill="none"
58
+     class="bk"
59
+     id="rect4" />
60
+  <g
61
+     id="g6">
62
+    <path
63
+       fill="none"
64
+       stroke="#ffffff"
65
+       stroke-width="5"
66
+       stroke-miterlimit="10"
67
+       d="M58.4,51.7c-0.9-0.9-1.4-2-1.4-2.3s0.5-0.4,1.4-1.4 C70.8,43.8,79.8,30.5,80,15.5H70H30H20c0.2,15,9.2,28.1,21.6,32.3c0.9,0.9,1.4,1.2,1.4,1.5s-0.5,1.6-1.4,2.5 C29.2,56.1,20.2,69.5,20,85.5h10h40h10C79.8,69.5,70.8,55.9,58.4,51.7z"
68
+       class="glass"
69
+       id="path8"
70
+       style="fill:#a4a4a4;fill-opacity:1" />
71
+    <clipPath
72
+       id="uil-hourglass-clip1">
73
+      <rect
74
+         x="15"
75
+         y="20"
76
+         width="70"
77
+         height="25"
78
+         class="clip"
79
+         id="rect11">
80
+        <animate
81
+           attributeName="height"
82
+           from="25"
83
+           to="0"
84
+           dur="1s"
85
+           repeatCount="indefinite"
86
+           vlaues="25;0;0"
87
+           keyTimes="0;0.5;1"
88
+           id="animate13" />
89
+        <animate
90
+           attributeName="y"
91
+           from="20"
92
+           to="45"
93
+           dur="1s"
94
+           repeatCount="indefinite"
95
+           vlaues="20;45;45"
96
+           keyTimes="0;0.5;1"
97
+           id="animate15" />
98
+      </rect>
99
+    </clipPath>
100
+    <clipPath
101
+       id="uil-hourglass-clip2">
102
+      <rect
103
+         x="15"
104
+         y="55"
105
+         width="70"
106
+         height="25"
107
+         class="clip"
108
+         id="rect18">
109
+        <animate
110
+           attributeName="height"
111
+           from="0"
112
+           to="25"
113
+           dur="1s"
114
+           repeatCount="indefinite"
115
+           vlaues="0;25;25"
116
+           keyTimes="0;0.5;1"
117
+           id="animate20" />
118
+        <animate
119
+           attributeName="y"
120
+           from="80"
121
+           to="55"
122
+           dur="1s"
123
+           repeatCount="indefinite"
124
+           vlaues="80;55;55"
125
+           keyTimes="0;0.5;1"
126
+           id="animate22" />
127
+      </rect>
128
+    </clipPath>
129
+    <path
130
+       d="M29,23c3.1,11.4,11.3,19.5,21,19.5S67.9,34.4,71,23H29z"
131
+       clip-path="url(#uil-hourglass-clip1)"
132
+       fill="#d2d2d2"
133
+       class="sand"
134
+       id="path24"
135
+       style="fill:#ffffff;fill-opacity:1" />
136
+    <path
137
+       d="M71.6,78c-3-11.6-11.5-20-21.5-20s-18.5,8.4-21.5,20H71.6z"
138
+       clip-path="url(#uil-hourglass-clip2)"
139
+       fill="#d2d2d2"
140
+       class="sand"
141
+       id="path26"
142
+       style="fill:#ffffff;fill-opacity:1" />
143
+    <animateTransform
144
+       attributeName="transform"
145
+       type="rotate"
146
+       from="0 50 50"
147
+       to="180 50 50"
148
+       repeatCount="indefinite"
149
+       dur="1s"
150
+       values="0 50 50;0 50 50;180 50 50"
151
+       keyTimes="0;0.7;1"
152
+       id="animateTransform28" />
153
+  </g>
154
+</svg>

+ 2
- 1
index.html View File

@@ -2,12 +2,13 @@
2 2
   <head>
3 3
     <title>Lanceur - Pitaya</title>
4 4
     <link rel="stylesheet" href="css/style.css" />
5
+    <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
5 6
   </head>
6 7
   <body>
7 8
 
8 9
     <!-- Application root element -->
9 10
     <div id="pitaya"></div>
10
-    
11
+
11 12
     <!-- Scripts -->
12 13
     <script type="text/javascript">
13 14
       // React context detection workaround

+ 1
- 8
js/app.jsx View File

@@ -12,14 +12,7 @@ var App = React.createClass({
12 12
 
13 13
     var editMode = this.props.processOpts.edit || false;
14 14
 
15
-    var view = editMode ?
16
-      <Provider store={store}>
17
-        { function() { return <EditView />; }.bind(this) }
18
-      </Provider> :
19
-      <Provider store={store}>
20
-        { function() { return <LauncherView />; }.bind(this) }
21
-      </Provider>
22
-    ;
15
+    var view = editMode ? <EditView /> : <LauncherView />;
23 16
 
24 17
     return (
25 18
       <div id="pitaya">

+ 2
- 2
js/components/common/app-icon.jsx View File

@@ -11,7 +11,7 @@ module.exports = React.createClass({
11 11
   mixins: [LazyLoad],
12 12
 
13 13
   getInitialState: function() {
14
-    return { icon: DEFAULT_ICON, currentTheme: undefined };
14
+    return { icon: DEFAULT_ICON, currentTheme: '' };
15 15
   },
16 16
 
17 17
   onInViewport: function() {
@@ -23,7 +23,7 @@ module.exports = React.createClass({
23 23
     var currentTheme = this.state.currentTheme;
24 24
     var newTheme = this.props.theme;
25 25
 
26
-    if( !this.isInViewport() || newTheme === currentTheme ) return;
26
+    if( !this.isInViewport() ||  newTheme === currentTheme ) return;
27 27
 
28 28
     this.setState({ icon: LOADING_ICON, currentTheme: newTheme });
29 29
 

+ 35
- 3
js/components/edit/desktop-app-item.jsx View File

@@ -1,8 +1,9 @@
1 1
 var React = require('react');
2 2
 var Util = require('../../util');
3 3
 var AppIcon = require('../common/app-icon.jsx');
4
+var DragSource = require('react-dnd').DragSource;
4 5
 
5
-module.exports = React.createClass({
6
+var DesktopAppItem = React.createClass({
6 7
 
7 8
   render: function() {
8 9
 
@@ -11,8 +12,10 @@ module.exports = React.createClass({
11 12
     var category = desktopEntry.Categories;
12 13
     var icon = desktopEntry.Icon;
13 14
 
14
-    return (
15
-      <li className="desktop-app">
15
+    var connectDragSource = this.props.connectDragSource;
16
+
17
+    return connectDragSource(
18
+      <li className="desktop-app list-group-item">
16 19
         <AppIcon className="desktop-app-icon" icon={icon} theme={this.props.theme} />
17 20
         <span className="desktop-app-label">{label}</span>
18 21
       </li>
@@ -21,3 +24,32 @@ module.exports = React.createClass({
21 24
   }
22 25
 
23 26
 });
27
+
28
+var dragSourceSpec = {
29
+
30
+  beginDrag: function(props) {
31
+    return props;
32
+  },
33
+
34
+  endDrag: function(props, monitor) {
35
+
36
+    if (!monitor.didDrop()) {
37
+      return;
38
+    }
39
+
40
+    var dropResult = monitor.getDropResult();
41
+
42
+    return props.onItemDropped(props.desktopEntry, dropResult);
43
+
44
+  }
45
+
46
+};
47
+
48
+function dragSourceCollect(connect, monitor) {
49
+  return {
50
+    connectDragSource: connect.dragSource(),
51
+    isDragging: monitor.isDragging()
52
+  };
53
+}
54
+
55
+module.exports = DragSource('NEW_ITEM', dragSourceSpec, dragSourceCollect)(DesktopAppItem);

+ 12
- 18
js/components/edit/desktop-app-list.jsx View File

@@ -1,39 +1,33 @@
1 1
 var React = require('react');
2 2
 var Util = require('../../util');
3 3
 var DesktopAppItem = require('./desktop-app-item.jsx');
4
-var IconThemeSelector = require('./icon-theme-selector.jsx');
5 4
 var path = require('path');
6
-var debug = require('debug')('pitaya:desktop-app-list');
5
+var debug = require('../../util/debug')('pitaya:desktop-app-list');
7 6
 
8
-module.exports = React.createClass({
9
-
10
-  getInitialState: function() {
11
-    return {
12
-      selectedTheme: null
13
-    };
14
-  },
7
+var DesktopAppList = React.createClass({
15 8
 
16 9
   render: function() {
17 10
 
18 11
     var items = this.props.desktopApps.map(function(desktopApp, i) {
19 12
       var desktopEntry = desktopApp.content['Desktop Entry'];
20
-      return <DesktopAppItem theme={this.state.selectedTheme} key={desktopApp.path} desktopEntry={desktopEntry} />;
13
+      return (
14
+        <DesktopAppItem theme={this.props.selectedTheme}
15
+          key={desktopApp.path}
16
+          desktopEntry={desktopEntry}
17
+          onItemDropped={this.props.onItemDropped} />
18
+      );
21 19
     }.bind(this));
22 20
 
23 21
     return (
24
-      <div>
25
-        <IconThemeSelector onThemeSelected={this.onThemeSelected} />
26
-        <ul className="desktop-apps">
22
+      <div className="apps-list">
23
+        <ul className="desktop-apps list-group">
27 24
           {items}
28 25
         </ul>
29 26
       </div>
30 27
     );
31 28
 
32
-  },
33
-
34
-  onThemeSelected: function(theme) {
35
-    console.log('Selected theme %s', theme);
36
-    this.setState({ selectedTheme: theme });
37 29
   }
38 30
 
39 31
 });
32
+
33
+module.exports = DesktopAppList;

+ 41
- 4
js/components/edit/edit-view.jsx View File

@@ -1,7 +1,13 @@
1 1
 var React = require('react');
2 2
 var connect = require('react-redux').connect;
3
+var ProfileTree = require('./profile-tree.jsx');
3 4
 var DesktopAppList = require('./desktop-app-list.jsx');
4
-var actions = require('../../actions');
5
+var ItemForm = require('./item-form.jsx');
6
+var IconThemeSelector = require('./icon-theme-selector.jsx');
7
+
8
+var actions = require('../../store/actions');
9
+var DragDropContext = require('react-dnd').DragDropContext;
10
+var HTML5Backend = require('react-dnd/modules/backends/HTML5');
5 11
 
6 12
 var EditView = React.createClass({
7 13
 
@@ -13,18 +19,49 @@ var EditView = React.createClass({
13 19
 
14 20
     return (
15 21
       <div className="edit">
16
-        <DesktopAppList desktopApps={this.props.desktopApps} />
22
+        <div className="menu-bar">
23
+
24
+        </div>
25
+        <div className="workspace">
26
+          <div className="left-menu">
27
+            <IconThemeSelector onThemeSelected={this.onThemeSelected} />
28
+            <DesktopAppList
29
+              theme={this.props.theme}
30
+              desktopApps={this.props.desktopApps}
31
+              onItemDropped={this.onItemDropped} />
32
+          </div>
33
+          <ProfileTree />
34
+          <ItemForm />
35
+        </div>
17 36
       </div>
18 37
     );
19 38
 
39
+  },
40
+
41
+  onItemDropped: function(desktopEntry, targetItem) {
42
+
43
+    var newProfileItem = {
44
+      label: desktopEntry.Name,
45
+      icon: desktopEntry.Icon,
46
+      exec: desktopEntry.Exec
47
+    };
48
+
49
+    this.props.dispatch(actions.edit.addProfileItem(newProfileItem, targetItem));
50
+
51
+  },
52
+
53
+  onThemeSelected: function(theme) {
54
+    this.props.dispatch(actions.edit.selectTheme(theme));
20 55
   }
21 56
 
22 57
 });
23 58
 
24 59
 function select(state) {
25 60
   return {
26
-    desktopApps: state.desktopApps
61
+    desktopApps: state.desktopApps,
62
+    profile: state.profile,
63
+    theme: state.theme
27 64
   };
28 65
 }
29 66
 
30
-module.exports = connect(select)(EditView);
67
+module.exports = DragDropContext(HTML5Backend)(connect(select)(EditView));

+ 5
- 3
js/components/edit/icon-theme-selector.jsx View File

@@ -39,9 +39,11 @@ module.exports = React.createClass({
39 39
     );
40 40
 
41 41
     return (
42
-      <select value={selectedTheme} onChange={this.onChange}>
43
-        {options}
44
-      </select>
42
+      <div className="icon-theme-selector">
43
+        <select className="form-control" value={selectedTheme} onChange={this.onChange}>
44
+          {options}
45
+        </select>
46
+      </div>
45 47
     );
46 48
 
47 49
   }

+ 16
- 0
js/components/edit/item-form.jsx View File

@@ -0,0 +1,16 @@
1
+var React = require('react');
2
+
3
+var ItemForm = React.createClass({
4
+
5
+  render: function() {
6
+
7
+    return (
8
+      <div className="item-form">
9
+      </div>
10
+    );
11
+
12
+  }
13
+
14
+});
15
+
16
+module.exports = ItemForm;

+ 67
- 0
js/components/edit/profile-tree.jsx View File

@@ -0,0 +1,67 @@
1
+var React = require('react');
2
+var connect = require('react-redux').connect;
3
+var actions = require('../../store/actions');
4
+var TreeItem = require('./tree-item.jsx');
5
+
6
+var TreeNode = React.createClass({
7
+
8
+  render: function() {
9
+
10
+    var data = this.props.data || {};
11
+    var subItems = data.items || [];
12
+
13
+    var listElements = subItems.map(function(subItem, i) {
14
+      return (
15
+        <li key={i} >
16
+          <TreeNode data={subItem} onItemMoved={this.props.onItemMoved} />
17
+        </li>
18
+      );
19
+    }.bind(this));
20
+
21
+    var appEntry = data.icon || data.label ?
22
+      <TreeItem data={data} onItemMoved={this.props.onItemMoved} /> :
23
+      null
24
+    ;
25
+
26
+    return (
27
+      <div className="tree-item">
28
+        {appEntry}
29
+        <ul>
30
+          {listElements}
31
+        </ul>
32
+      </div>
33
+    );
34
+
35
+  }
36
+
37
+});
38
+
39
+var ProfileTree = React.createClass({
40
+
41
+  componentDidMount: function() {
42
+    this.props.dispatch(actions.launcher.loadProfile('./default-profile.json'));
43
+  },
44
+
45
+  render: function() {
46
+
47
+    return (
48
+      <div className="profile-tree">
49
+        <TreeNode data={this.props.profile} onItemMoved={this.onItemMoved} />
50
+      </div>
51
+    );
52
+
53
+  },
54
+
55
+  onItemMoved: function(movedItem, targetItem) {
56
+    this.props.dispatch(actions.edit.moveProfileItem(movedItem, targetItem));
57
+  }
58
+
59
+});
60
+
61
+function select(state) {
62
+  return {
63
+    profile: state.profile
64
+  };
65
+}
66
+
67
+module.exports = connect(select)(ProfileTree);

+ 86
- 0
js/components/edit/tree-item.jsx View File

@@ -0,0 +1,86 @@
1
+var React = require('react/addons');
2
+var classNames = require('classnames');
3
+var AppIcon = require('../common/app-icon.jsx');
4
+var DragSource = require('react-dnd').DragSource;
5
+var DropTarget = require('react-dnd').DropTarget;
6
+var _ = require('lodash');
7
+
8
+var TreeItem = React.createClass({
9
+
10
+    render: function() {
11
+
12
+      var data = this.props.data;
13
+      var appIcon = data.icon ? <AppIcon icon={data.icon} /> : null;
14
+
15
+      var connectDragSource = this.props.connectDragSource;
16
+      var connectDropTarget = this.props.connectDropTarget;
17
+
18
+      var classes = classNames({
19
+        'alert': true,
20
+        'alert-default': !this.props.isDragging && !this.props.isOver,
21
+        'alert-info': this.props.isDragging,
22
+        'alert-success': this.props.isOver
23
+      });
24
+
25
+      return connectDropTarget(connectDragSource(
26
+        <div className={classes}>
27
+          {appIcon}
28
+          <span className="app-label">{data.label}</span>
29
+        </div>
30
+      ));
31
+
32
+    },
33
+
34
+});
35
+
36
+var dragSourceSpec = {
37
+
38
+  beginDrag: function(props) {
39
+    return props.data;
40
+  },
41
+
42
+  endDrag: function(props, monitor) {
43
+
44
+    if (!monitor.didDrop()) {
45
+      return;
46
+    }
47
+
48
+    var dropResult = monitor.getDropResult();
49
+
50
+    return props.onItemMoved(props.data, dropResult);
51
+
52
+  }
53
+
54
+};
55
+
56
+var dropTargetSpec = {
57
+
58
+  drop: function(props, monitor, component) {
59
+    return props.data;
60
+  },
61
+
62
+  canDrop: function(props, monitor) {
63
+    var draggedItem = monitor.getItem();
64
+    return !_.isEqual(draggedItem, props.data);
65
+  }
66
+
67
+};
68
+
69
+function dragSourceCollect(connect, monitor) {
70
+  return {
71
+    connectDragSource: connect.dragSource(),
72
+    isDragging: monitor.isDragging()
73
+  };
74
+}
75
+
76
+function dropTargetCollect(connect, monitor) {
77
+  return {
78
+    connectDropTarget: connect.dropTarget(),
79
+    isOver: monitor.isOver()
80
+  };
81
+}
82
+
83
+
84
+module.exports = DropTarget(['ITEM', 'NEW_ITEM'], dropTargetSpec, dropTargetCollect)(
85
+  DragSource('ITEM', dragSourceSpec, dragSourceCollect)(TreeItem)
86
+);

+ 1
- 1
js/components/launcher/launcher-view.jsx View File

@@ -2,7 +2,7 @@ var React = require('react');
2 2
 var CategoryHeader = require('./category-header.jsx');
3 3
 var AppList = require('./app-list.jsx');
4 4
 var AnimateMixin = require('../mixins/animate');
5
-var actions = require('../../actions');
5
+var actions = require('../../store/actions');
6 6
 var connect = require('react-redux').connect;
7 7
 var debug = require('../../util/debug')('launcher-view');
8 8
 

+ 1
- 1
js/components/mixins/lazy-load.js View File

@@ -46,7 +46,7 @@ module.exports = {
46 46
 };
47 47
 
48 48
 
49
-var computeComponentsVisibilityDebounced = debounce(computeComponentsVisibility, 250);
49
+var computeComponentsVisibilityDebounced = debounce(computeComponentsVisibility, 100);
50 50
 
51 51
 // Start listening for changes
52 52
 window.document.addEventListener('scroll', computeComponentsVisibilityDebounced, true);

js/actions/edit.js → js/store/actions/edit.js View File

@@ -1,12 +1,15 @@
1
-var Util = require('../util');
1
+var Util = require('../../util');
2 2
 var path = require('path');
3 3
 
4 4
 // Action types
5 5
 var LOAD_DESKTOP_APPS = exports.LOAD_PROFILE = 'LOAD_DESKTOP_APPS';
6 6
 var LOAD_DESKTOP_APPS_SUCCESS = exports.LOAD_DESKTOP_APPS_SUCCESS = 'LOAD_DESKTOP_APPS_SUCCESS';
7 7
 var LOAD_DESKTOP_APPS_FAILED = exports.LOAD_DESKTOP_APPS_FAILED = 'LOAD_DESKTOP_APPS_FAILED';
8
+var MOVE_PROFILE_ITEM = exports.MOVE_PROFILE_ITEM = 'MOVE_PROFILE_ITEM';
9
+var ADD_PROFILE_ITEM = exports.ADD_PROFILE_ITEM = 'ADD_PROFILE_ITEM';
10
+
11
+// Actions creators
8 12
 
9
-// Actions creator
10 13
 exports.loadDesktopApps = function() {
11 14
   return function(dispatch, getState) {
12 15
 
@@ -27,3 +30,20 @@ exports.loadDesktopApps = function() {
27 30
 
28 31
   };
29 32
 };
33
+
34
+
35
+exports.moveProfileItem = function(movedItem, targetItem) {
36
+  return {
37
+    type: 'MOVE_PROFILE_ITEM',
38
+    movedItem: movedItem,
39
+    targetItem: targetItem
40
+  };
41
+};
42
+
43
+exports.addProfileItem = function(newItem, targetItem) {
44
+  return {
45
+    type: 'ADD_PROFILE_ITEM',
46
+    newItem: newItem,
47
+    targetItem: targetItem
48
+  };
49
+};

js/actions/index.js → js/store/actions/index.js View File


js/actions/launcher.js → js/store/actions/launcher.js View File

@@ -1,4 +1,4 @@
1
-var Util = require('../util');
1
+var Util = require('../../util');
2 2
 
3 3
 var LOAD_PROFILE = exports.LOAD_PROFILE = 'LOAD_PROFILE';
4 4
 var LOAD_PROFILE_SUCCESS = exports.LOAD_PROFILE_SUCCESS = 'LOAD_PROFILE_SUCCESS';

+ 3
- 1
js/store/index.js View File

@@ -2,9 +2,11 @@ var redux = require('redux');
2 2
 var thunkMiddleware = require('redux-thunk');
3 3
 var loggerMiddleware = require('redux-logger');
4 4
 var reducers = require('./reducers');
5
+var loggerMiddleware = require('./middlewares/logger');
5 6
 
6 7
 var createStore = redux.applyMiddleware(
7
-  thunkMiddleware
8
+  thunkMiddleware,
9
+  loggerMiddleware
8 10
 )(redux.createStore);
9 11
 
10 12
 var appReducer = redux.combineReducers({

+ 15
- 0
js/store/middlewares/logger.js View File

@@ -0,0 +1,15 @@
1
+var debug = require('../../util/debug')('store:logger');
2
+
3
+module.exports = function loggerMiddleware(store) {
4
+  return function(next) {
5
+    return function(action) {
6
+      debug('Action %j', action);
7
+      debug('Store current state %j', store.getState());
8
+      next(action);
9
+      debug('Store new state %j', store.getState());
10
+      if(action.error) {
11
+        console.error(action.error.stack || action.error);
12
+      }
13
+    };
14
+  };
15
+};

+ 2
- 2
js/store/reducers/desktop-apps.js View File

@@ -1,8 +1,8 @@
1
-var actions = require('../../actions');
1
+var actions = require('../actions');
2 2
 
3 3
 module.exports = function(state, action) {
4 4
 
5
-  var desktopApps = [];
5
+  var desktopApps = state || [];
6 6
 
7 7
   if( action.type === actions.edit.LOAD_DESKTOP_APPS_SUCCESS ) {
8 8
     desktopApps = action.desktopApps;

+ 61
- 6
js/store/reducers/profile.js View File

@@ -1,17 +1,72 @@
1
-var actions = require('../../actions');
1
+var _ = require('lodash');
2
+var actions = require('../actions');
2 3
 
3 4
 module.exports = function(oldProfile, action) {
4 5
 
5
-  var newProfile = oldProfile || null;
6
-
7 6
   switch(action.type) {
8 7
 
9 8
     case actions.launcher.LOAD_PROFILE_SUCCESS:
10
-      newProfile = action.profile;
11
-      break;
9
+      return _.cloneDeep(action.profile);
10
+
11
+    case actions.edit.MOVE_PROFILE_ITEM:
12
+      return moveProfileItem(oldProfile, action.movedItem, action.targetItem);
13
+
14
+    case actions.edit.ADD_PROFILE_ITEM:
15
+      return addProfileItem(oldProfile, action.newItem, action.targetItem);
16
+
17
+    default:
18
+      return oldProfile || null;
12 19
 
13 20
   }
14 21
 
22
+};
23
+
24
+
25
+function moveProfileItem(oldProfile, movedItem, targetItem) {
26
+
27
+  var newProfile = _.cloneDeep(oldProfile);
28
+  var previousParent = treeFind(newProfile, movedItem).parent;
29
+  var newParent = treeFind(newProfile, targetItem).item;
30
+
31
+  previousParent.items = _.reject(previousParent.items, function(item) {
32
+    return _.isEqual(item, movedItem);
33
+  });
34
+
35
+  newParent.items = newParent.items || [];
36
+  newParent.items.push(_.cloneDeep(movedItem));
37
+
15 38
   return newProfile;
16 39
 
17
-};
40
+}
41
+
42
+function addProfileItem(oldProfile, newItem, targetItem) {
43
+
44
+  var newProfile = _.cloneDeep(oldProfile);
45
+  var newParent = treeFind(newProfile, targetItem).item;
46
+
47
+  newParent.items = newParent.items || [];
48
+  newParent.items.push(_.cloneDeep(newItem));
49
+
50
+  return newProfile;
51
+}
52
+
53
+function treeFind(branch, obj) {
54
+
55
+  var items = branch.items;
56
+
57
+  if(!items) return;
58
+
59
+  for( var i = 0, item = items[i]; (item = items[i]); i++ ) {
60
+
61
+    if( _.isEqual(item, obj) ) {
62
+      return {item: item, parent: branch};
63
+    }
64
+
65
+    if(item.items) {
66
+      var result = treeFind(item, obj);
67
+      if(result) return result;
68
+    }
69
+
70
+  }
71
+
72
+}

+ 8
- 3
js/util/debug.js View File

@@ -3,10 +3,15 @@ var util = require('util');
3 3
 
4 4
 module.exports = function createLogger(namespace) {
5 5
   var logger = debug('pitaya:'+namespace);
6
-  var console = global.window ? global.window.console : global.console;
6
+  var isNWContext = 'window' in global;
7
+  var console = isNWContext ? global.window.console : global.console;
7 8
   logger.log = function() {
8
-    var str = util.format.apply(util, arguments);
9
-    console.log(str);
9
+    if(isNWContext) {
10
+      console.log.apply(console, arguments);
11
+    } else {
12
+      var str = util.format.apply(util, arguments);
13
+      console.log(str);
14
+    }
10 15
   };
11 16
   return logger;
12 17
 };

+ 2
- 0
package.json View File

@@ -24,9 +24,11 @@
24 24
   },
25 25
   "dependencies": {
26 26
     "bootstrap": "^3.3.5",
27
+    "classnames": "^2.1.3",
27 28
     "debug": "^2.2.0",
28 29
     "glob": "^5.0.14",
29 30
     "ini": "^1.3.4",
31
+    "lodash": "^3.10.1",
30 32
     "minimist": "^1.1.3",
31 33
     "node-jsx": "^0.13.3",
32 34
     "react": "^0.13.3",

Loading…
Cancel
Save