Add Chats

This commit is contained in:
eugenijm 2020-05-07 16:10:53 +03:00
parent a0ddcbdf5b
commit aa2cf51c05
69 changed files with 2794 additions and 161 deletions

View file

@ -1,4 +1,5 @@
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { WSConnectionStatus } from '../services/api/api.service.js'
import { Socket } from 'phoenix'
const api = {
@ -7,6 +8,7 @@ const api = {
fetchers: {},
socket: null,
mastoUserSocket: null,
mastoUserSocketStatus: null,
followRequests: []
},
mutations: {
@ -28,6 +30,9 @@ const api = {
},
setFollowRequests (state, value) {
state.followRequests = value
},
setMastoUserSocketStatus (state, value) {
state.mastoUserSocketStatus = value
}
},
actions: {
@ -47,7 +52,7 @@ const api = {
startMastoUserSocket (store) {
return new Promise((resolve, reject) => {
try {
const { state, dispatch, rootState } = store
const { state, commit, dispatch, rootState } = store
const timelineData = rootState.statuses.timelines.friends
state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
state.mastoUserSocket.addEventListener(
@ -66,11 +71,22 @@ const api = {
showImmediately: timelineData.visibleStatuses.length === 0,
timeline: 'friends'
})
} else if (message.event === 'pleroma:chat_update') {
dispatch('addChatMessages', {
chatId: message.chatUpdate.id,
messages: [message.chatUpdate.lastMessage]
})
dispatch('updateChat', { chat: message.chatUpdate })
}
}
)
state.mastoUserSocket.addEventListener('open', () => {
commit('setMastoUserSocketStatus', WSConnectionStatus.JOINED)
})
state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
console.error('Error in MastoAPI websocket:', error)
commit('setMastoUserSocketStatus', WSConnectionStatus.ERROR)
dispatch('clearOpenedChats')
})
state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
const ignoreCodes = new Set([
@ -84,8 +100,11 @@ const api = {
console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
dispatch('startFetchingTimeline', { timeline: 'friends' })
dispatch('startFetchingNotifications')
dispatch('startFetchingChats')
dispatch('restartMastoUserSocket')
}
commit('setMastoUserSocketStatus', WSConnectionStatus.CLOSED)
dispatch('clearOpenedChats')
})
resolve()
} catch (e) {
@ -99,12 +118,13 @@ const api = {
return dispatch('startMastoUserSocket').then(() => {
dispatch('stopFetchingTimeline', { timeline: 'friends' })
dispatch('stopFetchingNotifications')
dispatch('stopFetchingChats')
})
},
stopMastoUserSocket ({ state, dispatch }) {
dispatch('startFetchingTimeline', { timeline: 'friends' })
dispatch('startFetchingNotifications')
console.log(state.mastoUserSocket)
dispatch('startFetchingChats')
state.mastoUserSocket.close()
},

228
src/modules/chats.js Normal file
View file

@ -0,0 +1,228 @@
import Vue from 'vue'
import { find, omitBy, orderBy, sumBy } from 'lodash'
import chatService from '../services/chat_service/chat_service.js'
import { parseChat, parseChatMessage } from '../services/entity_normalizer/entity_normalizer.service.js'
const emptyChatList = () => ({
data: [],
idStore: {}
})
const defaultState = {
chatList: emptyChatList(),
chatListFetcher: null,
openedChats: {},
openedChatMessageServices: {},
fetcher: undefined,
currentChatId: null
}
const getChatById = (state, id) => {
return find(state.chatList.data, { id })
}
const sortedChatList = (state) => {
return orderBy(state.chatList.data, ['updated_at'], ['desc'])
}
const unreadChatCount = (state) => {
return sumBy(state.chatList.data, 'unread')
}
const chats = {
state: { ...defaultState },
getters: {
currentChat: state => state.openedChats[state.currentChatId],
currentChatMessageService: state => state.openedChatMessageServices[state.currentChatId],
findOpenedChatByRecipientId: state => recipientId => find(state.openedChats, c => c.account.id === recipientId),
sortedChatList,
unreadChatCount
},
actions: {
// Chat list
startFetchingChats ({ dispatch, commit }) {
const fetcher = () => {
dispatch('fetchChats', { latest: true })
}
fetcher()
commit('setChatListFetcher', {
fetcher: () => setInterval(() => { fetcher() }, 5000)
})
},
stopFetchingChats ({ commit }) {
commit('setChatListFetcher', { fetcher: undefined })
},
fetchChats ({ dispatch, rootState, commit }, params = {}) {
return rootState.api.backendInteractor.chats()
.then(({ chats }) => {
dispatch('addNewChats', { chats })
return chats
})
},
addNewChats ({ rootState, commit, dispatch, rootGetters }, { chats }) {
commit('addNewChats', { dispatch, chats, rootGetters })
},
updateChat ({ commit }, { chat }) {
commit('updateChat', { chat })
},
// Opened Chats
startFetchingCurrentChat ({ commit, dispatch }, { fetcher }) {
dispatch('setCurrentChatFetcher', { fetcher })
},
setCurrentChatFetcher ({ rootState, commit }, { fetcher }) {
commit('setCurrentChatFetcher', { fetcher })
},
addOpenedChat ({ rootState, commit, dispatch }, { chat }) {
commit('addOpenedChat', { dispatch, chat: parseChat(chat) })
dispatch('addNewUsers', [chat.account])
},
addChatMessages ({ commit }, value) {
commit('addChatMessages', { commit, ...value })
},
resetChatNewMessageCount ({ commit }, value) {
commit('resetChatNewMessageCount', value)
},
removeFromCurrentChatStatuses ({ commit }, { id }) {
commit('removeFromCurrentChatStatuses', id)
},
clearCurrentChat ({ rootState, commit, dispatch }, value) {
commit('setCurrentChatId', { chatId: undefined })
commit('setCurrentChatFetcher', { fetcher: undefined })
},
readChat ({ rootState, commit, dispatch }, { id, lastReadId }) {
dispatch('resetChatNewMessageCount')
commit('readChat', { id })
rootState.api.backendInteractor.readChat({ id, lastReadId })
},
deleteChatMessage ({ rootState, commit }, value) {
rootState.api.backendInteractor.deleteChatMessage(value)
commit('deleteChatMessage', { commit, ...value })
},
resetChats ({ commit, dispatch }) {
dispatch('clearCurrentChat')
commit('resetChats', { commit })
},
clearOpenedChats ({ rootState, commit, dispatch, rootGetters }) {
commit('clearOpenedChats', { commit })
}
},
mutations: {
setChatListFetcher (state, { commit, fetcher }) {
const prevFetcher = state.chatListFetcher
if (prevFetcher) {
clearInterval(prevFetcher)
}
state.chatListFetcher = fetcher && fetcher()
},
setCurrentChatFetcher (state, { fetcher }) {
const prevFetcher = state.fetcher
if (prevFetcher) {
clearInterval(prevFetcher)
}
state.fetcher = fetcher && fetcher()
},
addOpenedChat (state, { _dispatch, chat }) {
state.currentChatId = chat.id
Vue.set(state.openedChats, chat.id, chat)
if (!state.openedChatMessageServices[chat.id]) {
Vue.set(state.openedChatMessageServices, chat.id, chatService.empty(chat.id))
}
},
setCurrentChatId (state, { chatId }) {
state.currentChatId = chatId
},
addNewChats (state, { _dispatch, chats, _rootGetters }) {
chats.forEach((updatedChat) => {
const chat = getChatById(state, updatedChat.id)
if (chat) {
chat.lastMessage = updatedChat.lastMessage
chat.unread = updatedChat.unread
} else {
state.chatList.data.push(updatedChat)
Vue.set(state.chatList.idStore, updatedChat.id, updatedChat)
}
})
},
updateChat (state, { _dispatch, chat: updatedChat, _rootGetters }) {
const chat = getChatById(state, updatedChat.id)
if (chat) {
chat.lastMessage = updatedChat.lastMessage
chat.unread = updatedChat.unread
chat.updated_at = updatedChat.updated_at
}
if (!chat) { state.chatList.data.unshift(updatedChat) }
Vue.set(state.chatList.idStore, updatedChat.id, updatedChat)
},
deleteChat (state, { _dispatch, id, _rootGetters }) {
state.chats.data = state.chats.data.filter(conversation =>
conversation.last_status.id !== id
)
state.chats.idStore = omitBy(state.chats.idStore, conversation => conversation.last_status.id === id)
},
resetChats (state, { commit }) {
state.chatList = emptyChatList()
state.currentChatId = null
commit('setChatListFetcher', { fetcher: undefined })
for (const chatId in state.openedChats) {
chatService.clear(state.openedChatMessageServices[chatId])
Vue.delete(state.openedChats, chatId)
Vue.delete(state.openedChatMessageServices, chatId)
}
},
setChatsLoading (state, { value }) {
state.chats.loading = value
},
addChatMessages (state, { commit, chatId, messages }) {
const chatMessageService = state.openedChatMessageServices[chatId]
if (chatMessageService) {
chatService.add(chatMessageService, { messages: messages.map(parseChatMessage) })
commit('refreshLastMessage', { chatId })
}
},
refreshLastMessage (state, { chatId }) {
const chatMessageService = state.openedChatMessageServices[chatId]
if (chatMessageService) {
const chat = getChatById(state, chatId)
if (chat) {
chat.lastMessage = chatMessageService.lastMessage
if (chatMessageService.lastMessage) {
chat.updated_at = chatMessageService.lastMessage.created_at
}
}
}
},
deleteChatMessage (state, { commit, chatId, messageId }) {
const chatMessageService = state.openedChatMessageServices[chatId]
if (chatMessageService) {
chatService.deleteMessage(chatMessageService, messageId)
commit('refreshLastMessage', { chatId })
}
},
resetChatNewMessageCount (state, _value) {
const chatMessageService = state.openedChatMessageServices[state.currentChatId]
chatService.resetNewMessageCount(chatMessageService)
},
// Used when a connection loss occurs
clearOpenedChats (state) {
const currentChatId = state.currentChatId
for (const chatId in state.openedChats) {
if (currentChatId !== chatId) {
chatService.clear(state.openedChatMessageServices[chatId])
Vue.delete(state.openedChats, chatId)
Vue.delete(state.openedChatMessageServices, chatId)
}
}
},
readChat (state, { id }) {
const chat = getChatById(state, id)
if (chat) {
chat.unread = 0
}
}
}
}
export default chats

View file

@ -46,7 +46,8 @@ export const defaultState = {
repeats: true,
moves: true,
emojiReactions: false,
followRequest: true
followRequest: true,
chatMention: true
},
webPushNotifications: false,
muteWords: [],

View file

@ -55,6 +55,7 @@ const defaultState = {
// Feature-set, apparently, not everything here is reported...
chatAvailable: false,
pleromaChatMessagesAvailable: false,
gopherAvailable: false,
mediaProxyAvailable: false,
suggestionsEnabled: false,

View file

@ -15,7 +15,8 @@ const defaultState = {
)
},
mobileLayout: false,
globalNotices: []
globalNotices: [],
layoutHeight: 0
}
const interfaceMod = {
@ -65,6 +66,9 @@ const interfaceMod = {
},
removeGlobalNotice (state, notice) {
state.globalNotices = state.globalNotices.filter(n => n !== notice)
},
setLayoutHeight (state, value) {
state.layoutHeight = value
}
},
actions: {
@ -110,6 +114,9 @@ const interfaceMod = {
},
removeGlobalNotice ({ commit }, notice) {
commit('removeGlobalNotice', notice)
},
setLayoutHeight ({ commit }, value) {
commit('setLayoutHeight', value)
}
}
}

View file

@ -478,7 +478,7 @@ export const mutations = {
},
setDeleted (state, { status }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.deleted = true
if (newStatus) newStatus.deleted = true
},
setManyDeleted (state, condition) {
Object.values(state.allStatusesObject).forEach(status => {
@ -521,6 +521,9 @@ export const mutations = {
dismissNotification (state, { id }) {
state.notifications.data = state.notifications.data.filter(n => n.id !== id)
},
dismissNotifications (state, { finder }) {
state.notifications.data = state.notifications.data.filter(n => finder)
},
updateNotification (state, { id, updater }) {
const notification = find(state.notifications.data, n => n.id === id)
notification && updater(notification)

View file

@ -498,6 +498,7 @@ const users = {
store.dispatch('stopFetchingFollowRequests')
store.commit('clearNotifications')
store.commit('resetStatuses')
store.dispatch('resetChats')
})
},
loginUser (store, accessToken) {
@ -537,6 +538,9 @@ const users = {
// Start fetching notifications
store.dispatch('startFetchingNotifications')
// Start fetching chats
store.dispatch('startFetchingChats')
}
if (store.getters.mergedConfig.useStreamingApi) {
@ -544,6 +548,7 @@ const users = {
console.error('Failed initializing MastoAPI Streaming socket', error)
startPolling()
}).then(() => {
store.dispatch('fetchChats', { latest: true })
setTimeout(() => store.dispatch('setNotificationsSilence', false), 10000)
})
} else {