Преглед изворни кода

merge master into alexicauvin

AlexiCauvin пре 6 година
родитељ
комит
ce2d4b0523

+ 3 - 0
jsonserver/server.js Прегледај датотеку

@@ -24,6 +24,9 @@ server.post('/user/login', (req, res) => {
24 24
   if (req.body.login !== '' && req.body.password !== '') return res.jsonp(jsonDb.user_logged)
25 25
   else return res.jsonp('error')
26 26
 })
27
+
28
+server.get('/user/is_logged_in', (req, res) => res.jsonp(jsonDb.user_logged))
29
+
27 30
 server.get('/workspace/:id', (req, res) => res.jsonp(jsonDb.workspace_detail))
28 31
 
29 32
 server.use(router)

+ 14 - 11
jsonserver/static_db.json Прегледај датотеку

@@ -1,11 +1,13 @@
1 1
 {
2
-  "login": true,
3 2
   "user_logged": {
4
-    "id": 5,
5
-    "username": "Smoi",
6
-    "firstname": "Côme",
7
-    "lastname": "Stoilenom",
8
-    "email": "osef@algoo.fr"
3
+    "logged": true,
4
+    "user": {
5
+      "id": 5,
6
+      "username": "Smoi",
7
+      "firstname": "Côme",
8
+      "lastname": "Stoilenom",
9
+      "email": "osef@algoo.fr"
10
+    }
9 11
   },
10 12
   "workspace_detail": {
11 13
     "id": 1,
@@ -16,7 +18,8 @@
16 18
         "id": 1,
17 19
         "title": "La programmation fonctionnelle",
18 20
         "type": "pageHtml",
19
-        "status": "validated"
21
+        "status": "validated",
22
+        "text": "<h1>Mon titre nul</h1>Je suis le contenu de cette fameuse <b>page HTML</b><br /> sur la programmation fonctionnelle"
20 23
       },
21 24
       {
22 25
         "id": 2,
@@ -26,25 +29,25 @@
26 29
       },
27 30
       {
28 31
         "id": 6,
29
-        "title": "La prommation fonctionnelle est-elle vraiment utile ?",
32
+        "title": "Une photo moche",
30 33
         "type": "file",
31 34
         "status": "current"
32 35
       },
33 36
       {
34 37
         "id": 10,
35
-        "title": "La prommation fonctionnelle est-elle vraiment utile ?",
38
+        "title": "le README.md d'un random repo github",
36 39
         "type": "pageMarkdown",
37 40
         "status": "current"
38 41
       },
39 42
       {
40 43
         "id": 7,
41
-        "title": "La prommation fonctionnelle est-elle vraiment utile ?",
44
+        "title": "Une liste de truc à faire que je ferai jamais",
42 45
         "type": "task",
43 46
         "status": "current"
44 47
       },
45 48
       {
46 49
         "id": 8,
47
-        "title": "La prommation fonctionnelle est-elle vraiment utile ?",
50
+        "title": "Ça, ça marche pas. Merci de le fix",
48 51
         "type": "issue",
49 52
         "status": "current"
50 53
       },

+ 1 - 1
package.json Прегледај датотеку

@@ -7,7 +7,7 @@
7 7
     "mockapi": "node jsonserver/server.js",
8 8
     "servdev": "NODE_ENV=development webpack-dev-server --watch --colors --inline --hot --progress",
9 9
     "servdevwindoz": "webpack-dev-server --watch --colors --inline --hot --progress",
10
-    "servdev-dashboard": "NODE_ENV=development webpack-dashboard -- webpack-dev-server --watch --colors --inline --hot --progress",
10
+    "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -- webpack-dev-server --watch --colors --inline --hot --progress",
11 11
     "build": "NODE_ENV=production webpack -p",
12 12
     "test": "echo \"Error: no test specified\" && exit 1"
13 13
   },

+ 2 - 2
src/action-creator.async.js Прегледај датотеку

@@ -75,9 +75,9 @@ export const userLogin = (login, password, rememberMe) => async dispatch => {
75 75
   if (fetchUserLogin.status === 200) dispatch(updateUserConnected(fetchUserLogin.json))
76 76
 }
77 77
 
78
-export const getUserConnected = () => async dispatch => {
78
+export const getIsUserConnected = () => async dispatch => {
79 79
   const fetchUserLogged = await fetchWrapper({
80
-    url: 'http://localhost:3001/user_logged',
80
+    url: 'http://localhost:3001/user/is_logged_in',
81 81
     param: {...FETCH_CONFIG, method: 'GET'},
82 82
     actionName: USER_CONNECTED,
83 83
     dispatch

+ 4 - 0
src/action-creator.sync.js Прегледај датотеку

@@ -7,3 +7,7 @@ export const updateUserData = userData => ({ type: `Update/${USER_DATA}`, data:
7 7
 
8 8
 export const WORKSPACE = 'Workspace'
9 9
 export const updateWorkspaceData = workspace => ({ type: `Update/${WORKSPACE}`, workspace })
10
+
11
+export const FILE_CONTENT = 'FileContent'
12
+export const setActiveFileContent = file => ({ type: `Set/${FILE_CONTENT}/Active`, file })
13
+export const hideActiveFileContent = () => ({ type: `Set/${FILE_CONTENT}/Hide` })

+ 56 - 0
src/component/Workspace/FileContentViewer.jsx Прегледај датотеку

@@ -0,0 +1,56 @@
1
+import React from 'react'
2
+import PropTypes from 'prop-types'
3
+import PopinFixed from '../common/PopinFixed/PopinFixed'
4
+import PopinFixedHeader from '../common/PopinFixed/PopinFixedHeader.jsx'
5
+import PopinFixedOption from '../common/PopinFixed/PopinFixedOption.jsx'
6
+import PopinFixedContent from '../common/PopinFixed/PopinFixedContent.jsx'
7
+import PageHtml from './FileType/PageHtml.jsx'
8
+import Thread from './FileType/Thread.jsx'
9
+import Timeline from '../Timeline.jsx'
10
+import { FILE_TYPE } from '../../helper.js'
11
+
12
+const FileContentViewer = props => {
13
+  const { customClass, icon } = FILE_TYPE.find(f => f.name === props.file.type) || {customClass: '', icon: ''}
14
+
15
+  const [leftPart, rightPart] = (() => {
16
+    switch (props.file.type) {
17
+      case FILE_TYPE[0].name: // pageHtml
18
+        return [
19
+          <PageHtml version={props.file.version} text={props.file.text} />,
20
+          <Timeline customClass={`${customClass}__contentpage`} />
21
+        ]
22
+      case FILE_TYPE[3].name: // thread
23
+        return [
24
+          <Thread />
25
+        ]
26
+    }
27
+  })()
28
+
29
+  return (
30
+    <PopinFixed customClass={`${customClass}`}>
31
+      <PopinFixedHeader
32
+        customClass={`${customClass}`}
33
+        icon={icon}
34
+        name={props.file.title}
35
+        onClickCloseBtn={props.onClose}
36
+      />
37
+
38
+      <PopinFixedOption customClass={`${customClass}`} />
39
+
40
+      <PopinFixedContent customClass={`${customClass}__contentpage`}>
41
+        { leftPart }
42
+        { rightPart }
43
+      </PopinFixedContent>
44
+    </PopinFixed>
45
+  )
46
+}
47
+
48
+export default FileContentViewer
49
+
50
+FileContentViewer.PropTypes = {
51
+  file: PropTypes.shape({
52
+    type: PropTypes.oneOf(FILE_TYPE.map(f => f.name)).isRequired,
53
+    title: PropTypes.string.isRequired
54
+  }).isRequired,
55
+  onClose: PropTypes.func.isRequired
56
+}

+ 1 - 16
src/component/Workspace/FileItem.jsx Прегледај датотеку

@@ -4,22 +4,7 @@ import classnames from 'classnames'
4 4
 import { FILE_TYPE } from '../../helper.js'
5 5
 
6 6
 const FileItem = props => {
7
-  const iconType = (() => {
8
-    switch (props.type) {
9
-      case FILE_TYPE.PAGE_HTML:
10
-        return 'fa fa-file-word-o'
11
-      case FILE_TYPE.PAGE_MARKDOWN:
12
-        return 'fa fa-file-code-o'
13
-      case FILE_TYPE.FILE:
14
-        return 'fa fa-file-image-o'
15
-      case FILE_TYPE.THREAD:
16
-        return 'fa fa-comments-o'
17
-      case FILE_TYPE.TASK:
18
-        return 'fa fa-list-ul'
19
-      case FILE_TYPE.ISSUE:
20
-        return 'fa fa-ticket'
21
-    }
22
-  })()
7
+  const iconType = (FILE_TYPE.find(f => f.name === props.type) || {icon: ''}).icon
23 8
 
24 9
   const iconStatus = (() => {
25 10
     switch (props.status) {

+ 16 - 0
src/component/Workspace/FileType/PageHtml.jsx Прегледај датотеку

@@ -0,0 +1,16 @@
1
+import React from 'react'
2
+
3
+const PageHtml = props => {
4
+  return (
5
+    <div className='wsFilePageHtml__contentpage__textnote'>
6
+      <div className='wsFilePageHtml__contentpage__textnote__latestversion'>
7
+        { props.version }
8
+      </div>
9
+      <div className='wsFilePageHtml__contentpage__textnote__text'>
10
+        { props.text }
11
+      </div>
12
+    </div>
13
+  )
14
+}
15
+
16
+export default PageHtml

src/container/Thread.jsx → src/component/Workspace/FileType/Thread.jsx Прегледај датотеку

@@ -1,6 +1,6 @@
1 1
 import React, { Component } from 'react'
2 2
 import classnames from 'classnames'
3
-import imgProfil from '../img/imgProfil.png'
3
+import imgProfil from '../../../img/imgProfil.png'
4 4
 
5 5
 class Thread extends Component {
6 6
   render () {

+ 0 - 7
src/component/common/Card/Card.jsx Прегледај датотеку

@@ -14,13 +14,6 @@ const Card = props => {
14 14
 export default Card
15 15
 
16 16
 Card.propTypes = {
17
-  // check https://stackoverflow.com/questions/27366077/only-allow-children-of-a-specific-type-in-a-react-component
18
-  // children: PropTypes.arrayOf( // children is an array
19
-  //   PropTypes.shape({ // of objects
20
-  //     type: PropTypes.oneOf([CardHeader, CardBody]) // that as an attribute 'type' equals to CardHeader or CardBody
21
-  //   })
22
-  // ),
23
-
24 17
   // from http://www.mattzabriskie.com/blog/react-validating-children
25 18
   children: PropTypes.arrayOf((children, key, componentName /* , location, propFullName */) => {
26 19
     if (

+ 19 - 0
src/component/common/PopinFixed/PopinFixed.jsx Прегледај датотеку

@@ -1,6 +1,9 @@
1 1
 import React from 'react'
2 2
 import classnames from 'classnames'
3 3
 import PropTypes from 'prop-types'
4
+import PopinFixedHeader from './PopinFixedHeader.jsx'
5
+import PopinFixedOption from './PopinFixedOption.jsx'
6
+import PopinFixedContent from './PopinFixedContent.jsx'
4 7
 
5 8
 const PopinFixed = props => {
6 9
   return (
@@ -17,6 +20,22 @@ PopinFixed.propTypes = {
17 20
   visible: PropTypes.bool
18 21
 }
19 22
 
23
+PopinFixed.propTypes = {
24
+  // from http://www.mattzabriskie.com/blog/react-validating-children
25
+  children: PropTypes.arrayOf((children, key, componentName /* , location, propFullName */) => {
26
+    if (
27
+      children.length > 3 ||
28
+      children[0].type !== PopinFixedHeader ||
29
+      children[1].type !== PopinFixedOption ||
30
+      children[2].type !== PopinFixedContent
31
+    ) {
32
+      return new Error(`PropType Error: childrens of ${componentName} must be: 1 PopinFixedHeader, 1 PopinFixedOption and 1 PopinFixedContent.`)
33
+    }
34
+  }).isRequired,
35
+  customClass: PropTypes.string,
36
+  visible: PropTypes.bool
37
+}
38
+
20 39
 PopinFixed.defaultProps = {
21 40
   customClass: '',
22 41
   visible: true

+ 29 - 8
src/component/common/PopinFixed/PopinFixedContent.jsx Прегледај датотеку

@@ -1,21 +1,42 @@
1 1
 import React from 'react'
2 2
 import classnames from 'classnames'
3 3
 import PropTypes from 'prop-types'
4
+import PageHtml from '../../Workspace/FileType/PageHtml.jsx'
5
+import Thread from '../../Workspace/FileType/Thread.jsx'
6
+import Timeline from '../../Timeline.jsx'
4 7
 
5 8
 const PopinFixedContent = props => {
6
-  return (
7
-    <div className={classnames('wsFileGeneric__contentpage', `${props.customClass}`)}>
8
-      <div className={classnames('wsFileGeneric__textnote', `${props.customClass}__textnote`)} />
9
+  return props.children.length === 2
10
+    ? (
11
+      <div className={classnames('wsFileGeneric__contentpage', `${props.customClass}`)}>
12
+        {props.children[0]}
9 13
 
10
-      <div className={classnames('wsFileGeneric__wrapper', `${props.customClass}__wrapper`)}>
11
-        {props.children}
14
+        <div className={classnames('wsFileGeneric__wrapper', `${props.customClass}__wrapper`)}>
15
+          {props.children[1]}
16
+        </div>
12 17
       </div>
13
-    </div>
14
-  )
18
+    )
19
+    : (
20
+      <div className={classnames('wsFileGeneric__contentpage', `${props.customClass}`)}>
21
+        { props.children }
22
+      </div>
23
+    )
15 24
 }
16 25
 
17 26
 export default PopinFixedContent
18 27
 
19 28
 PopinFixedContent.propTypes = {
20
-  customClass: PropTypes.string
29
+  customClass: PropTypes.string,
30
+  // from http://www.mattzabriskie.com/blog/react-validating-children
31
+  children: PropTypes.arrayOf((children, key, componentName /* , location, propFullName */) => {
32
+    if (
33
+      (Array.isArray(children.length) && (
34
+        children.length > 2 ||
35
+        (children.length === 2 && children.some(c => ![PageHtml, Thread, Timeline].includes(c.type)))
36
+      )) ||
37
+      (children.type === Timeline)
38
+    ) {
39
+      return new Error(`PropType Error: childrens of ${componentName} must be one of [PageHtml, Thread, Timeline, undefined].`)
40
+    }
41
+  }).isRequired
21 42
 }

+ 5 - 1
src/component/common/PopinFixed/PopinFixedHeader.jsx Прегледај датотеку

@@ -17,7 +17,10 @@ const PopinFixedHeader = props => {
17 17
         <i className='fa fa-pencil' />
18 18
       </div>
19 19
 
20
-      <div className={classnames('wsFileGeneric__header__close', `${props.customClass}__header__close`)}>
20
+      <div
21
+        className={classnames('wsFileGeneric__header__close', `${props.customClass}__header__close`)}
22
+        onClick={props.onClickCloseBtn}
23
+      >
21 24
         <i className='fa fa-times' />
22 25
       </div>
23 26
     </div>
@@ -28,6 +31,7 @@ export default PopinFixedHeader
28 31
 
29 32
 PopinFixedHeader.propTypes = {
30 33
   icon: PropTypes.string.isRequired,
34
+  onClickCloseBtn: PropTypes.func.isRequired,
31 35
   customClass: PropTypes.string,
32 36
   name: PropTypes.string
33 37
 }

+ 73 - 69
src/container/Login.jsx Прегледај датотеку

@@ -1,5 +1,6 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3
+import { Redirect } from 'react-router'
3 4
 import LoginLogo from '../component/Login/LoginLogo.jsx'
4 5
 import LoginLogoImg from '../img/logoTracimWhite.svg'
5 6
 import { userLogin } from '../action-creator.async.js'
@@ -25,85 +26,88 @@ class Login extends React.Component {
25 26
   handleChangePassword = e => this.setState({inputPassword: e.target.value})
26 27
   handleChangeRememberMe = () => this.setState(prev => ({inputRememberMe: !prev.inputRememberMe}))
27 28
 
28
-  handleClickSubmit = () => {
29
+  handleClickSubmit = async () => {
29 30
     const { history, dispatch } = this.props
30 31
     const { inputLogin, inputPassword, inputRememberMe } = this.state
31 32
 
32
-    dispatch(userLogin(inputLogin, inputPassword, inputRememberMe))
33
-    .then(() => history.push('/'))
33
+    await dispatch(userLogin(inputLogin, inputPassword, inputRememberMe))
34
+    history.push('/')
34 35
   }
35 36
 
36 37
   render () {
37
-    return (
38
-      <section className='loginpage'>
39
-        <div className='container-fluid'>
40
-
41
-          <LoginLogo customClass='loginpage__logo' logoSrc={LoginLogoImg} />
42
-
43
-          <div className='row justify-content-center'>
44
-            <div className='col-12 col-sm-11 col-md-8 col-lg-6 col-xl-5'>
45
-
46
-              <Card customClass='loginpage__connection'>
47
-                <CardHeader customClass='connection__header text-center'>{'Connexion'}</CardHeader>
48
-
49
-                <CardBody formClass='connection__form'>
50
-                  <InputGroupText
51
-                    parentClassName='connection__form__groupemail'
52
-                    customClass='mb-3 mt-4'
53
-                    icon='fa-envelope-open-o'
54
-                    type='email'
55
-                    placeHolder='Adresse Email'
56
-                    invalidMsg='Email invalide.'
57
-                    value={this.state.inputLogin}
58
-                    onChange={this.handleChangeLogin}
59
-                  />
60
-
61
-                  <InputGroupText
62
-                    parentClassName='connection__form__groupepw'
63
-                    customClass=''
64
-                    icon='fa-lock'
65
-                    type='password'
66
-                    placeHolder='Mot de passe'
67
-                    invalidMsg='Mot de passe invalide.'
68
-                    value={this.state.inputPassword}
69
-                    onChange={this.handleChangePassword}
70
-                  />
71
-
72
-                  <div className='row mt-4 mb-4'>
73
-                    <div className='col-12 col-sm-6 col-md-6 col-lg-6 col-xl-6'>
74
-                      <InputCheckbox
75
-                        parentClassName='connection__form__rememberme'
76
-                        customClass=''
77
-                        label='Se souvenir de moi'
78
-                        checked={this.state.inputRememberMe}
79
-                        onChange={this.handleChangeRememberMe}
80
-                      />
38
+    if (this.props.user.isLoggedIn) return <Redirect to={{pathname: '/'}} />
39
+    else {
40
+      return (
41
+        <section className='loginpage'>
42
+          <div className='container-fluid'>
43
+
44
+            <LoginLogo customClass='loginpage__logo' logoSrc={LoginLogoImg} />
45
+
46
+            <div className='row justify-content-center'>
47
+              <div className='col-12 col-sm-11 col-md-8 col-lg-6 col-xl-5'>
48
+
49
+                <Card customClass='loginpage__connection'>
50
+                  <CardHeader customClass='connection__header text-center'>{'Connexion'}</CardHeader>
51
+
52
+                  <CardBody formClass='connection__form'>
53
+                    <InputGroupText
54
+                      parentClassName='connection__form__groupemail'
55
+                      customClass='mb-3 mt-4'
56
+                      icon='fa-envelope-open-o'
57
+                      type='email'
58
+                      placeHolder='Adresse Email'
59
+                      invalidMsg='Email invalide.'
60
+                      value={this.state.inputLogin}
61
+                      onChange={this.handleChangeLogin}
62
+                    />
63
+
64
+                    <InputGroupText
65
+                      parentClassName='connection__form__groupepw'
66
+                      customClass=''
67
+                      icon='fa-lock'
68
+                      type='password'
69
+                      placeHolder='Mot de passe'
70
+                      invalidMsg='Mot de passe invalide.'
71
+                      value={this.state.inputPassword}
72
+                      onChange={this.handleChangePassword}
73
+                    />
74
+
75
+                    <div className='row mt-4 mb-4'>
76
+                      <div className='col-12 col-sm-6 col-md-6 col-lg-6 col-xl-6'>
77
+                        <InputCheckbox
78
+                          parentClassName='connection__form__rememberme'
79
+                          customClass=''
80
+                          label='Se souvenir de moi'
81
+                          checked={this.state.inputRememberMe}
82
+                          onChange={this.handleChangeRememberMe}
83
+                        />
84
+                      </div>
85
+
86
+                      <div className='col-12 col-sm-6 col-md-6 col-lg-6 col-xl-6 text-sm-right'>
87
+                        <LoginBtnForgotPw
88
+                          customClass='connection__form__pwforgot'
89
+                          label='Mot de passe oublié ?'
90
+                        />
91
+                      </div>
81 92
                     </div>
82 93
 
83
-                    <div className='col-12 col-sm-6 col-md-6 col-lg-6 col-xl-6 text-sm-right'>
84
-                      <LoginBtnForgotPw
85
-                        customClass='connection__form__pwforgot'
86
-                        label='Mot de passe oublié ?'
87
-                      />
88
-                    </div>
89
-                  </div>
90
-
91
-                  <Button
92
-                    htmlType='button'
93
-                    bootstrapType='primary'
94
-                    customClass='connection__form__btnsubmit'
95
-                    label='Connexion'
96
-                    onClick={this.handleClickSubmit}
97
-                  />
98
-                </CardBody>
99
-              </Card>
100
-
94
+                    <Button
95
+                      htmlType='button'
96
+                      bootstrapType='primary'
97
+                      customClass='connection__form__btnsubmit'
98
+                      label='Connexion'
99
+                      onClick={this.handleClickSubmit}
100
+                    />
101
+                  </CardBody>
102
+                </Card>
103
+
104
+              </div>
101 105
             </div>
102
-          </div>
103 106
 
104
-        </div>
105
-      </section>
106
-    )
107
+          </div>
108
+        </section>
109
+      )
110
+    }
107 111
   }
108 112
 }
109 113
 

+ 0 - 16
src/container/Page.jsx Прегледај датотеку

@@ -1,16 +0,0 @@
1
-import React from 'react'
2
-import { connect } from 'react-redux'
3
-
4
-class Page extends React.Component {
5
-  render () {
6
-    const { user } = this.props
7
-    return (
8
-      <div>
9
-        Page ! user Logged in : {user.isLoggedIn.toString()}
10
-      </div>
11
-    )
12
-  }
13
-}
14
-
15
-const mapStateToProps = ({ user }) => ({ user })
16
-export default connect(mapStateToProps)(Page)

+ 0 - 73
src/container/PageText.jsx Прегледај датотеку

@@ -1,73 +0,0 @@
1
-import React, { Component } from 'react'
2
-import PopinFixed from '../component/common/PopinFixed/PopinFixed'
3
-import PopinFixedHeader from '../component/common/PopinFixed/PopinFixedHeader.jsx'
4
-import PopinFixedOption from '../component/common/PopinFixed/PopinFixedOption.jsx'
5
-import PopinFixedContent from '../component/common/PopinFixed/PopinFixedContent.jsx'
6
-import Timeline from '../component/Timeline.jsx'
7
-
8
-class PageText extends Component {
9
-  render () {
10
-    return (
11
-      <PopinFixed customClass={'wsFileText'}>
12
-        <PopinFixedHeader
13
-          customClass='wsFileText'
14
-          icon='fa fa-file-word-o'
15
-          name='Facture 57840 - Jean-michel Chevalier - 04/09/2017'
16
-        />
17
-
18
-        <PopinFixedOption customClass={'wsFileText'} />
19
-
20
-        <PopinFixedContent customClass={'wsFileText__contentpage'}>
21
-          <Timeline customClass={'wsFileText__contentpage'} />
22
-        </PopinFixedContent>
23
-      </PopinFixed>
24
-    )
25
-  }
26
-}
27
-
28
-export default PageText
29
-
30
-/*
31
-      <div className={classnames('wsFileText wsFileGeneric', {'visible': this.props.visible})}>
32
-
33
-        <div className='wsFileText__contentpage wsFileGeneric__contentpage'>
34
-          <div className='wsFileText__contentpage__textnote'>
35
-            <div className='wsFileText__contentpage__textnote__latestversion'>
36
-              Dernière version : v3
37
-            </div>
38
-            <div className='wsFileText__contentpage__textnote__title'>
39
-              Titre de 30px de font size
40
-            </div>
41
-            <div className='wsFileText__contentpage__textnote__data'>
42
-              Labore tempor sunt id quis quis velit ut officia amet ut
43
-              adipisicing in in commodo exercitation cupidatat culpa
44
-              eiusmo dolor consectetur dolor ut proident proident culpamet
45
-              denim consequat in sit ex ullamco duis.
46
-              <br />
47
-              Labore tempor sunt id quis quis velit ut officia amet ut
48
-              adipisicing in in commodo exercitation cupidatat culpa
49
-              eiusmo dolor consectetur dolor ut proident proident culpamet
50
-              denim consequat in sit ex ullamco duis.
51
-              <br />
52
-              Labore tempor sunt id quis quis velit ut officia amet ut
53
-              adipisicing in in commodo exercitation cupidatat culpa
54
-              eiusmo dolor consectetur dolor ut proident proident culpamet
55
-              denim consequat in sit ex ullamco duis.
56
-              <br />
57
-              Labore tempor sunt id quis quis velit ut officia amet ut
58
-              adipisicing in in commodo exercitation cupidatat culpa
59
-              eiusmo dolor consectetur dolor ut proident proident culpamet
60
-              denim consequat in sit ex ullamco duis.
61
-            </div>
62
-          </div>
63
-          <div className='wsFileText__contentpage__wrapper wsFileGeneric__wrapper'>
64
-
65
-          </div>
66
-        </div>
67
-      </div>
68
-    )
69
-  }
70
-}
71
-
72
-export default PageText
73
-*/

+ 6 - 8
src/container/PrivateRoute.jsx Прегледај датотеку

@@ -7,14 +7,12 @@ import { Route, Redirect, withRouter } from 'react-router-dom'
7 7
 
8 8
 // /!\ you shall destruct props.component otherwise you get a warning:
9 9
 // "You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored
10
-const PrivateRoute = ({ component: Component, ...rest }) => {
11
-  return (
12
-    <Route {...rest} render={props => rest.user.isLoggedIn
13
-      ? <Component {...props} />
14
-      : <Redirect to={{pathname: '/login', state: {from: props.location}}} />
15
-    } />
16
-  )
17
-}
10
+const PrivateRoute = ({ component: Component, ...rest }) => (
11
+  <Route {...rest} render={props => rest.user.isLoggedIn
12
+    ? <Component {...props} />
13
+    : <Redirect to={{pathname: '/login', state: {from: props.location}}} />
14
+  } />
15
+)
18 16
 
19 17
 const mapStateToProps = ({ user }) => ({ user })
20 18
 export default withRouter(connect(mapStateToProps)(PrivateRoute))

+ 31 - 20
src/container/Tracim.jsx Прегледај датотеку

@@ -4,46 +4,57 @@ import Footer from '../component/Footer.jsx'
4 4
 import Header from './Header.jsx'
5 5
 import Sidebar from './Sidebar.jsx'
6 6
 import Login from './Login.jsx'
7
-import Page from './Page.jsx'
8 7
 import WorkspaceContent from './WorkspaceContent.jsx'
9 8
 import {
10 9
   Route,
11 10
   withRouter
12 11
 } from 'react-router-dom'
13 12
 import PrivateRoute from './PrivateRoute.jsx'
14
-
15
-const SidebarWrapper = props => {
16
-  if (props.locationPath !== '/login') {
17
-    return (
18
-      <div className='sidebarpagecontainer'>
19
-        <Sidebar />
20
-        {props.children}
21
-      </div>
22
-    )
23
-  } else return props.children
24
-}
13
+import { getIsUserConnected } from '../action-creator.async.js'
25 14
 
26 15
 class Tracim extends React.Component {
16
+  componentDidMount = () => {
17
+    this.props.dispatch(getIsUserConnected())
18
+  }
19
+
27 20
   render () {
28
-    const { location } = this.props
21
+    const { user, location } = this.props
22
+
23
+    const SidebarWrapper = props => {
24
+      if (props.locationPath !== '/login') {
25
+        return (
26
+          <div className='sidebarpagecontainer'>
27
+            <Sidebar />
28
+            {props.children}
29
+          </div>
30
+        )
31
+      } else return props.children
32
+    }
33
+
29 34
     return (
30 35
       <div>
31 36
         <Header />
32 37
 
33
-        <Route path='/login' component={Login} />
38
+        { user.isLoggedIn === undefined
39
+          ? (<div />) // while we dont know if user is connected, display nothing but the header @TODO show loader
40
+          : (
41
+            <div>
42
+              <Route path='/login' component={Login} />
34 43
 
35
-        <SidebarWrapper locationPath={location.pathname}>
44
+              <SidebarWrapper locationPath={location.pathname}>
36 45
 
37
-          <PrivateRoute exact path='/' component={WorkspaceContent} />
38
-          <PrivateRoute path='/page' component={Page} />
46
+                <PrivateRoute exact path='/' component={WorkspaceContent} />
39 47
 
40
-        </SidebarWrapper>
48
+              </SidebarWrapper>
41 49
 
42
-        <Footer />
50
+              <Footer />
51
+            </div>
52
+          )
53
+        }
43 54
       </div>
44 55
     )
45 56
   }
46 57
 }
47 58
 
48
-const mapStateToProps = () => ({})
59
+const mapStateToProps = ({ user }) => ({ user })
49 60
 export default withRouter(connect(mapStateToProps)(Tracim))

+ 27 - 17
src/container/WorkspaceContent.jsx Прегледај датотеку

@@ -3,14 +3,14 @@ import { connect } from 'react-redux'
3 3
 import Folder from '../component/Workspace/Folder.jsx'
4 4
 import FileItem from '../component/Workspace/FileItem.jsx'
5 5
 import FileItemHeader from '../component/Workspace/FileItemHeader.jsx'
6
-// import Thread from './Thread.jsx'
7
-// import PageText from './PageText.jsx'
8 6
 import Preview from './Preview.jsx'
9 7
 import PageWrapper from '../component/common/layout/PageWrapper.jsx'
10 8
 import PageTitle from '../component/common/layout/PageTitle.jsx'
11 9
 import PageContent from '../component/common/layout/PageContent.jsx'
12 10
 import DropdownCreateButton from '../component/common/Input/DropdownCreateButton.jsx'
11
+import FileContentViewer from '../component/Workspace/FileContentViewer.jsx'
13 12
 import { getWorkspaceContent } from '../action-creator.async.js'
13
+import { setActiveFileContent, hideActiveFileContent } from '../action-creator.sync.js'
14 14
 
15 15
 class WorkspaceContent extends React.Component {
16 16
   constructor (props) {
@@ -24,8 +24,16 @@ class WorkspaceContent extends React.Component {
24 24
     this.props.dispatch(getWorkspaceContent(/* this.props.workspace.id */1))
25 25
   }
26 26
 
27
+  handleClickFileItem = file => {
28
+    this.props.dispatch(setActiveFileContent(file))
29
+  }
30
+
31
+  handleClickCloseBtn = () => {
32
+    this.props.dispatch(hideActiveFileContent())
33
+  }
34
+
27 35
   render () {
28
-    const { workspace } = this.props
36
+    const { workspace, activeFileContent } = this.props
29 37
 
30 38
     return (
31 39
       <PageWrapper customeClass='workspace'>
@@ -44,24 +52,26 @@ class WorkspaceContent extends React.Component {
44 52
 
45 53
             { workspace.content.map(c => c.type === 'folder'
46 54
               ? <Folder folderData={c} key={c.id} />
47
-              : <FileItem
48
-                name={c.title}
49
-                type={c.type}
50
-                status={c.status}
51
-                onClickItem={() => this.setState({activeFileType: 'file'})}
52
-                key={c.id}
53
-              />
55
+              : (
56
+                <FileItem
57
+                  name={c.title}
58
+                  type={c.type}
59
+                  status={c.status}
60
+                  onClickItem={() => this.handleClickFileItem(c)}
61
+                  key={c.id}
62
+                />
63
+              )
54 64
             )}
55 65
           </div>
56 66
 
57 67
           <DropdownCreateButton customClass='workspace__content__button mb-5' />
58 68
 
59
-          <Preview visible={this.state.activeFileType === 'file'} />
60
-
61
-          {/*
62
-          <PageText visible={this.state.activeFileType === 'file'} />
63
-          <Thread visible={this.state.activeFileType === 'thread'} />
64
-          */}
69
+          { activeFileContent.display &&
70
+            <FileContentViewer
71
+              file={activeFileContent}
72
+              onClose={this.handleClickCloseBtn}
73
+            />
74
+          }
65 75
         </PageContent>
66 76
 
67 77
       </PageWrapper>
@@ -69,5 +79,5 @@ class WorkspaceContent extends React.Component {
69 79
   }
70 80
 }
71 81
 
72
-const mapStateToProps = ({ workspace }) => ({ workspace })
82
+const mapStateToProps = ({ workspace, activeFileContent }) => ({ workspace, activeFileContent })
73 83
 export default connect(mapStateToProps)(WorkspaceContent)

+ 2 - 2
src/css/Header.styl Прегледај датотеку

@@ -19,7 +19,7 @@
19 19
           color blue
20 20
           font-size 16px
21 21
           &:hover
22
-            color dark-blue
22
+            color darkBlue
23 23
     &__rightside
24 24
       display flex
25 25
       margin-top 15px
@@ -60,7 +60,7 @@
60 60
         &__btnquestion
61 61
           margin-right 30px
62 62
           .btnquestion__icon
63
-            color dark-grey
63
+            color darkGrey
64 64
       &__itemprofil
65 65
         &__profilgroup
66 66
           .profilgroup__name

src/css/PageText.styl → src/css/PageHtml.styl Прегледај датотеку

@@ -1,4 +1,4 @@
1
-.wsFileText
1
+.wsFilePageHtml
2 2
   width 1200px
3 3
   height calc(100% - 85px)
4 4
   overflow-Y auto
@@ -20,14 +20,8 @@
20 20
       &__latestversion
21 21
         text-align right
22 22
         opacity 0.7
23
-      &__title
24
-        margin 10px 0
25
-        font-size 30px
26
-        color htmlColor
27
-      &__data
28
-        flex-grow 1
29
-        flex-shrink 1
30
-        flex-basis 0
23
+      &__text
24
+        font-size 14px
31 25
     &__wrapper
32 26
       margin 10px
33 27
       border-radius 10px
@@ -86,28 +80,17 @@
86 80
             border-color htmlColor
87 81
             background-color htmlColor
88 82
 
89
-/***** CLASS MESSAGELISTSIZE *****/
90
-
91 83
 .messagelistOpen
92
-  .wsFileText__contentpage__messagelist
84
+  .wsFilePageHtml__contentpage__messagelist
93 85
     flex 0
94 86
     min-height 0
95 87
 
96
-/*********************************/
97
-
98
-/***** SENDER RECEIVER *****/
99
-
100 88
 .received
101
-  .wsFileText__contentpage__messagelist__item__content
89
+  .wsFilePageHtml__contentpage__messagelist__item__content
102 90
       background-color htmlColor
103 91
 
104
-/*****************************/
105
-
106
-/**** MEDIA QUERIES ****/
107
-
108 92
 @media (min-width: max-xs) and (max-width: max-lg)
109
-
110
-  .wsFileText
93
+  .wsFilePageHtml
111 94
     &__contentpage
112 95
       display block
113 96
       overflow-Y scroll
@@ -123,11 +106,9 @@
123 106
       &__texteditor
124 107
         &__simpletext
125 108
           display inline-flex
126
-          display ms-inline-flex
127 109
           width 60%
128 110
         &__submit
129 111
           display inline-flex
130
-          display ms-inline-flex
131 112
           margin 10px 0
132 113
           &__btn
133 114
             display flex
@@ -135,46 +116,28 @@
135 116
             &__icon
136 117
               margin-left 15px
137 118
 
138
-/**** MEDIA 992px & 1199px ****/
139
-
140 119
 @media (min-width: min-lg) and (max-width: max-lg)
141
-
142
-  .wsFileText
120
+  .wsFilePageHtml
143 121
     width 692px
144 122
     &__contentpage__texteditor__simpletext
145 123
       margin-left 15px
146 124
 
147
-/******************************/
148
-
149
-/**** MEDIA 768px & 991px ****/
150
-
151 125
 @media (min-width: min-md) and (max-width: max-md)
152
-
153
-  .wsFileText
126
+  .wsFilePageHtml
154 127
     width 768px
155 128
     &__contentpage__texteditor__simpletext
156 129
       margin-left 25px
157 130
 
158
-/******************************/
159
-
160
-/**** MEDIA 576px & 767px ****/
161
-
162 131
 @media (min-width: min-sm) and (max-width: max-sm)
163
-
164
-  .wsFileText
132
+  .wsFilePageHtml
165 133
       top 69px
166 134
       width 576px
167 135
       height calc(100% - 69px)
168 136
       &__contentpage__texteditor__simpletext
169 137
         margin-left 10px
170 138
 
171
-/******************************/
172
-
173
-/**** MEDIA 575px ****/
174
-
175 139
 @media (max-width: max-xs)
176
-
177
-  .wsFileText
140
+  .wsFilePageHtml
178 141
     top 70px
179 142
     height calc(100% - 70px)
180 143
     width 100%

+ 3 - 3
src/css/Variable.styl Прегледај датотеку

@@ -22,7 +22,8 @@ darkBlue = #215e8e
22 22
 blue = #2571fe
23 23
 lightBlue = #569EDE
24 24
 
25
-btnCallAction = #28a745 // couleur des boutons call2action dans le contexte generale (tout contexte sauf celui d'un type de contenu particulier)
25
+// c2a btn color in general context ; meaning every context but specific one related to file type (file, pageHtml, issues, threads ...)
26
+btnCallAction = #28a745
26 27
 btnCallAction-hover = darken(btnCallAction, 15%)
27 28
 
28 29
 threadColor = #2674d3
@@ -42,13 +43,12 @@ issueColor = #a4835e
42 43
 // calendarColor =
43 44
 // dashboard =
44 45
 
45
-
46 46
 /*************************/
47 47
 /**** BOX SHADOW ****/
48 48
 shadow-bottom = 0px 0px 5px 1px #606060
49 49
 shadow-right = 2px 2px 5px 0px #404040
50 50
 shadow-all = 1px 1px 5px 2px #ababab
51
-shadow-all-side-blue = 0px 0px 1px 1px light-blue
51
+shadow-all-side-blue = 0px 0px 1px 1px lightBlue
52 52
 shadow-all-side-green = 0 0 1px 2px green
53 53
 
54 54
 /***********************/

+ 1 - 1
src/css/index.styl Прегледај датотеку

@@ -18,7 +18,7 @@ html, body, #content, #content > div
18 18
 @import 'Folder'
19 19
 
20 20
 @import 'Thread'
21
-@import 'PageText'
21
+@import 'PageHtml'
22 22
 @import 'Timeline'
23 23
 @import 'Preview'
24 24
 

+ 25 - 8
src/helper.js Прегледај датотеку

@@ -5,11 +5,28 @@ export const FETCH_CONFIG = {
5 5
   }
6 6
 }
7 7
 
8
-export const FILE_TYPE = {
9
-  PAGE_HTML: 'pageHtml',
10
-  PAGE_MARKDOWN: 'pageMarkdown',
11
-  FILE: 'file',
12
-  THREAD: 'thread',
13
-  TASK: 'task',
14
-  ISSUE: 'issue'
15
-}
8
+export const FILE_TYPE = [{
9
+  name: 'pageHtml',
10
+  customClass: 'wsFilePageHtml',
11
+  icon: 'fa fa-file-word-o'
12
+}, {
13
+  name: 'pageMarkdown',
14
+  customClass: 'wsFilePageMarkdown',
15
+  icon: 'fa fa-file-code-o'
16
+}, {
17
+  name: 'file',
18
+  customClass: 'wsFileFile',
19
+  icon: 'fa fa-file-image-o'
20
+}, {
21
+  name: 'thread',
22
+  customClass: 'wsFileThread',
23
+  icon: 'fa fa-comments-o'
24
+}, {
25
+  name: 'task',
26
+  customClass: 'wsFileTask',
27
+  icon: 'fa fa-list-ul'
28
+}, {
29
+  name: 'issue',
30
+  customClass: 'wsFileIssue',
31
+  icon: 'fa fa-ticket'
32
+}]

+ 22 - 0
src/reducer/activeFileContent.js Прегледај датотеку

@@ -0,0 +1,22 @@
1
+import { FILE_CONTENT } from '../action-creator.sync.js'
2
+
3
+export default function activeFileContent (state = {
4
+  display: false,
5
+  type: '',
6
+  title: '',
7
+  status: ''
8
+}, action) {
9
+  switch (action.type) {
10
+    case `Set/${FILE_CONTENT}/Active`:
11
+      return {
12
+        display: true,
13
+        ...action.file
14
+      }
15
+
16
+    case `Set/${FILE_CONTENT}/Hide`:
17
+      return {...state, display: false}
18
+
19
+    default:
20
+      return state
21
+  }
22
+}

+ 10 - 0
src/reducer/fileType/index.js Прегледај датотеку

@@ -0,0 +1,10 @@
1
+import { combineReducers } from 'redux'
2
+
3
+// import color from './color.js'
4
+// import size from './size.js'
5
+// import price from './price.js'
6
+// import multicolor from './multicolor.js'
7
+
8
+export default combineReducers({
9
+
10
+})

+ 10 - 0
src/reducer/fileType/pageHtml.js Прегледај датотеку

@@ -0,0 +1,10 @@
1
+export default function pageHtml (state = {
2
+  title: '',
3
+  version: 0,
4
+  text: ''
5
+}, action) {
6
+  switch (action.type) {
7
+    default:
8
+      return state
9
+  }
10
+}

+ 2 - 1
src/reducer/root.js Прегледај датотеку

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

+ 12 - 2
src/reducer/user.js Прегледај датотеку

@@ -3,8 +3,18 @@ import {
3 3
   USER_DATA
4 4
 } from '../action-creator.sync.js'
5 5
 
6
+const serializeUser = data => ({
7
+  id: data.user.id,
8
+  isLoggedIn: data.logged,
9
+  username: data.user.username,
10
+  firstname: data.user.firstname,
11
+  lastname: data.user.lastname,
12
+  email: data.user.email
13
+})
14
+
6 15
 export default function user (state = {
7
-  isLoggedIn: false,
16
+  id: 0,
17
+  isLoggedIn: undefined,
8 18
   username: '',
9 19
   firstname: '',
10 20
   lastname: '',
@@ -12,7 +22,7 @@ export default function user (state = {
12 22
 }, action) {
13 23
   switch (action.type) {
14 24
     case `Update/${USER_CONNECTED}`:
15
-      return {...state, isLoggedIn: true, ...action.user}
25
+      return serializeUser(action.user)
16 26
 
17 27
     case `Update/${USER_DATA}`:
18 28
       return {...state, ...action.data}