Browse Source

[https://github.com/tracim/tracim/issues/745] added handler on app's btns + handler for btn 'see more' + fixed member's avatar

Skylsmoi 6 years ago
parent
commit
817be560ee

+ 2 - 2
frontend/src/action-creator.async.js View File

240
   })
240
   })
241
 }
241
 }
242
 
242
 
243
-export const getWorkspaceRecentActivityList = (user, idWorkspace) => dispatch => {
243
+export const getWorkspaceRecentActivityList = (user, idWorkspace, beforeId = null) => dispatch => {
244
   return fetchWrapper({
244
   return fetchWrapper({
245
-    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/workspaces/${idWorkspace}/contents/recently_active?limit=10`,
245
+    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/workspaces/${idWorkspace}/contents/recently_active?limit=10${beforeId ? `&before_content_id=${beforeId}` : ''}`,
246
     param: {
246
     param: {
247
       headers: {
247
       headers: {
248
         ...FETCH_CONFIG.headers,
248
         ...FETCH_CONFIG.headers,

+ 2 - 2
frontend/src/action-creator.sync.js View File

2
 export const UPDATE = 'Update'
2
 export const UPDATE = 'Update'
3
 export const ADD = 'Add'
3
 export const ADD = 'Add'
4
 export const REMOVE = 'Remove'
4
 export const REMOVE = 'Remove'
5
+export const APPEND = 'Append'
5
 
6
 
6
 export const TIMEZONE = 'Timezone'
7
 export const TIMEZONE = 'Timezone'
7
 export const setTimezone = timezone => ({ type: `${SET}/${TIMEZONE}`, timezone })
8
 export const setTimezone = timezone => ({ type: `${SET}/${TIMEZONE}`, timezone })
52
 export const WORKSPACE_RECENT_ACTIVITY = `${WORKSPACE}/RecentActivity/List`
53
 export const WORKSPACE_RECENT_ACTIVITY = `${WORKSPACE}/RecentActivity/List`
53
 export const WORKSPACE_RECENT_ACTIVITY_LIST = `${WORKSPACE_RECENT_ACTIVITY}/List`
54
 export const WORKSPACE_RECENT_ACTIVITY_LIST = `${WORKSPACE_RECENT_ACTIVITY}/List`
54
 export const setWorkspaceRecentActivityList = workspaceRecentActivityList => ({ type: `${SET}/${WORKSPACE_RECENT_ACTIVITY_LIST}`, workspaceRecentActivityList })
55
 export const setWorkspaceRecentActivityList = workspaceRecentActivityList => ({ type: `${SET}/${WORKSPACE_RECENT_ACTIVITY_LIST}`, workspaceRecentActivityList })
55
-export const WORKSPACE_RECENT_ACTIVITY_FOR_USER_LIST = `${WORKSPACE_RECENT_ACTIVITY}/ForUser/List`
56
-export const setWorkspaceRecentActivityForUserList = workspaceRecentActivityForUserList => ({ type: `${SET}/${WORKSPACE_RECENT_ACTIVITY_FOR_USER_LIST}`, workspaceRecentActivityForUserList })
56
+export const appendWorkspaceRecentActivityList = workspaceRecentActivityList => ({ type: `${APPEND}/${WORKSPACE_RECENT_ACTIVITY_LIST}`, workspaceRecentActivityList })
57
 
57
 
58
 export const WORKSPACE_READ_STATUS = `${WORKSPACE}/ReadStatus`
58
 export const WORKSPACE_READ_STATUS = `${WORKSPACE}/ReadStatus`
59
 export const WORKSPACE_READ_STATUS_LIST = `${WORKSPACE_READ_STATUS}/List`
59
 export const WORKSPACE_READ_STATUS_LIST = `${WORKSPACE_READ_STATUS}/List`

+ 3 - 1
frontend/src/component/Dashboard/ContentTypeBtn.jsx View File

15
         backgroundColor: color(props.hexcolor).darken(0.15).hexString()
15
         backgroundColor: color(props.hexcolor).darken(0.15).hexString()
16
       }
16
       }
17
     }}
17
     }}
18
+    onClick={props.onClickBtn}
18
   >
19
   >
19
     <div className={classnames(`${props.customClass}__text`)}>
20
     <div className={classnames(`${props.customClass}__text`)}>
20
       <div className={classnames(`${props.customClass}__text__icon`)}>
21
       <div className={classnames(`${props.customClass}__text__icon`)}>
33
   label: PropTypes.string.isRequired,
34
   label: PropTypes.string.isRequired,
34
   faIcon: PropTypes.string.isRequired,
35
   faIcon: PropTypes.string.isRequired,
35
   creationLabel: PropTypes.string.isRequired,
36
   creationLabel: PropTypes.string.isRequired,
36
-  customClass: PropTypes.string
37
+  customClass: PropTypes.string,
38
+  onClickBtn: PropTypes.func
37
 }
39
 }
38
 
40
 
39
 ContentTypeBtn.defaultProps = {
41
 ContentTypeBtn.defaultProps = {

+ 1 - 1
frontend/src/component/Dashboard/MemberList.jsx View File

45
                   {props.memberList.map(m =>
45
                   {props.memberList.map(m =>
46
                     <li className='memberlist__list__item' key={m.id}>
46
                     <li className='memberlist__list__item' key={m.id}>
47
                       <div className='memberlist__list__item__avatar'>
47
                       <div className='memberlist__list__item__avatar'>
48
-                        {m.avatarUrl ? <img src={m.avatarUrl} /> : <img src='NYI' />}
48
+                        <img src={m.avatarUrl} />
49
                       </div>
49
                       </div>
50
 
50
 
51
                       <div className='memberlist__list__item__info mr-auto'>
51
                       <div className='memberlist__list__item__info mr-auto'>

+ 0 - 8
frontend/src/component/Dashboard/MemberList.styl View File

21
       display flex
21
       display flex
22
       border-bottom 1px solid grey
22
       border-bottom 1px solid grey
23
       padding 10px 15px
23
       padding 10px 15px
24
-      &:hover
25
-        background-color fourthColor
26
-      &:nth-last-child(1)
27
-        border-bottom 0
28
-      &:nth-child(even)
29
-        background-color grey-hover
30
-        &:hover
31
-          background-color fourthColor
32
       &__avatar
24
       &__avatar
33
         margin-right 20px
25
         margin-right 20px
34
         & > img
26
         & > img

+ 9 - 4
frontend/src/component/Dashboard/RecentActivity.jsx View File

20
     </div>
20
     </div>
21
 
21
 
22
     <div className='activity__wrapper'>
22
     <div className='activity__wrapper'>
23
-      {props.recentActivityFilteredForUser.map(content => {
23
+      {props.recentActivityList.map(content => {
24
         const contentType = props.contentTypeList.find(ct => ct.slug === content.type)
24
         const contentType = props.contentTypeList.find(ct => ct.slug === content.type)
25
         return (
25
         return (
26
           <div
26
           <div
27
-            className='activity__workspace'
27
+            className={classnames('activity__workspace', {'read': props.readByUserList.includes(content.id)})}
28
             onClick={() => props.onClickRecentContent(content.id, content.type)}
28
             onClick={() => props.onClickRecentContent(content.id, content.type)}
29
             key={content.id}
29
             key={content.id}
30
           >
30
           >
53
 
53
 
54
 RecentActivity.propTypes = {
54
 RecentActivity.propTypes = {
55
   t: PropTypes.func.isRequired,
55
   t: PropTypes.func.isRequired,
56
-  recentActivityFilteredForUser: PropTypes.array.isRequired,
56
+  recentActivityList: PropTypes.array.isRequired,
57
   contentTypeList: PropTypes.array.isRequired,
57
   contentTypeList: PropTypes.array.isRequired,
58
-  onClickSeeMore: PropTypes.func.isRequired
58
+  onClickSeeMore: PropTypes.func.isRequired,
59
+  readByUserList: PropTypes.array
60
+}
61
+
62
+RecentActivity.defaultProps = {
63
+  readByUserList: []
59
 }
64
 }

+ 3 - 3
frontend/src/component/Dashboard/RecentActivity.styl View File

23
     border-bottom 1px solid grey
23
     border-bottom 1px solid grey
24
     padding 15px
24
     padding 15px
25
     cursor pointer
25
     cursor pointer
26
+    font-weight bold
27
+    &.read
28
+      font-weight normal
26
     &:hover
29
     &:hover
27
       background-color fourthColor
30
       background-color fourthColor
28
     &:nth-child(even)
31
     &:nth-child(even)
34
       font-size 25px
37
       font-size 25px
35
     &__name
38
     &__name
36
       font-size 18px
39
       font-size 18px
37
-      font-weight 500
38
-      span
39
-        font-weight 400
40
   &__more
40
   &__more
41
     &__btn
41
     &__btn
42
       margin 15px
42
       margin 15px

+ 34 - 11
frontend/src/container/Dashboard.jsx View File

20
   setWorkspaceDetail,
20
   setWorkspaceDetail,
21
   setWorkspaceMemberList,
21
   setWorkspaceMemberList,
22
   setWorkspaceRecentActivityList,
22
   setWorkspaceRecentActivityList,
23
-  setWorkspaceRecentActivityForUserList,
23
+  appendWorkspaceRecentActivityList,
24
   setWorkspaceReadStatusList
24
   setWorkspaceReadStatusList
25
 } from '../action-creator.sync.js'
25
 } from '../action-creator.sync.js'
26
 import { ROLE, PAGE } from '../helper.js'
26
 import { ROLE, PAGE } from '../helper.js'
34
   constructor (props) {
34
   constructor (props) {
35
     super(props)
35
     super(props)
36
     this.state = {
36
     this.state = {
37
-      workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null, // this is used to avoid handling the parseInt everytime
37
+      workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null, // this is used to avoid handling the parseInt every time
38
       newMember: {
38
       newMember: {
39
         id: '',
39
         id: '',
40
         avatarUrl: '',
40
         avatarUrl: '',
51
   }
51
   }
52
 
52
 
53
   async componentDidMount () {
53
   async componentDidMount () {
54
+    this.loadWorkspaceDetail()
55
+    this.loadMemberList()
56
+    this.loadRecentActivity()
57
+  }
58
+
59
+  componentDidUpdate (prevProps, prevState) {
60
+    const { props, state } = this
61
+
62
+    if (prevProps.match.params.idws !== props.match.params.idws) {
63
+      this.setState({workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null})
64
+    }
65
+
66
+    if (prevState.workspaceIdInUrl !== state.workspaceIdInUrl) {
67
+      this.loadWorkspaceDetail()
68
+      this.loadMemberList()
69
+      this.loadRecentActivity()
70
+    }
71
+  }
72
+
73
+  loadWorkspaceDetail = async () => {
54
     const { props, state } = this
74
     const { props, state } = this
55
 
75
 
56
     const fetchWorkspaceDetail = await props.dispatch(getWorkspaceDetail(props.user, state.workspaceIdInUrl))
76
     const fetchWorkspaceDetail = await props.dispatch(getWorkspaceDetail(props.user, state.workspaceIdInUrl))
58
       case 200: props.dispatch(setWorkspaceDetail(fetchWorkspaceDetail.json)); break
78
       case 200: props.dispatch(setWorkspaceDetail(fetchWorkspaceDetail.json)); break
59
       default: props.dispatch(newFlashMessage(`${props.t('An error has happened while fetching')} ${props.t('workspace detail')}`, 'warning')); break
79
       default: props.dispatch(newFlashMessage(`${props.t('An error has happened while fetching')} ${props.t('workspace detail')}`, 'warning')); break
60
     }
80
     }
61
-    this.loadMemberList()
62
-    this.loadRecentActivity()
63
   }
81
   }
64
 
82
 
65
   loadMemberList = async () => {
83
   loadMemberList = async () => {
87
       case 200: props.dispatch(setWorkspaceReadStatusList(fetchWorkspaceReadStatusList.json)); break
105
       case 200: props.dispatch(setWorkspaceReadStatusList(fetchWorkspaceReadStatusList.json)); break
88
       default: props.dispatch(newFlashMessage(`${props.t('An error has happened while fetching')} ${props.t('read status list')}`, 'warning')); break
106
       default: props.dispatch(newFlashMessage(`${props.t('An error has happened while fetching')} ${props.t('read status list')}`, 'warning')); break
89
     }
107
     }
90
-
91
-    const readStatusForUserList = fetchWorkspaceReadStatusList.json.filter(c => c.read_by_user).map(c => c.content_id)
92
-    const recentActivityForUserList = fetchWorkspaceRecentActivityList.json.filter(content => !readStatusForUserList.includes(content.content_id))
93
-
94
-    props.dispatch(setWorkspaceRecentActivityForUserList(recentActivityForUserList))
95
   }
108
   }
96
 
109
 
97
   handleToggleNewMemberDashboard = () => this.setState(prevState => ({displayNewMemberDashboard: !prevState.displayNewMemberDashboard}))
110
   handleToggleNewMemberDashboard = () => this.setState(prevState => ({displayNewMemberDashboard: !prevState.displayNewMemberDashboard}))
114
   }
127
   }
115
 
128
 
116
   handleClickSeeMore = async () => {
129
   handleClickSeeMore = async () => {
117
-    console.log('nyi')
130
+    const { props, state } = this
131
+
132
+    const idLastRecentActivity = props.curWs.recentActivityList[props.curWs.recentActivityList.length - 1].id
133
+
134
+    const fetchWorkspaceRecentActivityList = await props.dispatch(getWorkspaceRecentActivityList(props.user, state.workspaceIdInUrl, idLastRecentActivity))
135
+    switch (fetchWorkspaceRecentActivityList.status) {
136
+      case 200: props.dispatch(appendWorkspaceRecentActivityList(fetchWorkspaceRecentActivityList.json)); break
137
+      default: props.dispatch(newFlashMessage(`${props.t('An error has happened while fetching')} ${props.t('recent activity list')}`, 'warning')); break
138
+    }
118
   }
139
   }
119
 
140
 
120
   handleSearchUser = async userNameToSearch => {
141
   handleSearchUser = async userNameToSearch => {
223
                   label={ct.label}
244
                   label={ct.label}
224
                   faIcon={ct.faIcon}
245
                   faIcon={ct.faIcon}
225
                   creationLabel={ct.creationLabel}
246
                   creationLabel={ct.creationLabel}
247
+                  onClickBtn={() => props.history.push(PAGE.WORKSPACE.NEW(props.curWs.id, ct.slug))}
226
                   key={ct.label}
248
                   key={ct.label}
227
                 />
249
                 />
228
               )}
250
               )}
231
             <div className='dashboard__workspaceInfo'>
253
             <div className='dashboard__workspaceInfo'>
232
               <RecentActivity
254
               <RecentActivity
233
                 customClass='dashboard__activity'
255
                 customClass='dashboard__activity'
234
-                recentActivityFilteredForUser={props.curWs.recentActivityForUserList}
256
+                recentActivityList={props.curWs.recentActivityList}
257
+                readByUserList={props.curWs.contentReadStatusList}
235
                 contentTypeList={props.contentType}
258
                 contentTypeList={props.contentType}
236
                 onClickRecentContent={this.handleClickRecentContent}
259
                 onClickRecentContent={this.handleClickRecentContent}
237
                 onClickEverythingAsRead={this.handleClickMarkRecentActivityAsRead}
260
                 onClickEverythingAsRead={this.handleClickMarkRecentActivityAsRead}

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

106
 
106
 
107
               <Route exact path={PAGE.WORKSPACE.ROOT} render={() => props.workspaceList.length === 0 // handle '/' and redirect to first workspace
107
               <Route exact path={PAGE.WORKSPACE.ROOT} render={() => props.workspaceList.length === 0 // handle '/' and redirect to first workspace
108
                 ? null // @FIXME this needs to be handled in case of new user that has no workspace
108
                 ? null // @FIXME this needs to be handled in case of new user that has no workspace
109
-                : <Redirect to={{pathname: `/workspaces/${props.workspaceList[0].id}/contents`, state: {from: props.location}}} />
109
+                : <Redirect to={{pathname: PAGE.WORKSPACE.DASHBOARD(props.workspaceList[0].id), state: {from: props.location}}} />
110
               } />
110
               } />
111
 
111
 
112
               <Route exact path={`${PAGE.WORKSPACE.ROOT}/:idws`} render={props2 => // handle '/workspaces/:id' and add '/contents'
112
               <Route exact path={`${PAGE.WORKSPACE.ROOT}/:idws`} render={props2 => // handle '/workspaces/:id' and add '/contents'
113
-                <Redirect to={{pathname: `/workspaces/${props2.match.params.idws}/contents`, state: {from: props.location}}} />
113
+                <Redirect to={{pathname: PAGE.WORKSPACE.CONTENT_LIST(props2.match.params.idws), state: {from: props.location}}} />
114
               } />
114
               } />
115
 
115
 
116
               <Route path={PAGE.WORKSPACE.DASHBOARD(':idws')} component={Dashboard} />
116
               <Route path={PAGE.WORKSPACE.DASHBOARD(':idws')} component={Dashboard} />

+ 22 - 15
frontend/src/reducer/currentWorkspace.js View File

1
 import {
1
 import {
2
   SET,
2
   SET,
3
+  APPEND,
3
   WORKSPACE_DETAIL,
4
   WORKSPACE_DETAIL,
4
   WORKSPACE_MEMBER_LIST,
5
   WORKSPACE_MEMBER_LIST,
5
-  WORKSPACE_READ_STATUS_LIST, WORKSPACE_RECENT_ACTIVITY_FOR_USER_LIST,
6
+  WORKSPACE_READ_STATUS_LIST,
6
   WORKSPACE_RECENT_ACTIVITY_LIST
7
   WORKSPACE_RECENT_ACTIVITY_LIST
7
 } from '../action-creator.sync.js'
8
 } from '../action-creator.sync.js'
8
 import { handleRouteFromApi } from '../helper.js'
9
 import { handleRouteFromApi } from '../helper.js'
10
+import { generateAvatarFromPublicName } from 'tracim_frontend_lib'
9
 
11
 
10
 const defaultWorkspace = {
12
 const defaultWorkspace = {
11
   id: 0,
13
   id: 0,
43
         memberList: action.workspaceMemberList.map(m => ({
45
         memberList: action.workspaceMemberList.map(m => ({
44
           id: m.user_id,
46
           id: m.user_id,
45
           publicName: m.user.public_name,
47
           publicName: m.user.public_name,
46
-          avatarUrl: m.user.avatar_url,
48
+          avatarUrl: m.user.avatar_url
49
+            ? m.user.avatar_url
50
+            : m.user.public_name ? generateAvatarFromPublicName(m.user.public_name) : '',
47
           role: m.role,
51
           role: m.role,
48
           isActive: m.is_active
52
           isActive: m.is_active
49
         }))
53
         }))
66
         }))
70
         }))
67
       }
71
       }
68
 
72
 
69
-    case `${SET}/${WORKSPACE_RECENT_ACTIVITY_FOR_USER_LIST}`:
73
+    case `${APPEND}/${WORKSPACE_RECENT_ACTIVITY_LIST}`:
70
       return {
74
       return {
71
         ...state,
75
         ...state,
72
-        recentActivityForUserList: action.workspaceRecentActivityForUserList.map(ra => ({
73
-          id: ra.content_id,
74
-          slug: ra.slug,
75
-          label: ra.label,
76
-          type: ra.content_type,
77
-          idParent: ra.parent_id,
78
-          showInUi: ra.show_in_ui,
79
-          isArchived: ra.is_archived,
80
-          isDeleted: ra.is_deleted,
81
-          statusSlug: ra.status,
82
-          subContentTypeSlug: ra.sub_content_types
83
-        }))
76
+        recentActivityList: [
77
+          ...state.recentActivityList,
78
+          ...action.workspaceRecentActivityList.map(ra => ({
79
+            id: ra.content_id,
80
+            slug: ra.slug,
81
+            label: ra.label,
82
+            type: ra.content_type,
83
+            idParent: ra.parent_id,
84
+            showInUi: ra.show_in_ui,
85
+            isArchived: ra.is_archived,
86
+            isDeleted: ra.is_deleted,
87
+            statusSlug: ra.status,
88
+            subContentTypeSlug: ra.sub_content_types
89
+          }))
90
+        ]
84
       }
91
       }
85
 
92
 
86
     case `${SET}/${WORKSPACE_READ_STATUS_LIST}`:
93
     case `${SET}/${WORKSPACE_READ_STATUS_LIST}`: