diff options
Diffstat (limited to 'src/store/actions.js.orig')
-rw-r--r-- | src/store/actions.js.orig | 865 |
1 files changed, 865 insertions, 0 deletions
diff --git a/src/store/actions.js.orig b/src/store/actions.js.orig new file mode 100644 index 000000000..c3ffb03f3 --- /dev/null +++ b/src/store/actions.js.orig @@ -0,0 +1,865 @@ +<<<<<<< HEAD:src/store.js +import _ from 'lodash' +import Vue from 'vue' +import Vuex from 'vuex' +import { translate as t } from 'nextcloud-server/dist/l10n' + +import { + create as createAccount, + update as updateAccount, + fetch as fetchAccount, + fetchAll as fetchAllAccounts, +} from './service/AccountService' +import { fetchAll as fetchAllFolders } from './service/FolderService' +======= +/* + * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import _ from "lodash"; + +import {savePreference} from "../service/PreferenceService"; +import { + create as createAccount, + deleteAccount, + fetch as fetchAccount, + fetchAll as fetchAllAccounts +} from "../service/AccountService"; +import {fetchAll as fetchAllFolders} from "../service/FolderService"; +>>>>>>> 12b4a7d848da1bf5d72db31f1a3b7e42c09af590:src/store/actions.js +import { + deleteMessage, + fetchEnvelopes, + fetchMessage, + setEnvelopeFlag, +<<<<<<< HEAD:src/store.js + syncEnvelopes, +} from './service/MessageService' +import { showNewMessagesNotification } from './service/NotificationService' +import { savePreference } from './service/PreferenceService' +import { parseUid } from './util/EnvelopeUidParser' +import { value } from './util/undefined' + +Vue.use(Vuex) + +const UNIFIED_ACCOUNT_ID = 0 +const UNIFIED_INBOX_ID = 'inbox' +const UNIFIED_INBOX_UID = UNIFIED_ACCOUNT_ID + '-' + UNIFIED_INBOX_ID + +export const mutations = { + savePreference(state, { key, value }) { + Vue.set(state.preferences, key, value) + }, + addAccount(state, account) { + account.folders = [] + account.collapsed = true + Vue.set(state.accounts, account.id, account) + Vue.set( + state, + 'accountList', + _.sortBy(state.accountList.concat([account.id])) + ) + }, + editAccount(state, account) { + Vue.set( + state.accounts, + account.id, + Object.assign({}, state.accounts[account.id], account) + ) + }, + toggleAccountCollapsed(state, accountId) { + state.accounts[accountId].collapsed = !state.accounts[accountId] + .collapsed + }, + addFolder(state, { account, folder }) { + const addToState = folder => { + const id = account.id + '-' + folder.id + folder.accountId = account.id + folder.envelopes = [] + folder.searchEnvelopes = [] + Vue.set(state.folders, id, folder) + return id + } + + // Add all folders (including subfolders ot state, but only toplevel to account + const id = addToState(folder) + folder.folders.forEach(addToState) + + account.folders.push(id) + }, + updateFolderSyncToken(state, { folder, syncToken }) { + folder.syncToken = syncToken + }, + addEnvelope(state, { accountId, folder, envelope }) { + const uid = accountId + '-' + folder.id + '-' + envelope.id + envelope.accountId = accountId + envelope.folderId = folder.id + envelope.uid = uid + Vue.set(state.envelopes, uid, envelope) + Vue.set( + folder, + 'envelopes', + _.sortedUniq( + _.orderBy( + folder.envelopes.concat([uid]), + id => state.envelopes[id].dateInt, + 'desc' + ) + ) + ) + }, + addSearchEnvelopes(state, { accountId, folder, envelopes, clear }) { + const uids = envelopes.map(envelope => { + const uid = accountId + '-' + folder.id + '-' + envelope.id + envelope.accountId = accountId + envelope.folderId = folder.id + envelope.uid = uid + Vue.set(state.envelopes, uid, envelope) + return uid + }) + + if (clear) { + Vue.set(folder, 'searchEnvelopes', uids) + } else { + Vue.set( + folder, + 'searchEnvelopes', + _.sortedUniq( + _.orderBy( + folder.searchEnvelopes.concat(uids), + id => state.envelopes[id].dateInt, + 'desc' + ) + ) + ) + } + }, + addUnifiedEnvelope(state, { folder, envelope }) { + Vue.set( + folder, + 'envelopes', + _.sortedUniq( + _.orderBy( + folder.envelopes.concat([envelope.uid]), + id => state.envelopes[id].dateInt, + 'desc' + ) + ) + ) + }, + addUnifiedEnvelopes(state, { folder, uids }) { + Vue.set(folder, 'envelopes', uids) + }, + addUnifiedSearchEnvelopes(state, { folder, uids }) { + Vue.set(folder, 'searchEnvelopes', uids) + }, + flagEnvelope(state, { envelope, flag, value }) { + envelope.flags[flag] = value + }, + removeEnvelope(state, { accountId, folder, id }) { + const envelopeUid = accountId + '-' + folder.id + '-' + id + const idx = folder.envelopes.indexOf(envelopeUid) + if (idx < 0) { + console.warn('envelope does not exist', accountId, folder.id, id) + return + } + folder.envelopes.splice(idx, 1) + + const unifiedAccount = state.accounts[UNIFIED_ACCOUNT_ID] + unifiedAccount.folders + .map(fId => state.folders[fId]) + .filter(f => f.specialRole === folder.specialRole) + .forEach(folder => { + const idx = folder.envelopes.indexOf(envelopeUid) + if (idx < 0) { + console.warn( + 'envelope does not exist in unified mailbox', + accountId, + folder.id, + id + ) + return + } + folder.envelopes.splice(idx, 1) + }) + + Vue.delete(folder.envelopes, envelopeUid) + }, + addMessage(state, { accountId, folderId, message }) { + const uid = accountId + '-' + folderId + '-' + message.id + message.accountId = accountId + message.folderId = folderId + message.uid = uid + Vue.set(state.messages, uid, message) + }, + updateDraft(state, { draft, data, newUid }) { + // Update draft's UID + const oldUid = draft.uid + const uid = draft.accountId + '-' + draft.folderId + '-' + newUid + console.debug('saving draft as UID ' + uid) + draft.uid = uid + + // TODO: strategy to keep the full draft object in sync, not just the visible + // changes + draft.subject = data.subject + + // Update ref in folder's envelope list + const envs = + state.folders[draft.accountId + '-' + draft.folderId].envelopes + const idx = envs.indexOf(oldUid) + if (idx < 0) { + console.warn( + 'not replacing draft ' + + oldUid + + ' in envelope list because it did not exist' + ) + } else { + envs[idx] = uid + } + + // Move message/envelope objects to new keys + Vue.delete(state.envelopes, oldUid) + Vue.delete(state.messages, oldUid) + Vue.set(state.envelopes, uid, draft) + Vue.set(state.messages, uid, draft) + }, + setMessageBodyText(state, { uid, bodyText }) { + Vue.set(state.messages[uid], 'bodyText', bodyText) + }, + removeMessage(state, { accountId, folderId, id }) { + Vue.delete(state.messages, accountId + '-' + folderId + '-' + id) + }, +} + +export const actions = { + savePreference({ commit, getters }, { key, value }) { + return savePreference(key, value).then(({ value }) => { + commit('savePreference', { + key, + value, +======= + syncEnvelopes +} from "../service/MessageService"; +import {showNewMessagesNotification} from "../service/NotificationService"; +import {parseUid} from "../util/EnvelopeUidParser"; + +export default { + savePreference ({commit, getters}, {key, value}) { + return savePreference(key, value) + .then(({value}) => { + commit('savePreference', { + key, + value, + }) +>>>>>>> 12b4a7d848da1bf5d72db31f1a3b7e42c09af590:src/store/actions.js + }) + }) + }, + fetchAccounts({ commit, getters }) { + return fetchAllAccounts().then(accounts => { + accounts.forEach(account => commit('addAccount', account)) + return getters.getAccounts() + }) + }, + fetchAccount({ commit }, id) { + return fetchAccount(id).then(account => { + commit('addAccount', account) + return account + }) + }, + createAccount({ commit }, config) { + return createAccount(config).then(account => { + commit('addAccount', account) + return account + }) + }, +<<<<<<< HEAD:src/store.js + updateAccount({ commit }, config) { + return updateAccount(config).then(account => { + console.debug('account updated', account) + commit('editAccount', account) + return account + }) + }, + fetchFolders({ commit, getters }, { accountId }) { +======= + deleteAccount ({commit}, account) { + return deleteAccount(account.id) + .then(account => { + console.debug('account deleted') + location.reload() // TODO: better handling of this + }) + .catch(err => { + console.error('could not delete account', err) + throw err + }) + }, + fetchFolders ({commit, getters}, {accountId}) { +>>>>>>> 12b4a7d848da1bf5d72db31f1a3b7e42c09af590:src/store/actions.js + if (getters.getAccount(accountId).isUnified) { + return Promise.resolve(getters.getFolders(accountId)) + } + + return fetchAllFolders(accountId).then(folders => { + let account = getters.getAccount(accountId) + + folders.forEach(folder => { + commit('addFolder', { + account, + folder, + }) + }) + return folders + }) + }, + fetchEnvelopes( + { state, commit, getters, dispatch }, + { accountId, folderId, query } + ) { + const folder = getters.getFolder(accountId, folderId) + const isSearch = !_.isUndefined(query) + if (folder.isUnified) { + // Fetch and combine envelopes of all individual folders + // + // The last envelope is excluded to efficiently build the next unified + // pages (fetch only individual pages that do not have sufficient local + // data) + // + // TODO: handle short/ending streams and show their last element as well + return Promise.all( + getters + .getAccounts() + .filter(account => !account.isUnified) + .map(account => + Promise.all( + getters + .getFolders(account.id) + .filter( + f => f.specialRole === folder.specialRole + ) + .map(folder => + dispatch('fetchEnvelopes', { + accountId: account.id, + folderId: folder.id, + query, + }) + ) + ) + ) + ) + .then(res => res.map(envs => envs.slice(0, 19))) + .then(res => _.flattenDepth(res, 2)) + .then(envs => _.orderBy(envs, env => env.dateInt, 'desc')) + .then(envs => _.slice(envs, 0, 19)) // 19 to handle non-overlapping streams + .then(envelopes => { + if (!isSearch) { + commit('addUnifiedEnvelopes', { + folder, + uids: envelopes.map(e => e.uid), + }) + } else { + commit('addUnifiedSearchEnvelopes', { + folder, + uids: envelopes.map(e => e.uid), + }) + } + return envelopes + }) + } + + return fetchEnvelopes(accountId, folderId, query).then(envelopes => { + let folder = getters.getFolder(accountId, folderId) + + if (!isSearch) { + envelopes.forEach(envelope => + commit('addEnvelope', { + accountId, + folder, + envelope, + }) + ) + } else { + commit('addSearchEnvelopes', { + accountId, + folder, + envelopes, + clear: true, + }) + } + return envelopes + }) + }, + fetchNextUnifiedEnvelopePage( + { state, commit, getters, dispatch }, + { accountId, folderId, query } + ) { + const folder = getters.getFolder(accountId, folderId) + const isSearch = !_.isUndefined(query) + const list = isSearch ? 'searchEnvelopes' : 'envelopes' + + // We only care about folders of the same type/role + const individualFolders = _.flatten( + getters + .getAccounts() + .filter(a => !a.isUnified) + .map(a => + getters + .getFolders(a.id) + .filter(f => f.specialRole === folder.specialRole) + ) + ) + // Build a sorted list of all currently known envelopes (except last elem) + const knownEnvelopes = _.orderBy( + _.flatten( + individualFolders.map(f => f[list].slice(0, f[list].length - 1)) + ), + id => state.envelopes[id].dateInt, + 'desc' + ) + // The index of the last element in the current unified mailbox determines + // the new offset + const tailId = _.last(folder[list]) + const tailIdx = knownEnvelopes.indexOf(tailId) + if (tailIdx === -1) { + return Promise.reject( + new Error( + 'current envelopes do not contain unified mailbox tail. this should not have happened' + ) + ) + } + + // Select the next page, based on offline data + const nextCandidates = knownEnvelopes.slice(tailIdx + 1, tailIdx + 20) + + // Now, let's check if any of the "streams" have reached their ends. + // In that case, we attempt to fetch more elements recursively + // + // In case of an empty next page we always fetch all streams (this might be redundant) + // + // Their end was reached if the last known (oldest) envelope is an element + // of the offline page + // TODO: what about streams that ended before? Is it safe to ignore those? + const needFetch = individualFolders + .filter(f => !_.isEmpty(f[list])) + .filter(f => { + const lastShown = f[list][f[list].length - 2] + return ( + nextCandidates.length <= 18 || + nextCandidates.indexOf(lastShown) !== -1 + ) + }) + + if (_.isEmpty(needFetch)) { + if (!isSearch) { + commit('addUnifiedEnvelopes', { + folder, + uids: _.sortedUniq( + _.orderBy( + folder[list].concat(nextCandidates), + id => state.envelopes[id].dateInt, + 'desc' + ) + ), + }) + } else { + commit('addUnifiedSearchEnvelopes', { + folder, + uids: _.sortedUniq( + _.orderBy( + folder[list].concat(nextCandidates), + id => state.envelopes[id].dateInt, + 'desc' + ) + ), + }) + } + } else { + return Promise.all( + needFetch.map(f => + dispatch('fetchNextEnvelopePage', { + accountId: f.accountId, + folderId: f.id, + query, + }) + ) + ).then(() => { + return dispatch('fetchNextUnifiedEnvelopePage', { + accountId, + folderId, + query, + }) + }) + } + }, + fetchNextEnvelopePage( + { commit, getters, dispatch }, + { accountId, folderId, query } + ) { + const folder = getters.getFolder(accountId, folderId) + const isSearch = !_.isUndefined(query) + const list = isSearch ? 'searchEnvelopes' : 'envelopes' + + if (folder.isUnified) { + return dispatch('fetchNextUnifiedEnvelopePage', { + accountId, + folderId, + query, + }) + } + + const lastEnvelopeId = folder[list][folder.envelopes.length - 1] + if (typeof lastEnvelopeId === 'undefined') { + console.error('folder is empty', folder[list]) + return Promise.reject( + new Error( + 'Local folder has no envelopes, cannot determine cursor' + ) + ) + } + const lastEnvelope = getters.getEnvelopeById(lastEnvelopeId) + if (typeof lastEnvelope === 'undefined') { + return Promise.reject( + new Error( + 'Cannot find last envelope. Required for the folder cursor' + ) + ) + } + + return fetchEnvelopes( + accountId, + folderId, + query, + lastEnvelope.dateInt + ).then(envelopes => { + if (!isSearch) { + envelopes.forEach(envelope => + commit('addEnvelope', { + accountId, + folder, + envelope, + }) + ) + } else { + commit('addSearchEnvelopes', { + accountId, + folder, + envelopes, + clear: false, + }) + } + + return envelopes + }) + }, + syncEnvelopes({ commit, getters, dispatch }, { accountId, folderId }) { + const folder = getters.getFolder(accountId, folderId) + + if (folder.isUnified) { + return Promise.all( + getters + .getAccounts() + .filter(account => !account.isUnified) + .map(account => + Promise.all( + getters + .getFolders(account.id) + .filter( + f => f.specialRole === folder.specialRole + ) + .map(folder => + dispatch('syncEnvelopes', { + accountId: account.id, + folderId: folder.id, + }) + ) + ) + ) + ) + } + + const syncToken = folder.syncToken + const uids = getters + .getEnvelopes(accountId, folderId) + .map(env => env.id) + + return syncEnvelopes(accountId, folderId, syncToken, uids).then( + syncData => { + const unifiedFolder = getters.getUnifiedFolder( + folder.specialRole + ) + + syncData.newMessages.forEach(envelope => { + commit('addEnvelope', { + accountId, + folder, + envelope, + }) + if (unifiedFolder) { + commit('addUnifiedEnvelope', { + folder: unifiedFolder, + envelope, + }) + } + }) + syncData.changedMessages.forEach(envelope => { + commit('addEnvelope', { + accountId, + folder, + envelope, + }) + }) + syncData.vanishedMessages.forEach(id => { + commit('removeEnvelope', { + accountId, + folder, + id, + }) + // Already removed from unified inbox + }) + commit('updateFolderSyncToken', { + folder, + syncToken: syncData.token, + }) + + return syncData.newMessages + } + ) + }, + syncInboxes({ getters, dispatch }) { + return Promise.all( + getters + .getAccounts() + .filter(a => !a.isUnified) + .map(account => { + return Promise.all( + getters.getFolders(account.id).map(folder => { + if (folder.specialRole !== 'inbox') { + return + } + + return dispatch('syncEnvelopes', { + accountId: account.id, + folderId: folder.id, + }) + }) + ) + }) + ).then(results => { + const newMessages = _.flatMapDeep(results).filter( + _.negate(_.isUndefined) + ) + if (newMessages.length > 0) { + showNewMessagesNotification(newMessages) + } + }) + }, + toggleEnvelopeFlagged({ commit, getters }, envelope) { + // Change immediately and switch back on error + const oldState = envelope.flags.flagged + commit('flagEnvelope', { + envelope, + flag: 'flagged', + value: !oldState, + }) + + setEnvelopeFlag( + envelope.accountId, + envelope.folderId, + envelope.id, + 'flagged', + !oldState + ).catch(e => { + console.error('could not toggle message flagged state', e) + + // Revert change + commit('flagEnvelope', { + envelope, + flag: 'flagged', + value: oldState, + }) + }) + }, + toggleEnvelopeSeen({ commit, getters }, envelope) { + // Change immediately and switch back on error + const oldState = envelope.flags.unseen + commit('flagEnvelope', { + envelope, + flag: 'unseen', + value: !oldState, + }) + + setEnvelopeFlag( + envelope.accountId, + envelope.folderId, + envelope.id, + 'unseen', + !oldState + ).catch(e => { + console.error('could not toggle message unseen state', e) + + // Revert change + commit('flagEnvelope', { + envelope, + flag: 'unseen', + value: oldState, + }) + }) + }, + fetchMessage({ commit }, uid) { + const { accountId, folderId, id } = parseUid(uid) + return fetchMessage(accountId, folderId, id).then(message => { + // Only commit if not undefined (not found) + if (message) { + commit('addMessage', { + accountId, + folderId, + message, + }) + } + + return message + }) + }, + replaceDraft({ getters, commit }, { draft, uid, data }) { + commit('updateDraft', { + draft, + data, + newUid: uid, + }) + }, + deleteMessage({ getters, commit }, envelope) { + const folder = getters.getFolder(envelope.accountId, envelope.folderId) + commit('removeEnvelope', { + accountId: envelope.accountId, + folder, + id: envelope.id, + }) + + return deleteMessage(envelope.accountId, envelope.folderId, envelope.id) + .then(() => { + commit('removeMessage', { + accountId: envelope.accountId, + folder, + id: envelope.id, + }) + console.log('message removed') + }) + .catch(err => { + console.error('could not delete message', err) + commit('addEnvelope', { + accountId: envelope.accountId, + folder, + envelope, + }) + throw err + }) +<<<<<<< HEAD:src/store.js + }, +} + +export const getters = { + getPreference: state => (key, def) => { + return value(state.preferences[key]).or(def) + }, + getAccount: state => id => { + return state.accounts[id] + }, + getAccounts: state => () => { + return state.accountList.map(id => state.accounts[id]) + }, + getFolder: state => (accountId, folderId) => { + return state.folders[accountId + '-' + folderId] + }, + getFolders: state => accountId => { + return state.accounts[accountId].folders.map( + folderId => state.folders[folderId] + ) + }, + getUnifiedFolder: state => specialRole => { + return _.head( + state.accounts[UNIFIED_ACCOUNT_ID].folders + .map(folderId => state.folders[folderId]) + .filter(folder => folder.specialRole === specialRole) + ) + }, + getEnvelope: state => (accountId, folderId, id) => { + return state.envelopes[accountId + '-' + folderId + '-' + id] + }, + getEnvelopeById: state => id => { + return state.envelopes[id] + }, + getEnvelopes: (state, getters) => (accountId, folderId) => { + return getters + .getFolder(accountId, folderId) + .envelopes.map(msgId => state.envelopes[msgId]) + }, + getSearchEnvelopes: (state, getters) => (accountId, folderId) => { + return getters + .getFolder(accountId, folderId) + .searchEnvelopes.map(msgId => state.envelopes[msgId]) + }, + getMessage: state => (accountId, folderId, id) => { + return state.messages[accountId + '-' + folderId + '-' + id] + }, + getMessageByUid: state => uid => { + return state.messages[uid] + }, +} + +export default new Vuex.Store({ + strict: process.env.NODE_ENV !== 'production', + state: { + preferences: {}, + accounts: { + [UNIFIED_ACCOUNT_ID]: { + id: UNIFIED_ACCOUNT_ID, + isUnified: true, + folders: [UNIFIED_INBOX_UID], + collapsed: false, + emailAddress: '', + name: '', + }, + }, + accountList: [UNIFIED_ACCOUNT_ID], + folders: { + [UNIFIED_INBOX_UID]: { + id: UNIFIED_INBOX_ID, + accountId: 0, + isUnified: true, + specialRole: 'inbox', + name: t('mail', 'All inboxes'), // TODO, + unread: 0, + folders: [], + envelopes: [], + searchEnvelopes: [], + }, + }, + envelopes: {}, + messages: {}, + autocompleteEntries: [], + }, + getters, + mutations, + actions, +}) +======= + } +} +>>>>>>> 12b4a7d848da1bf5d72db31f1a3b7e42c09af590:src/store/actions.js |