diff --git a/frontend/src/premium/eagle-eye/components/list/eagle-eye-email-digest-card.vue b/frontend/src/premium/eagle-eye/components/list/eagle-eye-email-digest-card.vue index 7e59452032..9433e830c7 100644 --- a/frontend/src/premium/eagle-eye/components/list/eagle-eye-email-digest-card.vue +++ b/frontend/src/premium/eagle-eye/components/list/eagle-eye-email-digest-card.vue @@ -11,8 +11,8 @@
- Receive automatically in your inbox a collection of up - to 10 most relevant results from Eagle Eye. + Receive the 10 most relevant results from Eagle Eye + automatically in your inbox.
+ + +
store.getters['eagleEye/activeView'] +) const loading = computed( - () => store.state.eagleEye.list.loading + () => + store.state.eagleEye.views[activeView.value.id].list + .loading +) +const count = computed( + () => + store.state.eagleEye.views[activeView.value.id].count ) -const count = computed(() => store.state.eagleEye.count) const pagination = computed( () => store.getters['eagleEye/pagination'] ) const isLoadMoreVisible = computed(() => { + if (activeView.value.id === 'feed') { + return false + } + return ( pagination.value.currentPage * pagination.value.pageSize < @@ -80,4 +100,8 @@ const onLoadMore = () => { pagination.value.currentPage + 1 ) } + +const showBottomFeedMessage = computed(() => { + return activeView.value.id === 'feed' +}) diff --git a/frontend/src/premium/eagle-eye/components/list/eagle-eye-loading-state.vue b/frontend/src/premium/eagle-eye/components/list/eagle-eye-loading-state.vue index 56c81ea4b9..2dde923cfe 100644 --- a/frontend/src/premium/eagle-eye/components/list/eagle-eye-loading-state.vue +++ b/frontend/src/premium/eagle-eye/components/list/eagle-eye-loading-state.vue @@ -1,6 +1,6 @@ @@ -83,37 +113,40 @@ const { currentUser } = mapGetters('auth') const settingsDrawerOpen = ref(false) -const keywords = computed(() => { - const { eagleEyeSettings } = currentUser.value - - if (!eagleEyeSettings?.feed) { - return [] - } - - return eagleEyeSettings.feed.keywords +const eagleEyeFeedSettings = computed(() => { + return currentUser.value.eagleEyeSettings?.feed }) -const exactKeywords = computed(() => { - const { eagleEyeSettings } = currentUser.value +const keywords = computed( + () => eagleEyeFeedSettings.value.keywords +) - if (!eagleEyeSettings?.feed) { - return [] - } +const exactKeywords = computed( + () => eagleEyeFeedSettings.value.exactKeywords +) - return eagleEyeSettings.feed.exactKeywords -}) -const platforms = computed(() => { - const { eagleEyeSettings } = currentUser.value +const excludedKeywords = computed( + () => eagleEyeFeedSettings.value.excludedKeywords +) - if (!eagleEyeSettings?.feed) { - return [] - } +const platforms = computed( + () => eagleEyeFeedSettings.value.platforms +) - return eagleEyeSettings.feed.platforms -}) +const publishedDate = computed( + () => eagleEyeFeedSettings.value.publishedDate +) diff --git a/frontend/src/premium/eagle-eye/eagle-eye-service.js b/frontend/src/premium/eagle-eye/eagle-eye-service.js index bbc15f1f7a..bbc7620013 100644 --- a/frontend/src/premium/eagle-eye/eagle-eye-service.js +++ b/frontend/src/premium/eagle-eye/eagle-eye-service.js @@ -53,12 +53,12 @@ export class EagleEyeService { return response.data } - static async addAction({ postId, actionData }) { + static async addAction({ postId, action }) { const tenantId = AuthCurrentTenant.get() const response = await authAxios.post( `/tenant/${tenantId}/eagleEyeContent/${postId}/action`, - actionData + action ) return response.data diff --git a/frontend/src/premium/eagle-eye/pages/eagle-eye-page.vue b/frontend/src/premium/eagle-eye/pages/eagle-eye-page.vue index 218099dc21..26243af5ae 100644 --- a/frontend/src/premium/eagle-eye/pages/eagle-eye-page.vue +++ b/frontend/src/premium/eagle-eye/pages/eagle-eye-page.vue @@ -2,27 +2,30 @@
- - - - +
+ + + + +
@@ -49,6 +52,15 @@ const store = useStore() const { activeView, activeViewList } = mapGetters('eagleEye') +const cssVars = computed(() => { + const isMenuCollapsed = + store.getters['layout/menuCollapsed'] + const menuWidth = isMenuCollapsed ? '64px' : '260px' + + return { + '--eagle-eye-padding': menuWidth + } +}) const list = computed(() => activeViewList.value.posts) const isLoading = computed(() => { if (activeView.value.id === 'feed') { @@ -86,3 +98,19 @@ onMounted(async () => { } }) + + diff --git a/frontend/src/premium/eagle-eye/store/actions.js b/frontend/src/premium/eagle-eye/store/actions.js index 4aabde6c87..761500db41 100644 --- a/frontend/src/premium/eagle-eye/store/actions.js +++ b/frontend/src/premium/eagle-eye/store/actions.js @@ -8,6 +8,7 @@ import { shouldFetchNewResults, isStorageUpdating } from '@/premium/eagle-eye/eagle-eye-storage' +import Message from '@/shared/message/message' export default { ...sharedActions('eagleEye'), @@ -129,105 +130,232 @@ export default { } }, + // Add temporary actions to post so that UI updates immeadiately + async doAddTemporaryPostAction( + { commit, getters }, + { index, storeActionType, action } + ) { + const activeView = getters.activeView.id + + // Add new action + if (storeActionType === 'add') { + commit('CREATE_TEMPORARY_ACTION', { + action, + activeView, + index + }) + // Remove action + } else { + commit('REMOVE_TEMPORARY_ACTION', { + action, + activeView, + index + }) + } + }, + + // Add or remove actions from the database depending on the action type + async doUpdatePostAction( + { state, dispatch, getters }, + { post, index, storeActionType, actionType } + ) { + const activeView = getters.activeView.id + const action = state.views[activeView].list.posts[ + index + ].actions.find((a) => a.type === actionType) || { + type: actionType, + timestamp: moment() + } + // Add new action + if (storeActionType === 'add') { + await dispatch('doAddAction', { + post, + action, + index + }) + // Remove action + } else { + await dispatch('doRemoveAction', { + postId: post.id, + action, + index + }) + } + }, + async doAddAction( { state, commit, getters, rootGetters }, { post, action, index } ) { const activeView = getters.activeView.id + const oppositeActionTypes = { + ['thumbs-up']: 'thumbs-down', + ['thumbs-down']: 'thumbs-up' + } + const oppositeAction = state.views[ + activeView + ].list.posts[index].actions.find( + (a) => a.type === oppositeActionTypes[action.type] + ) - try { - commit('UPDATE_ACTION_LOADING', { - index, - activeView + // If action is thumbs, delete opposite thumbs from post + if ( + oppositeActionTypes[action.type] && + oppositeAction + ) { + commit('REMOVE_TEMPORARY_ACTION', { + action: oppositeAction, + activeView, + index }) - // Create content in database - const postResponse = - await EagleEyeService.createContent({ - post - }) - - commit('CREATE_CONTENT_SUCCESS', { - post: postResponse, - index, - activeView + await EagleEyeService.deleteAction({ + postId: post.id, + actionId: oppositeAction.id }) + } - // Add action to db content - const actionData = { - type: action, - timestamp: moment().utc().format('YYYY-MM-DD') + const postDb = await EagleEyeService.createContent({ + post: { + actions: [], + platform: post.platform, + post: post.post, + postedAt: post.postedAt, + url: post.url } + }) - const actionResponse = - await EagleEyeService.addAction({ - postId: postResponse.id, - actionData - }) + const actionDb = await EagleEyeService.addAction({ + postId: postDb.id, + action + }) - commit('CREATE_ACTION_SUCCESS', { - action: actionResponse, - index, - activeView - }) + commit('CREATE_ACTION_SUCCESS', { + post: postDb, + action: actionDb, + index, + activeView + }) - // Update local storage with updated action - const currentUser = rootGetters['auth/currentUser'] - const currentTenant = - rootGetters['auth/currentTenant'] + // Update posts with new actions in local storage + const currentUser = rootGetters['auth/currentUser'] + const currentTenant = rootGetters['auth/currentTenant'] - setResultsInStorage({ - posts: state.list.posts, - tenantId: currentTenant.id, - userId: currentUser.id - }) - } catch (error) { - commit('UPDATE_ACTION_ERROR', { - index, - activeView - }) - } + setResultsInStorage({ + posts: state.views['feed'].list.posts, + storageDate: moment(), + tenantId: currentTenant.id, + userId: currentUser.id + }) }, async doRemoveAction( { state, commit, getters, rootGetters }, - { postId, actionId, actionType, index } + { postId, action, index } ) { const activeView = getters.activeView.id + const actionId = action.id + const deleteImmeadiately = + activeView === 'bookmarked' && + action.type === 'bookmark' - try { - commit('UPDATE_ACTION_LOADING', { + if (deleteImmeadiately) { + commit('REMOVE_ACTION_SUCCESS', { + postId, + action, index, activeView }) + } - await EagleEyeService.deleteAction({ - postId, - actionId - }) + await EagleEyeService.deleteAction({ + postId, + actionId + }) - commit('DELETE_ACTION_SUCCESS', { - actionId, - actionType, + if (!deleteImmeadiately) { + commit('REMOVE_ACTION_SUCCESS', { + postId, + action, index, activeView }) + } - // Update local storage with updated action - const currentUser = rootGetters['auth/currentUser'] - const currentTenant = - rootGetters['auth/currentTenant'] + // Update posts with new actions in local storage + const currentUser = rootGetters['auth/currentUser'] + const currentTenant = rootGetters['auth/currentTenant'] - setResultsInStorage({ - posts: state.list.posts, - tenantId: currentTenant.id, - userId: currentUser.id - }) - } catch (error) { - commit('UPDATE_ACTION_ERROR', { index, activeView }) + setResultsInStorage({ + posts: state.views['feed'].list.posts, + storageDate: moment(), + tenantId: currentTenant.id, + userId: currentUser.id + }) + }, + + doAddActionQueue({ commit, state, dispatch }, job) { + commit('ADD_PENDING_ACTION', job) + + // If there are no actions active, start the next one in the queue + if (Object.keys(state.activeAction).length == 0) { + dispatch('doStartActionQueue') } }, + async doStartActionQueue({ + commit, + dispatch, + state, + getters, + rootGetters + }) { + if (state.pendingActions.length > 0) { + commit('SET_ACTIVE_ACTION', state.pendingActions[0]) + + const pendingAction = { ...state.pendingActions[0] } + + commit('POP_CURRENT_ACTION') + + try { + await pendingAction.handler() + await dispatch('doStartActionQueue') + } catch (error) { + // In case of an error, create post again and update it in UI + EagleEyeService.createContent({ + post: pendingAction.post + }).then((response) => { + const activeView = getters.activeView.id + const currentUser = + rootGetters['auth/currentUser'] + const currentTenant = + rootGetters['auth/currentTenant'] + + commit('UPDATE_POST', { + activeView, + index: pendingAction.index, + post: response + }) + + // Update posts with new actions in local storage + setResultsInStorage({ + posts: state.views['feed'].list.posts, + storageDate: moment(), + tenantId: currentTenant.id, + userId: currentUser.id + }) + }) + + Message.error( + 'Something went wrong. Please try again' + ) + commit('SET_ACTIVE_ACTION', {}) + } + } + + commit('SET_ACTIVE_ACTION', {}) + }, + async doUpdateSettings({ commit, dispatch }, data) { commit('UPDATE_EAGLE_EYE_SETTINGS_STARTED') return EagleEyeService.updateSettings(data) diff --git a/frontend/src/premium/eagle-eye/store/getters.js b/frontend/src/premium/eagle-eye/store/getters.js index 2ebedde6fe..b52596f2df 100644 --- a/frontend/src/premium/eagle-eye/store/getters.js +++ b/frontend/src/premium/eagle-eye/store/getters.js @@ -1,4 +1,5 @@ import sharedGetters from '@/shared/store/getters' +import { INITIAL_PAGE_SIZE } from './constants' export default { ...sharedGetters(), @@ -7,5 +8,35 @@ export default { const activeView = sharedGetters().activeView(state) return state.views[activeView.id].list + }, + + pagination: (state, getters) => { + return { + ...getters.activeView.pagination, + total: getters.activeView.count, + showSizeChanger: true + } + }, + + limit: (state, getters) => { + const { pagination } = getters.activeView + + if (!pagination?.pageSize) { + return INITIAL_PAGE_SIZE + } + + return pagination.pageSize + }, + + offset: (state, getters) => { + const { pagination } = getters.activeView + + if (!pagination?.pageSize) { + return 0 + } + + const { currentPage = 1 } = pagination + + return (currentPage - 1) * pagination.pageSize } } diff --git a/frontend/src/premium/eagle-eye/store/mutations.js b/frontend/src/premium/eagle-eye/store/mutations.js index 2a2552f21f..a1d399bb2a 100644 --- a/frontend/src/premium/eagle-eye/store/mutations.js +++ b/frontend/src/premium/eagle-eye/store/mutations.js @@ -24,11 +24,11 @@ export default { ) { state.views[activeView].list.loading = false if (appendToList) { - state.views[activeView].list.posts.concat(list) + state.views[activeView].list.posts.push(...list) } else { state.views[activeView].list.posts = list } - state.count = count + state.views[activeView].count = count }, FETCH_ERROR(state, { activeView }) { @@ -37,64 +37,128 @@ export default { state.views[activeView].count = 0 }, - UPDATE_ACTION_LOADING(state, { index, activeView }) { - state.views[activeView].list.posts[index].loading = true - }, - - CREATE_CONTENT_SUCCESS( + CREATE_ACTION_SUCCESS( state, - { post, index, activeView } + { post, action, index, activeView } ) { - state.views[activeView].list.posts[index] = { - ...state.views[activeView].list.posts[index], - ...post + // Update feed post if bookmark action is updated + if (activeView === 'bookmarked') { + const feedPost = state.views['feed'].list.posts.find( + (p) => p.url === post.url + ) + + if (feedPost) { + const indexAction = feedPost.actions.findIndex( + (a) => a.type === action.type + ) + + if (indexAction === -1) { + feedPost.actions.push(action) + } else { + feedPost.actions[indexAction] = { + ...action, + id: action.id + } + } + } } - }, - CREATE_ACTION_SUCCESS( - state, - { action, index, activeView } - ) { - state.views[activeView].list.posts[ - index - ].loading = false - state.views[activeView].list.posts[index].actions.push( - action + const { actions } = + state.views[activeView].list.posts[index] + const indexAction = actions.findIndex( + (a) => a.type === action.type ) + + // Update store post with new one, except for actions + state.views[activeView].list.posts[index] = { + ...post, + actions + } + + if (indexAction === -1) { + actions.push(action) + } else { + actions[indexAction] = { + ...action, + id: action.id + } + } }, - DELETE_ACTION_SUCCESS( + REMOVE_ACTION_SUCCESS( state, - { actionId, actionType, index, activeView } + { postId, action, index, activeView } ) { - state.views[activeView].list.posts[ - index - ].loading = false + // Update feed post if bookmark action is updated + if (activeView === 'bookmarked') { + const feedPost = state.views['feed'].list.posts.find( + (p) => p.id === postId + ) + + if (feedPost) { + const deleteIndex = feedPost.actions.findIndex( + (a) => a.type === action.type + ) + + if (deleteIndex !== -1) { + feedPost.actions.splice(deleteIndex, 1) + console.log(feedPost.actions) + } + } + } // Remove post from bookmarks view if ( - actionType === 'bookmark' && + action.type === 'bookmark' && activeView === 'bookmarked' ) { state.views[activeView].list.posts.splice(index, 1) - // Remove action from post } else { - const deleteIndex = state.views[ - activeView - ].list.posts[index].actions.findIndex( - (a) => a.id === actionId + // Remove action from post + const { actions } = + state.views[activeView].list.posts[index] + const deleteIndex = actions.findIndex( + (a) => a.type === action.type ) - state.views[activeView].list.posts[ - index - ].actions.splice(deleteIndex, 1) + if (deleteIndex !== -1) { + actions.splice(deleteIndex, 1) + } } }, - UPDATE_ACTION_ERROR(state, { index, activeView }) { - state.views[activeView].list.posts[ - index - ].loading = false + CREATE_TEMPORARY_ACTION( + state, + { action, activeView, index } + ) { + const { actions } = + state.views[activeView].list.posts[index] + const indexAction = actions.findIndex( + (a) => a.type === action.type + ) + + if (indexAction === -1) { + actions.push(action) + } else { + actions[indexAction] = action + } + }, + + REMOVE_TEMPORARY_ACTION( + state, + { action, activeView, index } + ) { + const { actions } = + state.views[activeView].list.posts[index] + const deleteIndex = actions.findIndex( + (a) => a.type === action.type + ) + + actions[deleteIndex].toRemove = true + }, + + UPDATE_POST(state, { activeView, index, post }) { + state.views[activeView].list.posts[index] = post }, SORTER_CHANGED(state, payload) { @@ -112,5 +176,17 @@ export default { UPDATE_EAGLE_EYE_SETTINGS_ERROR(state) { state.loadingUpdateSettings = false + }, + + ADD_PENDING_ACTION(state, job) { + state.pendingActions.push(job) + }, + + SET_ACTIVE_ACTION(state, job) { + state.activeAction = job + }, + + POP_CURRENT_ACTION(state) { + state.pendingActions.shift() } } diff --git a/frontend/src/premium/eagle-eye/store/state.js b/frontend/src/premium/eagle-eye/store/state.js index 8a37deec75..8e92f374b2 100644 --- a/frontend/src/premium/eagle-eye/store/state.js +++ b/frontend/src/premium/eagle-eye/store/state.js @@ -30,6 +30,8 @@ export default () => { active: false } }, + pendingActions: [], + activeAction: {}, loadingUpdateSettings: false } }