Add follower and following views

This commit is contained in:
eal 2017-08-21 20:25:01 +03:00
parent dbad99cb4f
commit ccfc2e57d0
12 changed files with 229 additions and 28 deletions

View file

@ -40,7 +40,7 @@
</div> </div>
<div class="media-body"> <div class="media-body">
<div class="base05 base05=border usercard" v-if="userExpanded"> <div class="base05 base05=border usercard" v-if="userExpanded">
<user-card-content :user="status.user"></user-card-content> <user-card-content :user="status.user" :switcher="false"></user-card-content>
</div> </div>
<div class="user-content"> <div class="user-content">
<div class="media-heading"> <div class="media-heading">

View file

@ -1,20 +1,32 @@
import Status from '../status/status.vue' import Status from '../status/status.vue'
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js' import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
import StatusOrConversation from '../status_or_conversation/status_or_conversation.vue' import StatusOrConversation from '../status_or_conversation/status_or_conversation.vue'
import UserCard from '../user_card/user_card.vue'
const Timeline = { const Timeline = {
props: [ props: [
'timeline', 'timeline',
'timelineName', 'timelineName',
'title', 'title',
'showingStatuses',
'userId' 'userId'
], ],
computed: { computed: {
timelineError () { return this.$store.state.statuses.error } timelineError () { return this.$store.state.statuses.error },
followers () {
return this.timeline.followers
},
friends () {
return this.timeline.friends
},
viewing () {
return this.timeline.viewing
}
}, },
components: { components: {
Status, Status,
StatusOrConversation StatusOrConversation,
UserCard
}, },
created () { created () {
const store = this.$store const store = this.$store
@ -30,6 +42,12 @@ const Timeline = {
showImmediately, showImmediately,
userId: this.userId userId: this.userId
}) })
// don't fetch followers for public, friend, twkn
if (this.timelineName === 'user') {
this.fetchFriends()
this.fetchFollowers()
}
}, },
methods: { methods: {
showNewStatuses () { showNewStatuses () {
@ -48,6 +66,16 @@ const Timeline = {
userId: this.userId userId: this.userId
}).then(() => store.commit('setLoading', { timeline: this.timelineName, value: false })) }).then(() => store.commit('setLoading', { timeline: this.timelineName, value: false }))
}, },
fetchFollowers() {
const id = this.userId
this.$store.state.api.backendInteractor.fetchFollowers({ id })
.then((followers) => this.$store.dispatch('addFollowers', { followers }))
},
fetchFriends() {
const id = this.userId
this.$store.state.api.backendInteractor.fetchFriends({ id })
.then((friends) => this.$store.dispatch('addFriends', { friends }))
},
scrollLoad (e) { scrollLoad (e) {
let height = Math.max(document.body.offsetHeight, document.body.scrollHeight) let height = Math.max(document.body.offsetHeight, document.body.scrollHeight)
if (this.timeline.loading === false && this.$store.state.config.autoLoad && (window.innerHeight + window.pageYOffset) >= (height - 750)) { if (this.timeline.loading === false && this.$store.state.config.autoLoad && (window.innerHeight + window.pageYOffset) >= (height - 750)) {

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="timeline panel panel-default"> <div class="timeline panel panel-default" v-if="viewing == 'statuses'">
<div class="panel-heading timeline-heading base01-background base04"> <div class="panel-heading timeline-heading base01-background base04">
<div class="title"> <div class="title">
{{title}} {{title}}
@ -24,6 +24,30 @@
</div> </div>
</div> </div>
</div> </div>
<div class="timeline panel panel-default" v-else-if="viewing == 'followers'">
<div class="panel-heading timeline-heading base01-background base04">
<div class="title">
Followers
</div>
</div>
<div class="panel-body">
<div class="timeline">
<user-card v-for="follower in followers" :user="follower" :showFollows="false"></user-card>
</div>
</div>
</div>
<div class="timeline panel panel-default" v-else-if="viewing == 'friends'">
<div class="panel-heading timeline-heading base01-background base04">
<div class="title">
Following
</div>
</div>
<div class="panel-body">
<div class="timeline">
<user-card v-for="friend in friends" :user="friend" :showFollows="true"></user-card>
</div>
</div>
</div>
</template> </template>
<script src="./timeline.js"></script> <script src="./timeline.js"></script>
@ -65,6 +89,13 @@
} }
} }
.avatar {
padding-top: 0.3em;
width:32px;
height: 32px;
border-radius: 50%;
}
.new-status-notification { .new-status-notification {
position:relative; position:relative;

View file

@ -0,0 +1,23 @@
import UserCardContent from '../user_card_content/user_card_content.vue'
const UserCard = {
props: [
'user',
'showFollows'
],
data () {
return {
userExpanded: false
}
},
components: {
UserCardContent
},
methods: {
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
}
}
}
export default UserCard

View file

@ -0,0 +1,61 @@
<template>
<div class="card base00-background base03-border">
<a href="#">
<img @click.prevent="toggleUserExpanded" class="avatar" :src="user.profile_image_url">
</a>
<div class="base05 base05=border usercard" v-if="userExpanded">
<user-card-content :user="user" :switcher="false"></user-card-content>
</div>
<div class="name-and-screen-name" v-else>
<div class="user-name">{{ user.name }}</div>
<a href="user.statusnet_profile_url"><div class="user-screen-name">@{{ user.screen_name }}</div></a>
</div>
<span class="follows-you" v-if="!userExpanded && showFollows">
<div class="follows" v-if="user.follows_you">
Follows you!
</div>
</span>
</div>
</template>
<script src="./user_card.js"></script>
<style lang="scss">
.name-and-screen-name {
margin-left: 0.7em;
min-width: 16em;
display:block;
margin-top:0.0em;
margin-right: 2em;
text-align: left;
}
.follows-you {
margin-left: 2em;
width:-webkit-fill-available;
width: -moz-webkit-fill-available;
}
.follows {
float: right;
}
.card {
display: flex;
flex: 1 0;
padding-top: 0.6em;
padding-right: 1em;
padding-bottom: 0.6em;
padding-left: 1em;
border-bottom: 1px solid;
margin: 0;
border-bottom-color: inherit;
}
.usercard {
width: -webkit-fill-available;
width: -moz-webkit-fill-available;
stretch: fill;
margin-left: 0.7em;
}
</style>

View file

@ -40,17 +40,20 @@
</div> </div>
<div class="panel-body profile-panel-body" :style="bodyStyle"> <div class="panel-body profile-panel-body" :style="bodyStyle">
<div class="user-counts"> <div class="user-counts">
<div class="user-count"> <div class="user-count base04">
<h5>Statuses</h5> <a href="#" v-on:click.prevent="setProfileView('statuses')" v-if="switcher"><h5 class="base05">Statuses</h5></a>
<span>{{user.statuses_count}} <br><span class="dailyAvg">{{dailyAvg}} per day</span></span> <h5 v-else>Statuses</h5>
<span class="base05">{{user.statuses_count}} <br><span class="dailyAvg">{{dailyAvg}} per day</span></span>
</div> </div>
<div class="user-count"> <div class="user-count">
<h5>Following</h5> <a href="#" v-on:click.prevent="setProfileView('friends')" v-if="switcher"><h5 class="base05">Following</h5></a>
<span>{{user.friends_count}}</span> <h5 v-else>Following</h5>
<span class="base05">{{user.friends_count}}</span>
</div> </div>
<div class="user-count"> <div class="user-count">
<h5>Followers</h5> <a href="#" v-on:click.prevent="setProfileView('followers')" v-if="switcher"><h5 class="base05">Followers</h5></a>
<span>{{user.followers_count}}</span> <h5 v-else>Followers</h5>
<span class="base05">{{user.followers_count}}</span>
</div> </div>
</div> </div>
<p>{{user.description}}</p> <p>{{user.description}}</p>
@ -60,7 +63,7 @@
<script> <script>
export default { export default {
props: [ 'user' ], props: [ 'user', 'switcher' ],
computed: { computed: {
headingStyle () { headingStyle () {
let color = this.$store.state.config.colors['base00'] let color = this.$store.state.config.colors['base00']
@ -103,12 +106,17 @@
const store = this.$store const store = this.$store
store.commit('setMuted', {user: this.user, muted: !this.user.muted}) store.commit('setMuted', {user: this.user, muted: !this.user.muted})
store.state.api.backendInteractor.setUserMute(this.user) store.state.api.backendInteractor.setUserMute(this.user)
},
setProfileView (v) {
const store = this.$store
store.commit('setProfileView', { v })
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss';
.profile-panel-background { .profile-panel-background {
background-size: cover; background-size: cover;
@ -230,6 +238,9 @@
font-weight: bolder; font-weight: bolder;
margin: 0 0 0.25em; margin: 0 0 0.25em;
} }
a {
text-decoration: none;
}
} }
.dailyAvg { .dailyAvg {

View file

@ -2,7 +2,7 @@
<div class="user-panel"> <div class="user-panel">
<div v-if='user' class="panel panel-default"> <div v-if='user' class="panel panel-default">
<user-card-content :user="user"></user-card-content> <user-card-content :user="user" :switcher="false"></user-card-content>
<div class="panel-footer base00-background"> <div class="panel-footer base00-background">
<post-status-form v-if='user'></post-status-form> <post-status-form v-if='user'></post-status-form>

View file

@ -20,11 +20,11 @@ const UserProfile = {
} else { } else {
return false return false
} }
} },
}, },
components: { components: {
UserCardContent, UserCardContent,
Timeline Timeline,
} }
} }

View file

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div v-if="user" class="user-profile panel panel-default base00-background"> <div v-if="user" class="user-profile panel panel-default base00-background">
<user-card-content :user="user"></user-card-content> <user-card-content :user="user" :switcher="true"></user-card-content>
</div> </div>
<Timeline :title="'User Timeline'" v-bind:timeline="timeline" v-bind:timeline-name="'user'" :user-id="userId"/> <Timeline :title="'User Timeline'" v-bind:timeline="timeline" v-bind:timeline-name="'user'" :user-id="userId"/>
</div> </div>

View file

@ -19,7 +19,10 @@ export const defaultState = {
newStatusCount: 0, newStatusCount: 0,
maxId: 0, maxId: 0,
minVisibleId: 0, minVisibleId: 0,
loading: false loading: false,
followers: [],
friends: [],
viewing: 'statuses'
}, },
public: { public: {
statuses: [], statuses: [],
@ -30,7 +33,10 @@ export const defaultState = {
newStatusCount: 0, newStatusCount: 0,
maxId: 0, maxId: 0,
minVisibleId: 0, minVisibleId: 0,
loading: false loading: false,
followers: [],
friends: [],
viewing: 'statuses'
}, },
user: { user: {
statuses: [], statuses: [],
@ -41,7 +47,10 @@ export const defaultState = {
newStatusCount: 0, newStatusCount: 0,
maxId: 0, maxId: 0,
minVisibleId: 0, minVisibleId: 0,
loading: false loading: false,
followers: [],
friends: [],
viewing: 'statuses'
}, },
publicAndExternal: { publicAndExternal: {
statuses: [], statuses: [],
@ -52,7 +61,10 @@ export const defaultState = {
newStatusCount: 0, newStatusCount: 0,
maxId: 0, maxId: 0,
minVisibleId: 0, minVisibleId: 0,
loading: false loading: false,
followers: [],
friends: [],
viewing: 'statuses'
}, },
friends: { friends: {
statuses: [], statuses: [],
@ -63,7 +75,10 @@ export const defaultState = {
newStatusCount: 0, newStatusCount: 0,
maxId: 0, maxId: 0,
minVisibleId: 0, minVisibleId: 0,
loading: false loading: false,
followers: [],
friends: [],
viewing: 'statuses'
} }
} }
} }
@ -316,7 +331,10 @@ export const mutations = {
newStatusCount: 0, newStatusCount: 0,
maxId: 0, maxId: 0,
minVisibleId: 0, minVisibleId: 0,
loading: false loading: false,
followers: [],
friends: [],
viewing: 'statuses'
} }
state.timelines[timeline] = emptyTimeline state.timelines[timeline] = emptyTimeline
@ -343,6 +361,16 @@ export const mutations = {
setError (state, { value }) { setError (state, { value }) {
state.error = value state.error = value
}, },
setProfileView (state, { v }) {
// load followers / friends only when needed
state.timelines['user'].viewing = v
},
addFriends (state, { friends }) {
state.timelines['user'].friends = friends
},
addFollowers (state, { followers }) {
state.timelines['user'].followers = followers
},
markNotificationsAsSeen (state, notifications) { markNotificationsAsSeen (state, notifications) {
each(notifications, (notification) => { each(notifications, (notification) => {
notification.seen = true notification.seen = true
@ -359,6 +387,12 @@ const statuses = {
setError ({ rootState, commit }, { value }) { setError ({ rootState, commit }, { value }) {
commit('setError', { value }) commit('setError', { value })
}, },
addFriends ({ rootState, commit }, { friends }) {
commit('addFriends', { friends })
},
addFollowers ({ rootState, commit }, { followers }) {
commit('addFollowers', { followers })
},
deleteStatus ({ rootState, commit }, status) { deleteStatus ({ rootState, commit }, status) {
commit('setDeleted', { status }) commit('setDeleted', { status })
apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials }) apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials })

View file

@ -13,6 +13,7 @@ const STATUS_URL = '/api/statuses/show'
const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload' const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload'
const CONVERSATION_URL = '/api/statusnet/conversation' const CONVERSATION_URL = '/api/statusnet/conversation'
const MENTIONS_URL = '/api/statuses/mentions.json' const MENTIONS_URL = '/api/statuses/mentions.json'
const FOLLOWERS_URL = '/api/statuses/followers.json'
const FRIENDS_URL = '/api/statuses/friends.json' const FRIENDS_URL = '/api/statuses/friends.json'
const FOLLOWING_URL = '/api/friendships/create.json' const FOLLOWING_URL = '/api/friendships/create.json'
const UNFOLLOWING_URL = '/api/friendships/destroy.json' const UNFOLLOWING_URL = '/api/friendships/destroy.json'
@ -114,8 +115,15 @@ const unfollowUser = ({id, credentials}) => {
}).then((data) => data.json()) }).then((data) => data.json())
} }
const fetchFriends = ({credentials}) => { const fetchFriends = ({id, credentials}) => {
return fetch(FRIENDS_URL, { headers: authHeaders(credentials) }) let url = `${FRIENDS_URL}?user_id=${id}`
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json())
}
const fetchFollowers = ({id, credentials}) => {
let url = `${FOLLOWERS_URL}?user_id=${id}`
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json()) .then((data) => data.json())
} }
@ -169,7 +177,6 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
if (since) { if (since) {
params.push(['since_id', since]) params.push(['since_id', since])
} }
if (until) { if (until) {
params.push(['max_id', until]) params.push(['max_id', until])
} }
@ -261,6 +268,7 @@ const apiService = {
fetchConversation, fetchConversation,
fetchStatus, fetchStatus,
fetchFriends, fetchFriends,
fetchFollowers,
followUser, followUser,
unfollowUser, unfollowUser,
favorite, favorite,

View file

@ -10,8 +10,12 @@ const backendInteractorService = (credentials) => {
return apiService.fetchConversation({id, credentials}) return apiService.fetchConversation({id, credentials})
} }
const fetchFriends = () => { const fetchFriends = ({id}) => {
return apiService.fetchFriends({credentials}) return apiService.fetchFriends({id, credentials})
}
const fetchFollowers = ({id}) => {
return apiService.fetchFollowers({id, credentials})
} }
const fetchAllFollowing = ({username}) => { const fetchAllFollowing = ({username}) => {
@ -44,6 +48,7 @@ const backendInteractorService = (credentials) => {
fetchStatus, fetchStatus,
fetchConversation, fetchConversation,
fetchFriends, fetchFriends,
fetchFollowers,
followUser, followUser,
unfollowUser, unfollowUser,
fetchAllFollowing, fetchAllFollowing,