Ver código fonte

added dynamic sidebar + fixed relative url with webpack

Skylsmoi 6 anos atrás
pai
commit
d96a851971

+ 9 - 9
dist/index.html Ver arquivo

6
     <title>Tracim</title>
6
     <title>Tracim</title>
7
     <link rel='shortcut icon' href='favicon.ico'>
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
     <link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
10
     <link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
11
     <!--
11
     <!--
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">
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
   </head>
15
   </head>
16
   <body>
16
   <body>
17
     <div id='content'></div>
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
     <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
23
     <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
25
     <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
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
     <script type='text/javascript'>
35
     <script type='text/javascript'>
36
       const pageHtml = new appPageHtml()
36
       const pageHtml = new appPageHtml()

+ 2 - 0
jsonserver/server.js Ver arquivo

29
 
29
 
30
 server.get('/user/is_logged_in', (req, res) => res.jsonp(jsonDb.user_logged))
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
 server.get('/workspace/:id', (req, res) => res.jsonp(jsonDb.workspace_detail))
34
 server.get('/workspace/:id', (req, res) => res.jsonp(jsonDb.workspace_detail))
33
 
35
 
34
 server.get('/workspace/:idws/content/:idc', (req, res) => {
36
 server.get('/workspace/:idws/content/:idc', (req, res) => {

+ 20 - 1
jsonserver/static_db.json Ver arquivo

259
         "hour": "09h59"
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 Ver arquivo

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

+ 28 - 18
src/action-creator.async.js Ver arquivo

7
   updateUserData,
7
   updateUserData,
8
   WORKSPACE,
8
   WORKSPACE,
9
   updateWorkspaceData,
9
   updateWorkspaceData,
10
+  WORKSPACE_LIST,
11
+  updateWorkspaceListData,
10
   APP_LIST,
12
   APP_LIST,
11
   setAppList
13
   setAppList
12
 } from './action-creator.sync.js'
14
 } from './action-creator.sync.js'
58
 }
60
 }
59
 
61
 
60
 export const userLogin = (login, password, rememberMe) => async dispatch => {
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
   const fetchUserLogin = await fetchWrapper({
63
   const fetchUserLogin = await fetchWrapper({
68
-    url: 'http://localhost:3001/user/login',
64
+    url: `${FETCH_CONFIG.apiUrl}/user/login`,
69
     param: {
65
     param: {
70
-      ...FETCH_CONFIG,
66
+      ...FETCH_CONFIG.header,
71
       method: 'POST',
67
       method: 'POST',
72
-      body: jsonBody
68
+      body: JSON.stringify({
69
+        login,
70
+        password,
71
+        remember_me: rememberMe
72
+      })
73
     },
73
     },
74
     actionName: USER_LOGIN,
74
     actionName: USER_LOGIN,
75
     dispatch
75
     dispatch
79
 
79
 
80
 export const getIsUserConnected = () => async dispatch => {
80
 export const getIsUserConnected = () => async dispatch => {
81
   const fetchUserLogged = await fetchWrapper({
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
     actionName: USER_CONNECTED,
84
     actionName: USER_CONNECTED,
85
     dispatch
85
     dispatch
86
   })
86
   })
89
 
89
 
90
 export const updateUserLang = newLang => async dispatch => {
90
 export const updateUserLang = newLang => async dispatch => {
91
   const fetchUpdateUserLang = await fetchWrapper({
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
     actionName: USER_DATA,
94
     actionName: USER_DATA,
95
     dispatch
95
     dispatch
96
   })
96
   })
100
 // export const testResponseNoData = () => async dispatch => {
100
 // export const testResponseNoData = () => async dispatch => {
101
 //   const fetchResponseNoData = await fetchWrapper({
101
 //   const fetchResponseNoData = await fetchWrapper({
102
 //     url: 'http://localhost:3001/deletenodata',
102
 //     url: 'http://localhost:3001/deletenodata',
103
-//     param: {...FETCH_CONFIG, method: 'DELETE'},
103
+//     param: {...FETCH_CONFIG.header, method: 'DELETE'},
104
 //     actionName: 'TestNoData',
104
 //     actionName: 'TestNoData',
105
 //     dispatch
105
 //     dispatch
106
 //   })
106
 //   })
107
 //   console.log('jsonResponseNoData', fetchResponseNoData)
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
 export const getWorkspaceContent = workspaceId => async dispatch => {
120
 export const getWorkspaceContent = workspaceId => async dispatch => {
111
   const fetchGetWorkspaceContent = await fetchWrapper({
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
     actionName: WORKSPACE,
124
     actionName: WORKSPACE,
115
     dispatch
125
     dispatch
116
   })
126
   })
119
 
129
 
120
 export const getAppList = () => async dispatch => {
130
 export const getAppList = () => async dispatch => {
121
   const fetchGetAppList = await fetchWrapper({
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
     actionName: APP_LIST,
134
     actionName: APP_LIST,
125
     dispatch
135
     dispatch
126
   })
136
   })

+ 4 - 0
src/action-creator.sync.js Ver arquivo

8
 export const WORKSPACE = 'Workspace'
8
 export const WORKSPACE = 'Workspace'
9
 export const updateWorkspaceData = workspace => ({ type: `Update/${WORKSPACE}`, workspace })
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
 export const FILE_CONTENT = 'FileContent'
15
 export const FILE_CONTENT = 'FileContent'
12
 export const setActiveFileContent = file => ({ type: `Set/${FILE_CONTENT}/Active`, file })
16
 export const setActiveFileContent = file => ({ type: `Set/${FILE_CONTENT}/Active`, file })
13
 export const hideActiveFileContent = () => ({ type: `Set/${FILE_CONTENT}/Hide` })
17
 export const hideActiveFileContent = () => ({ type: `Set/${FILE_CONTENT}/Hide` })

+ 109 - 150
src/component/Sidebar/WorkspaceListItem.jsx Ver arquivo

1
 import React from 'react'
1
 import React from 'react'
2
 // import classnames from 'classnames'
2
 // import classnames from 'classnames'
3
 import PropTypes from 'prop-types'
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
   return (
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
         <div className='sidebar__navigation__workspace__item__number'>
15
         <div className='sidebar__navigation__workspace__item__number'>
36
           {pad(props.number)}
16
           {pad(props.number)}
37
         </div>
17
         </div>
43
         <div className='sidebar__navigation__workspace__item__icon'>
23
         <div className='sidebar__navigation__workspace__item__icon'>
44
           <i className='fa fa-chevron-down' />
24
           <i className='fa fa-chevron-down' />
45
         </div>
25
         </div>
46
-
47
       </div>
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
               </div>
46
               </div>
71
             </div>
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
               </div>
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
               </div>
68
               </div>
93
             </div>
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
               </div>
85
               </div>
117
             </div>
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
               </div>
101
               </div>
139
             </div>
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
               </div>
121
               </div>
161
             </div>
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
               </div>
141
               </div>
183
             </div>
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
               </div>
161
               </div>
205
             </div>
162
             </div>
206
-          </div>
207
-        </li>
208
-      </ul>
163
+          </li>
164
+        </ul>
165
+      </AnimateHeight>
209
     </li>
166
     </li>
210
   )
167
   )
211
 }
168
 }
216
   number: PropTypes.number.isRequired,
173
   number: PropTypes.number.isRequired,
217
   name: PropTypes.string.isRequired,
174
   name: PropTypes.string.isRequired,
218
   onClickTitle: PropTypes.func,
175
   onClickTitle: PropTypes.func,
176
+  onClickAllContent: PropTypes.func,
219
   isOpen: PropTypes.bool
177
   isOpen: PropTypes.bool
220
 }
178
 }
221
 
179
 
222
 WorkspaceListItem.defaultProps = {
180
 WorkspaceListItem.defaultProps = {
223
   onClickTitle: () => {},
181
   onClickTitle: () => {},
182
+  onClickAllContent: () => {},
224
   isOpen: false
183
   isOpen: false
225
 }
184
 }

+ 35 - 48
src/container/Sidebar.jsx Ver arquivo

1
 import React from 'react'
1
 import React from 'react'
2
-import {connect} from 'react-redux'
2
+import { connect } from 'react-redux'
3
+import { withRouter } from 'react-router'
3
 import WorkspaceListItem from '../component/Sidebar/WorkspaceListItem.jsx'
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
 class Sidebar extends React.Component {
9
 class Sidebar extends React.Component {
6
   constructor (props) {
10
   constructor (props) {
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
           </ul>
51
           </ul>
65
         </nav>
52
         </nav>
66
 
53
 
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 Ver arquivo

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

+ 19 - 10
src/container/WorkspaceContent.jsx Ver arquivo

12
   getAppList,
12
   getAppList,
13
   getWorkspaceContent
13
   getWorkspaceContent
14
 } from '../action-creator.async.js'
14
 } from '../action-creator.async.js'
15
-// import appDatabase from '../app/index.js'
16
 
15
 
17
 class WorkspaceContent extends React.Component {
16
 class WorkspaceContent extends React.Component {
18
   constructor (props) {
17
   constructor (props) {
23
   }
22
   }
24
 
23
 
25
   componentDidMount () {
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
   handleClickContentItem = content => {
43
   handleClickContentItem = content => {
39
         apiUrl: FETCH_CONFIG.apiUrl
52
         apiUrl: FETCH_CONFIG.apiUrl
40
       },
53
       },
41
       loggedUser: user.isLoggedIn ? user : {},
54
       loggedUser: user.isLoggedIn ? user : {},
42
-      content,
55
+      content
43
     })
56
     })
44
   }
57
   }
45
 
58
 
46
   render () {
59
   render () {
47
     const { workspace, app } = this.props
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
     return (
62
     return (
52
       <PageWrapper customeClass='workspace'>
63
       <PageWrapper customeClass='workspace'>
53
         <PageTitle
64
         <PageTitle
80
 
91
 
81
           <DropdownCreateButton customClass='workspace__content__button mb-5' />
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
         </PageContent>
95
         </PageContent>
87
 
96
 
88
       </PageWrapper>
97
       </PageWrapper>
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
 export default connect(mapStateToProps)(WorkspaceContent)
103
 export default connect(mapStateToProps)(WorkspaceContent)

+ 3 - 2
src/css/Sidebar.styl Ver arquivo

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

+ 2 - 2
src/helper.js Ver arquivo

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

+ 1 - 7
src/reducer/app.js Ver arquivo

1
 import { APP_LIST } from '../action-creator.sync.js'
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
   switch (action.type) {
4
   switch (action.type) {
11
     case `Set/${APP_LIST}`:
5
     case `Set/${APP_LIST}`:
12
       const rez = {}
6
       const rez = {}

+ 2 - 1
src/reducer/root.js Ver arquivo

1
 import { combineReducers } from 'redux'
1
 import { combineReducers } from 'redux'
2
 import user from './user.js'
2
 import user from './user.js'
3
 import workspace from './workspace.js'
3
 import workspace from './workspace.js'
4
+import workspaceList from './workspaceList.js'
4
 import activeFileContent from './activeFileContent.js'
5
 import activeFileContent from './activeFileContent.js'
5
 import app from './app.js'
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
 export default rootReducer
10
 export default rootReducer

+ 24 - 0
src/reducer/workspaceList.js Ver arquivo

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 Ver arquivo

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