Browse Source

added dynamic sidebar + fixed relative url with webpack

Skylsmoi 6 years ago
parent
commit
d96a851971

+ 9 - 9
dist/index.html View File

@@ -6,18 +6,18 @@
6 6
     <title>Tracim</title>
7 7
     <link rel='shortcut icon' href='favicon.ico'>
8 8
 
9
-    <link rel="stylesheet" type="text/css" href="./font/font-awesome-4.7.0/css/font-awesome.css">
9
+    <link rel="stylesheet" type="text/css" href="/font/font-awesome-4.7.0/css/font-awesome.css">
10 10
     <link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
11 11
     <!--
12 12
     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
13 13
     -->
14
-    <link rel="stylesheet" type="text/css" href="./dev/bootstrap-4.0.0-beta.css">
14
+    <link rel="stylesheet" type="text/css" href="/dev/bootstrap-4.0.0-beta.css">
15 15
   </head>
16 16
   <body>
17 17
     <div id='content'></div>
18 18
 
19
-    <script src='tracim.vendor.bundle.js'></script>
20
-    <script src='tracim.app.entry.js'></script>
19
+    <script src='/tracim.vendor.bundle.js'></script>
20
+    <script src='/tracim.app.entry.js'></script>
21 21
 
22 22
     <!--
23 23
     <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
@@ -25,12 +25,12 @@
25 25
     <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
26 26
     -->
27 27
 
28
-    <script src='./app/pageHtml.app.js'></script>
29
-    <script src='./app/thread.app.js'></script>
28
+    <script src='/app/pageHtml.app.js'></script>
29
+    <script src='/app/thread.app.js'></script>
30 30
 
31
-    <script src="./dev/jquery-3.2.1.js"></script>
32
-    <script src="./dev/popper-1.12.3.js"></script>
33
-    <script src="./dev/bootstrap-4.0.0-beta.2.js"></script>
31
+    <script src="/dev/jquery-3.2.1.js"></script>
32
+    <script src="/dev/popper-1.12.3.js"></script>
33
+    <script src="/dev/bootstrap-4.0.0-beta.2.js"></script>
34 34
 
35 35
     <script type='text/javascript'>
36 36
       const pageHtml = new appPageHtml()

+ 2 - 0
jsonserver/server.js View File

@@ -29,6 +29,8 @@ server.get('/app/config', (req, res) => res.jsonp(jsonDb.app_config))
29 29
 
30 30
 server.get('/user/is_logged_in', (req, res) => res.jsonp(jsonDb.user_logged))
31 31
 
32
+server.get('/user/:id/workspace', (req, res) => res.jsonp(jsonDb.workspace_list))
33
+
32 34
 server.get('/workspace/:id', (req, res) => res.jsonp(jsonDb.workspace_detail))
33 35
 
34 36
 server.get('/workspace/:idws/content/:idc', (req, res) => {

+ 20 - 1
jsonserver/static_db.json View File

@@ -259,5 +259,24 @@
259 259
         "hour": "09h59"
260 260
       }
261 261
     }]
262
-  }
262
+  },
263
+  "workspace_list": [{
264
+    "id": 0,
265
+    "title": "Ressources humaine"
266
+  }, {
267
+    "id": 1,
268
+    "title": "Marketing"
269
+  }, {
270
+    "id": 2,
271
+    "title": "Missions externes"
272
+  }, {
273
+    "id": 3,
274
+    "title": "Recherche et développement"
275
+  }, {
276
+    "id": 4,
277
+    "title": "Commercialisation"
278
+  }, {
279
+    "id": 5,
280
+    "title": "Évolutions"
281
+  }]
263 282
 }

+ 1 - 0
package.json View File

@@ -28,6 +28,7 @@
28 28
     "file-loader": "^1.1.5",
29 29
     "prop-types": "^15.6.0",
30 30
     "react": "^16.0.0",
31
+    "react-animate-height": "^0.10.10",
31 32
     "react-dom": "^16.0.0",
32 33
     "react-redux": "^5.0.6",
33 34
     "react-router-dom": "^4.2.2",

+ 28 - 18
src/action-creator.async.js View File

@@ -7,6 +7,8 @@ import {
7 7
   updateUserData,
8 8
   WORKSPACE,
9 9
   updateWorkspaceData,
10
+  WORKSPACE_LIST,
11
+  updateWorkspaceListData,
10 12
   APP_LIST,
11 13
   setAppList
12 14
 } from './action-creator.sync.js'
@@ -58,18 +60,16 @@ const fetchWrapper = async ({url, param, actionName, dispatch, debug = false}) =
58 60
 }
59 61
 
60 62
 export const userLogin = (login, password, rememberMe) => async dispatch => {
61
-  const jsonBody = JSON.stringify({
62
-    login,
63
-    password,
64
-    remember_me: rememberMe
65
-  })
66
-
67 63
   const fetchUserLogin = await fetchWrapper({
68
-    url: 'http://localhost:3001/user/login',
64
+    url: `${FETCH_CONFIG.apiUrl}/user/login`,
69 65
     param: {
70
-      ...FETCH_CONFIG,
66
+      ...FETCH_CONFIG.header,
71 67
       method: 'POST',
72
-      body: jsonBody
68
+      body: JSON.stringify({
69
+        login,
70
+        password,
71
+        remember_me: rememberMe
72
+      })
73 73
     },
74 74
     actionName: USER_LOGIN,
75 75
     dispatch
@@ -79,8 +79,8 @@ export const userLogin = (login, password, rememberMe) => async dispatch => {
79 79
 
80 80
 export const getIsUserConnected = () => async dispatch => {
81 81
   const fetchUserLogged = await fetchWrapper({
82
-    url: 'http://localhost:3001/user/is_logged_in',
83
-    param: {...FETCH_CONFIG, method: 'GET'},
82
+    url: `${FETCH_CONFIG.apiUrl}/user/is_logged_in`,
83
+    param: {...FETCH_CONFIG.header, method: 'GET'},
84 84
     actionName: USER_CONNECTED,
85 85
     dispatch
86 86
   })
@@ -89,8 +89,8 @@ export const getIsUserConnected = () => async dispatch => {
89 89
 
90 90
 export const updateUserLang = newLang => async dispatch => {
91 91
   const fetchUpdateUserLang = await fetchWrapper({
92
-    url: 'http://localhost:3001/user',
93
-    param: {...FETCH_CONFIG, method: 'PATCH', body: JSON.stringify({lang: newLang})},
92
+    url: `${FETCH_CONFIG.apiUrl}/user`,
93
+    param: {...FETCH_CONFIG.header, method: 'PATCH', body: JSON.stringify({lang: newLang})},
94 94
     actionName: USER_DATA,
95 95
     dispatch
96 96
   })
@@ -100,17 +100,27 @@ export const updateUserLang = newLang => async dispatch => {
100 100
 // export const testResponseNoData = () => async dispatch => {
101 101
 //   const fetchResponseNoData = await fetchWrapper({
102 102
 //     url: 'http://localhost:3001/deletenodata',
103
-//     param: {...FETCH_CONFIG, method: 'DELETE'},
103
+//     param: {...FETCH_CONFIG.header, method: 'DELETE'},
104 104
 //     actionName: 'TestNoData',
105 105
 //     dispatch
106 106
 //   })
107 107
 //   console.log('jsonResponseNoData', fetchResponseNoData)
108 108
 // }
109 109
 
110
+export const getWorkspaceList = userId => async dispatch => {
111
+  const fetchGetWorkspaceList = await fetchWrapper({
112
+    url: `${FETCH_CONFIG.apiUrl}/user/${userId}/workspace`,
113
+    param: {...FETCH_CONFIG.header, method: 'GET'},
114
+    actionName: WORKSPACE_LIST,
115
+    dispatch
116
+  })
117
+  if (fetchGetWorkspaceList.status === 200) dispatch(updateWorkspaceListData(fetchGetWorkspaceList.json))
118
+}
119
+
110 120
 export const getWorkspaceContent = workspaceId => async dispatch => {
111 121
   const fetchGetWorkspaceContent = await fetchWrapper({
112
-    url: `http://localhost:3001/workspace/${workspaceId}`,
113
-    param: {...FETCH_CONFIG, method: 'GET'},
122
+    url: `${FETCH_CONFIG.apiUrl}/workspace/${workspaceId}`,
123
+    param: {...FETCH_CONFIG.header, method: 'GET'},
114 124
     actionName: WORKSPACE,
115 125
     dispatch
116 126
   })
@@ -119,8 +129,8 @@ export const getWorkspaceContent = workspaceId => async dispatch => {
119 129
 
120 130
 export const getAppList = () => async dispatch => {
121 131
   const fetchGetAppList = await fetchWrapper({
122
-    url: `http://localhost:3001/app/config`,
123
-    param: {...FETCH_CONFIG, method: 'GET'},
132
+    url: `${FETCH_CONFIG.apiUrl}/app/config`,
133
+    param: {...FETCH_CONFIG.header, method: 'GET'},
124 134
     actionName: APP_LIST,
125 135
     dispatch
126 136
   })

+ 4 - 0
src/action-creator.sync.js View File

@@ -8,6 +8,10 @@ export const updateUserData = userData => ({ type: `Update/${USER_DATA}`, data:
8 8
 export const WORKSPACE = 'Workspace'
9 9
 export const updateWorkspaceData = workspace => ({ type: `Update/${WORKSPACE}`, workspace })
10 10
 
11
+export const WORKSPACE_LIST = 'WorkspaceList'
12
+export const updateWorkspaceListData = workspaceList => ({ type: `Update/${WORKSPACE_LIST}`, workspaceList })
13
+export const updateWorkspaceListIsOpen = (workspaceId, isOpen) => ({ type: `Update/${WORKSPACE_LIST}/isOpen`, workspaceId, isOpen })
14
+
11 15
 export const FILE_CONTENT = 'FileContent'
12 16
 export const setActiveFileContent = file => ({ type: `Set/${FILE_CONTENT}/Active`, file })
13 17
 export const hideActiveFileContent = () => ({ type: `Set/${FILE_CONTENT}/Hide` })

+ 109 - 150
src/component/Sidebar/WorkspaceListItem.jsx View File

@@ -1,37 +1,17 @@
1 1
 import React from 'react'
2 2
 // import classnames from 'classnames'
3 3
 import PropTypes from 'prop-types'
4
+import AnimateHeight from 'react-animate-height'
4 5
 
5
-const WorkspaceListItem = props => {
6
-  const handleClickTitle = () => {
7
-    props.onClickTitle()
8
-    const subMenuElement = document.getElementById(`sidebarSubMenu_${props.number}`)
9
-
10
-    if (props.isOpen) {
11
-      subMenuElement.style.height = '0px'
12
-    } else {
13
-      subMenuElement.style.height = 'auto'
14
-      const autoHeight = subMenuElement.offsetHeight + 'px'
15
-
16
-      subMenuElement.style.height = '0px'
17
-      // the setTimeout ensure that the line bellow is executed right after previous has ended
18
-      setTimeout(() => { subMenuElement.style.height = autoHeight }, 1)
19
-    }
20
-  }
21
-
22
-  const pad = number => {
23
-    number = number.toString()
24
-    return number.length < 2 ? pad('0' + number, 2) : number
25
-  }
6
+const pad = number => {
7
+  number = number.toString()
8
+  return number.length < 2 ? pad('0' + number, 2) : number
9
+}
26 10
 
11
+const WorkspaceListItem = props => {
27 12
   return (
28
-    <li
29
-      className='sidebar__navigation__workspace__item nav-item dropdown'
30
-      onClick={handleClickTitle}
31
-    >
32
-
33
-      <div className='sidebar__navigation__workspace__item__wrapper'>
34
-
13
+    <li className='sidebar__navigation__workspace__item'>
14
+      <div className='sidebar__navigation__workspace__item__wrapper' onClick={props.onClickTitle}>
35 15
         <div className='sidebar__navigation__workspace__item__number'>
36 16
           {pad(props.number)}
37 17
         </div>
@@ -43,169 +23,146 @@ const WorkspaceListItem = props => {
43 23
         <div className='sidebar__navigation__workspace__item__icon'>
44 24
           <i className='fa fa-chevron-down' />
45 25
         </div>
46
-
47 26
       </div>
48 27
 
49
-      <ul
50
-        className='sidebar__navigation__workspace__item__submenu'
51
-        id={`sidebarSubMenu_${props.number}`}
52
-      >
53
-        <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
54
-
55
-          <div className='dropdown__icon'>
56
-            <i className='fa fa-th' />
57
-          </div>
58
-
59
-          <div
60
-            className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown dropdown-toggle'
61
-            role='button'
62
-            data-toggle='dropdown'
63
-            aria-haspopup='true'
64
-            aria-expanded='false'
28
+      <AnimateHeight duration={500} height={props.isOpen ? 'auto' : 0}>
29
+        <ul
30
+          className='sidebar__navigation__workspace__item__submenu'
31
+          id={`sidebarSubMenu_${props.number}`}
32
+        >
33
+          <li
34
+            className='sidebar__navigation__workspace__item__submenu__dropdown'
35
+            onClick={() => props.onClickAllContent(props.wsId)}
65 36
           >
37
+            <div className='dropdown__icon'>
38
+              <i className='fa fa-th' />
39
+            </div>
66 40
 
67
-            <div className='dropdown__title' id='navbarDropdown'>
68
-              <div className='dropdown__title__text'>
69
-                Tous les fichiers
41
+            <div className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown'>
42
+              <div className='dropdown__title'>
43
+                <div className='dropdown__title__text'>
44
+                  Tous les fichiers
45
+                </div>
70 46
               </div>
71 47
             </div>
72
-          </div>
73 48
 
74
-          {/*
75
-          <div className='dropdown__subdropdown dropdown-menu' aria-labelledby='navbarDropdown'>
76
-            <div className='dropdown__subdropdown__item dropdown-item'>
77
-              <div className='dropdown__subdropdown__item__iconfile alignname'>
78
-                <i className='fa fa-file-text-o' />
79
-              </div>
49
+            {/*
50
+            <div className='dropdown__subdropdown dropdown-menu' aria-labelledby='navbarDropdown'>
51
+              <div className='dropdown__subdropdown__item dropdown-item'>
52
+                <div className='dropdown__subdropdown__item__iconfile alignname'>
53
+                  <i className='fa fa-file-text-o' />
54
+                </div>
80 55
 
81
-              <div className='dropdown__subdropdown__item__textfile alignname'>
82
-                Documents Archivés
56
+                <div className='dropdown__subdropdown__item__textfile alignname'>
57
+                  Documents Archivés
58
+                </div>
83 59
               </div>
84
-            </div>
85
-            <div className='dropdown__subdropdown__item dropdown-item'>
86
-              <div className='dropdown__subdropdown__item__iconfile alignname'>
87
-                <i className='fa fa-file-text-o' />
88
-              </div>
89
-
90
-              <div className='dropdown__subdropdown__item__textfile alignname'>
91
-                Documents Supprimés
60
+              <div className='dropdown__subdropdown__item dropdown-item'>
61
+                <div className='dropdown__subdropdown__item__iconfile alignname'>
62
+                  <i className='fa fa-file-text-o' />
63
+                </div>
64
+
65
+                <div className='dropdown__subdropdown__item__textfile alignname'>
66
+                  Documents Supprimés
67
+                </div>
92 68
               </div>
93 69
             </div>
94
-          </div>
95
-          */}
70
+            */}
96 71
 
97
-        </li>
72
+          </li>
98 73
 
99
-        <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
74
+          <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
100 75
 
101
-          <div className='dropdown__icon'>
102
-            <i className='fa fa-signal dashboard-color' />
103
-          </div>
104
-
105
-          <div
106
-            className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown dropdown-toggle'
107
-            role='button'
108
-            data-toggle='dropdown'
109
-            aria-haspopup='true'
110
-            aria-expanded='false'
111
-          >
76
+            <div className='dropdown__icon'>
77
+              <i className='fa fa-signal dashboard-color' />
78
+            </div>
112 79
 
113
-            <div className='dropdown__title' id='navbarDropdown'>
114
-              <div className='dropdown__title__text'>
115
-                Tableau de bord
80
+            <div className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown'>
81
+              <div className='dropdown__title'>
82
+                <div className='dropdown__title__text'>
83
+                  Tableau de bord
84
+                </div>
116 85
               </div>
117 86
             </div>
118
-          </div>
119
-        </li>
87
+          </li>
120 88
 
121
-        <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
89
+          <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
122 90
 
123
-          <div className='dropdown__icon'>
124
-            <i className='fa fa-list-ul task-color' />
125
-          </div>
91
+            <div className='dropdown__icon'>
92
+              <i className='fa fa-list-ul task-color' />
93
+            </div>
126 94
 
127
-          <div
128
-            className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown dropdown-toggle'
129
-            role='button'
130
-            data-toggle='dropdown'
131
-            aria-haspopup='true'
132
-            aria-expanded='false'
133
-          >
95
+            <div className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown'>
134 96
 
135
-            <div className='dropdown__title' id='navbarDropdown'>
136
-              <div className='dropdown__title__text'>
137
-                Liste de tâches
97
+              <div className='dropdown__title' id='navbarDropdown'>
98
+                <div className='dropdown__title__text'>
99
+                  Liste de tâches
100
+                </div>
138 101
               </div>
139 102
             </div>
140
-          </div>
141
-        </li>
103
+          </li>
142 104
 
143
-        <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
105
+          <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
144 106
 
145
-          <div className='dropdown__icon'>
146
-            <i className='fa fa-folder-o docandfile-color' />
147
-          </div>
107
+            <div className='dropdown__icon'>
108
+              <i className='fa fa-folder-o docandfile-color' />
109
+            </div>
148 110
 
149
-          <div
150
-            className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown dropdown-toggle'
151
-            role='button'
152
-            data-toggle='dropdown'
153
-            aria-haspopup='true'
154
-            aria-expanded='false'
155
-          >
111
+            <div
112
+              className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown'
113
+              aria-haspopup='true'
114
+              aria-expanded='false'
115
+            >
156 116
 
157
-            <div className='dropdown__title' id='navbarDropdown'>
158
-              <div className='dropdown__title__text'>
159
-                Documents & fichiers
117
+              <div className='dropdown__title' id='navbarDropdown'>
118
+                <div className='dropdown__title__text'>
119
+                  Documents & fichiers
120
+                </div>
160 121
               </div>
161 122
             </div>
162
-          </div>
163
-        </li>
123
+          </li>
164 124
 
165
-        <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
125
+          <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
166 126
 
167
-          <div className='dropdown__icon'>
168
-            <i className='fa fa-comments talk-color' />
169
-          </div>
127
+            <div className='dropdown__icon'>
128
+              <i className='fa fa-comments talk-color' />
129
+            </div>
170 130
 
171
-          <div
172
-            className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown dropdown-toggle'
173
-            role='button'
174
-            data-toggle='dropdown'
175
-            aria-haspopup='true'
176
-            aria-expanded='false'
177
-          >
131
+            <div
132
+              className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown'
133
+              aria-haspopup='true'
134
+              aria-expanded='false'
135
+            >
178 136
 
179
-            <div className='dropdown__title' id='navbarDropdown'>
180
-              <div className='dropdown__title__text'>
181
-                Discussions
137
+              <div className='dropdown__title' id='navbarDropdown'>
138
+                <div className='dropdown__title__text'>
139
+                  Discussions
140
+                </div>
182 141
               </div>
183 142
             </div>
184
-          </div>
185
-        </li>
143
+          </li>
186 144
 
187
-        <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
145
+          <li className='sidebar__navigation__workspace__item__submenu__dropdown'>
188 146
 
189
-          <div className='dropdown__icon'>
190
-            <i className='fa fa-calendar calendar-color' />
191
-          </div>
147
+            <div className='dropdown__icon'>
148
+              <i className='fa fa-calendar calendar-color' />
149
+            </div>
192 150
 
193
-          <div
194
-            className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown dropdown-toggle'
195
-            role='button'
196
-            data-toggle='dropdown'
197
-            aria-haspopup='true'
198
-            aria-expanded='false'
199
-          >
151
+            <div
152
+              className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown'
153
+              aria-haspopup='true'
154
+              aria-expanded='false'
155
+            >
200 156
 
201
-            <div className='dropdown__title' id='navbarDropdown'>
202
-              <div className='dropdown__title__text'>
203
-                Calendrier
157
+              <div className='dropdown__title' id='navbarDropdown'>
158
+                <div className='dropdown__title__text'>
159
+                  Calendrier
160
+                </div>
204 161
               </div>
205 162
             </div>
206
-          </div>
207
-        </li>
208
-      </ul>
163
+          </li>
164
+        </ul>
165
+      </AnimateHeight>
209 166
     </li>
210 167
   )
211 168
 }
@@ -216,10 +173,12 @@ WorkspaceListItem.propTypes = {
216 173
   number: PropTypes.number.isRequired,
217 174
   name: PropTypes.string.isRequired,
218 175
   onClickTitle: PropTypes.func,
176
+  onClickAllContent: PropTypes.func,
219 177
   isOpen: PropTypes.bool
220 178
 }
221 179
 
222 180
 WorkspaceListItem.defaultProps = {
223 181
   onClickTitle: () => {},
182
+  onClickAllContent: () => {},
224 183
   isOpen: false
225 184
 }

+ 35 - 48
src/container/Sidebar.jsx View File

@@ -1,6 +1,10 @@
1 1
 import React from 'react'
2
-import {connect} from 'react-redux'
2
+import { connect } from 'react-redux'
3
+import { withRouter } from 'react-router'
3 4
 import WorkspaceListItem from '../component/Sidebar/WorkspaceListItem.jsx'
5
+import { getWorkspaceList } from '../action-creator.async.js'
6
+import { updateWorkspaceListIsOpen } from '../action-creator.sync.js'
7
+import { PAGE_NAME } from '../helper.js'
4 8
 
5 9
 class Sidebar extends React.Component {
6 10
   constructor (props) {
@@ -10,57 +14,40 @@ class Sidebar extends React.Component {
10 14
     }
11 15
   }
12 16
 
13
-  handleClickWorkspace = wsId => {
14
-    // console.log('sidebar handleClickWs')
15
-    this.setState(prev => ({firstWsOpen: !prev.firstWsOpen})) // delete this, purpose is only to test transition on click
16
-    // console.log('sidebar firstwsOpen toggled')
17
+  componentDidMount () {
18
+    const { user, workspaceList, dispatch } = this.props
19
+    user.id !== 0 && workspaceList.length === 0 && dispatch(getWorkspaceList(user.id))
17 20
   }
18 21
 
19
-  render () {
20
-    // <div className='sidebar-expandbar'>
21
-    //   <i className='fa fa-minus-square-o sidebar-expandbar__icon' />
22
-    // </div>
23
-    return (
24
-      <div className='sidebar d-none d-lg-table-cell'>
25
-        <nav className='sidebar__navigation navbar navbar-light'>
26
-          <ul className='sidebar__navigation__workspace navbar-nav collapse navbar-collapse'>
27
-            <WorkspaceListItem
28
-              number={1}
29
-              name='workspace 1 sympa'
30
-              isOpen={this.state.firstWsOpen}
31
-              onClickTitle={() => this.handleClickWorkspace(1)}
32
-            />
33
-
34
-            <li className='sidebar__navigation__workspace__item nav-item dropdown'>
35
-              <div className='sidebar__navigation__workspace__item__wrapper'>
36
-                <div className='sidebar__navigation__workspace__item__number'>
37
-                  02
38
-                </div>
39
-                <div className='sidebar__navigation__workspace__item__name'>
40
-                  Workspace 2
41
-                </div>
22
+  componentDidUpdate (prevProps) {
23
+    const { user, dispatch } = this.props
24
+    user.id !== 0 && prevProps.user.id !== user.id && dispatch(getWorkspaceList(user.id))
25
+  }
42 26
 
43
-                <div className='sidebar__navigation__workspace__item__icon'>
44
-                  <i className='fa fa-chevron-down' />
45
-                </div>
46
-              </div>
47
-            </li>
27
+  handleClickWorkspace = (wsId, newIsOpen) => this.props.dispatch(updateWorkspaceListIsOpen(wsId, newIsOpen))
48 28
 
49
-            <li className='sidebar__navigation__workspace__item nav-item dropdown'>
50
-              <div className='sidebar__navigation__workspace__item__wrapper'>
51
-                <div className='sidebar__navigation__workspace__item__number'>
52
-                  03
53
-                </div>
54
-                <div className='sidebar__navigation__workspace__item__name'>
55
-                  Workspace 3
56
-                </div>
29
+  handleClickAllContent = wsId => {
30
+    this.props.history.push(`${PAGE_NAME.WS_CONTENT}/${wsId}`)
31
+  }
57 32
 
58
-                <div className='sidebar__navigation__workspace__item__icon'>
59
-                  <i className='fa fa-chevron-down' />
60
-                </div>
61
-              </div>
62
-            </li>
33
+  render () {
34
+    const { workspaceList } = this.props
63 35
 
36
+    return (
37
+      <div className='sidebar d-none d-lg-table-cell'>
38
+        <nav className='sidebar__navigation'>
39
+          <ul className='sidebar__navigation__workspace'>
40
+            { workspaceList.map((ws, i) =>
41
+              <WorkspaceListItem
42
+                number={++i}
43
+                wsId={ws.id}
44
+                name={ws.title}
45
+                isOpen={ws.isOpen}
46
+                onClickTitle={() => this.handleClickWorkspace(ws.id, !ws.isOpen)}
47
+                onClickAllContent={this.handleClickAllContent}
48
+                key={ws.id}
49
+              />
50
+            )}
64 51
           </ul>
65 52
         </nav>
66 53
 
@@ -74,5 +61,5 @@ class Sidebar extends React.Component {
74 61
   }
75 62
 }
76 63
 
77
-const mapStateToProps = ({user}) => ({user})
78
-export default connect(mapStateToProps)(Sidebar)
64
+const mapStateToProps = ({ user, workspaceList }) => ({ user, workspaceList })
65
+export default withRouter(connect(mapStateToProps)(Sidebar))

+ 2 - 1
src/container/Tracim.jsx View File

@@ -6,7 +6,7 @@ import Sidebar from './Sidebar.jsx'
6 6
 import Login from './Login.jsx'
7 7
 import Dashboard from './Dashboard.jsx'
8 8
 import AccountPage from './AccountPage.jsx'
9
-import FlashMessage from './FlashMessage.jsx'
9
+// import FlashMessage from './FlashMessage.jsx'
10 10
 import WorkspaceContent from './WorkspaceContent.jsx'
11 11
 import {
12 12
   Route,
@@ -46,6 +46,7 @@ class Tracim extends React.Component {
46 46
               <SidebarWrapper locationPath={location.pathname}>
47 47
 
48 48
                 <PrivateRoute exact path={PAGE_NAME.HOME} component={WorkspaceContent} />
49
+                <PrivateRoute path={`${PAGE_NAME.WS_CONTENT}/:idws`} component={WorkspaceContent} />
49 50
                 <PrivateRoute exact path={PAGE_NAME.ACCOUNT} component={AccountPage} />
50 51
                 <PrivateRoute exact path={PAGE_NAME.DASHBOARD} component={Dashboard} />
51 52
 

+ 19 - 10
src/container/WorkspaceContent.jsx View File

@@ -12,7 +12,6 @@ import {
12 12
   getAppList,
13 13
   getWorkspaceContent
14 14
 } from '../action-creator.async.js'
15
-// import appDatabase from '../app/index.js'
16 15
 
17 16
 class WorkspaceContent extends React.Component {
18 17
   constructor (props) {
@@ -23,8 +22,22 @@ class WorkspaceContent extends React.Component {
23 22
   }
24 23
 
25 24
   componentDidMount () {
26
-    this.props.dispatch(getWorkspaceContent(/* this.props.workspace.id */1))
27
-    this.props.dispatch(getAppList())
25
+    const { workspaceList, app, match, dispatch } = this.props
26
+
27
+    if (match.params.idws !== undefined) dispatch(getWorkspaceContent(match.params.idws))
28
+    else if (workspaceList.length > 0) dispatch(getWorkspaceContent(workspaceList[0].id)) // load first ws if none specified
29
+
30
+    Object.keys(app).length === 0 && dispatch(getAppList())
31
+  }
32
+
33
+  componentDidUpdate (prevProps) {
34
+    const { workspaceList, match, dispatch } = this.props
35
+
36
+    if (prevProps.match.params.idws === match.params.idws) return
37
+
38
+    if (match.params.idws !== undefined) dispatch(getWorkspaceContent(match.params.idws))
39
+    // else load first ws if none specified
40
+    else if (match.params.idws === undefined && workspaceList.length > 0) dispatch(getWorkspaceContent(workspaceList[0].id))
28 41
   }
29 42
 
30 43
   handleClickContentItem = content => {
@@ -39,15 +52,13 @@ class WorkspaceContent extends React.Component {
39 52
         apiUrl: FETCH_CONFIG.apiUrl
40 53
       },
41 54
       loggedUser: user.isLoggedIn ? user : {},
42
-      content,
55
+      content
43 56
     })
44 57
   }
45 58
 
46 59
   render () {
47 60
     const { workspace, app } = this.props
48 61
 
49
-    // const AppContainer = (appDatabase.find(p => p.name === activeFileContent.type) || {container: '<div>unknow</div>'}).container
50
-
51 62
     return (
52 63
       <PageWrapper customeClass='workspace'>
53 64
         <PageTitle
@@ -80,9 +91,7 @@ class WorkspaceContent extends React.Component {
80 91
 
81 92
           <DropdownCreateButton customClass='workspace__content__button mb-5' />
82 93
 
83
-          <div id='appContainer'>
84
-            {/* activeFileContent.display && <AppContainer /> */}
85
-          </div>
94
+          <div id='appContainer' />
86 95
         </PageContent>
87 96
 
88 97
       </PageWrapper>
@@ -90,5 +99,5 @@ class WorkspaceContent extends React.Component {
90 99
   }
91 100
 }
92 101
 
93
-const mapStateToProps = ({ user, workspace, activeFileContent, app }) => ({ user, workspace, activeFileContent, app })
102
+const mapStateToProps = ({ user, workspace, workspaceList, activeFileContent, app }) => ({ user, workspace, workspaceList, activeFileContent, app })
94 103
 export default connect(mapStateToProps)(WorkspaceContent)

+ 3 - 2
src/css/Sidebar.styl View File

@@ -43,6 +43,8 @@ leftside()
43 43
   &__navigation
44 44
     padding 0
45 45
     &__workspace
46
+      padding-left 0
47
+      list-style none
46 48
       &__item
47 49
         width sidebar-width
48 50
         &__wrapper
@@ -75,13 +77,12 @@ leftside()
75 77
           padding 0
76 78
           width 100%
77 79
           background-color fourthColor
78
-          height 0 // height is handled is js
79 80
           overflow hidden
80
-          transition height 0.5s
81 81
           &__dropdown
82 82
             display flex
83 83
             align-items center
84 84
             border-top 1px solid darkBlue
85
+            cursor pointer
85 86
             &:nth-last-child(1)
86 87
               border-bottom 1px solid darkBlue
87 88
             &:hover

+ 2 - 2
src/helper.js View File

@@ -1,5 +1,5 @@
1 1
 export const FETCH_CONFIG = {
2
-  headers: {
2
+  header: {
3 3
     'Accept': 'application/json',
4 4
     'Content-Type': 'application/json'
5 5
   },
@@ -8,7 +8,7 @@ export const FETCH_CONFIG = {
8 8
 
9 9
 export const PAGE_NAME = {
10 10
   HOME: '/',
11
-  WS_CONTENT: '/',
11
+  WS_CONTENT: '/workspace',
12 12
   LOGIN: '/login',
13 13
   DASHBOARD: '/dashboard',
14 14
   ACCOUNT: '/account'

+ 1 - 7
src/reducer/app.js View File

@@ -1,12 +1,6 @@
1 1
 import { APP_LIST } from '../action-creator.sync.js'
2 2
 
3
-export default function app (state = {
4
-  name: '',
5
-  componentLeft: '',
6
-  componentRight: '',
7
-  customClass: '',
8
-  icon: ''
9
-}, action) {
3
+export default function app (state = {}, action) {
10 4
   switch (action.type) {
11 5
     case `Set/${APP_LIST}`:
12 6
       const rez = {}

+ 2 - 1
src/reducer/root.js View File

@@ -1,9 +1,10 @@
1 1
 import { combineReducers } from 'redux'
2 2
 import user from './user.js'
3 3
 import workspace from './workspace.js'
4
+import workspaceList from './workspaceList.js'
4 5
 import activeFileContent from './activeFileContent.js'
5 6
 import app from './app.js'
6 7
 
7
-const rootReducer = combineReducers({ user, workspace, activeFileContent, app })
8
+const rootReducer = combineReducers({ user, workspace, workspaceList, activeFileContent, app })
8 9
 
9 10
 export default rootReducer

+ 24 - 0
src/reducer/workspaceList.js View File

@@ -0,0 +1,24 @@
1
+import {
2
+  WORKSPACE_LIST
3
+} from '../action-creator.sync.js'
4
+
5
+export function workspaceList (state = [], action) {
6
+  switch (action.type) {
7
+    case `Update/${WORKSPACE_LIST}`:
8
+      return action.workspaceList.map(ws => ({
9
+        ...ws,
10
+        isOpen: false
11
+      }))
12
+
13
+    case `Update/${WORKSPACE_LIST}/isOpen`:
14
+      return state.map(ws => ws.id === action.workspaceId
15
+        ? {...ws, isOpen: action.isOpen}
16
+        : ws
17
+      )
18
+
19
+    default:
20
+      return state
21
+  }
22
+}
23
+
24
+export default workspaceList

+ 2 - 1
webpack.config.js View File

@@ -30,7 +30,8 @@ module.exports = {
30 30
   output: {
31 31
     path: path.resolve(__dirname, 'dist'),
32 32
     filename: 'tracim.app.entry.js',
33
-    pathinfo: !isProduction
33
+    pathinfo: !isProduction,
34
+    publicPath: '/'
34 35
   },
35 36
   devServer: {
36 37
     contentBase: path.join(__dirname, 'dist/'),