From 98b704f465bdfc4c8b14ef89685c73a905de2793 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Tue, 20 Mar 2018 17:41:39 -0700 Subject: [PATCH] optimistic updates for fav/reblog --- routes/_actions/favorite.js | 14 ++++++-------- routes/_actions/reblog.js | 14 ++++++-------- routes/_store/mixins/mixins.js | 2 ++ routes/_store/mixins/statusMixins.js | 22 ++++++++++++++++++++++ routes/_utils/sync.js | 15 +++++++++++++++ tests/spec/105-deletes.js | 3 +-- 6 files changed, 52 insertions(+), 18 deletions(-) create mode 100644 routes/_store/mixins/statusMixins.js diff --git a/routes/_actions/favorite.js b/routes/_actions/favorite.js index c809159e..f6c09d66 100644 --- a/routes/_actions/favorite.js +++ b/routes/_actions/favorite.js @@ -10,18 +10,16 @@ export async function setFavorited (statusId, favorited) { } let instanceName = store.get('currentInstance') let accessToken = store.get('accessToken') + let networkPromise = favorited + ? favoriteStatus(instanceName, accessToken, statusId) + : unfavoriteStatus(instanceName, accessToken, statusId) + store.setStatusFavorited(instanceName, statusId, favorited) // optimistic update try { - await (favorited - ? favoriteStatus(instanceName, accessToken, statusId) - : unfavoriteStatus(instanceName, accessToken, statusId)) + await networkPromise await database.setStatusFavorited(instanceName, statusId, favorited) - let statusModifications = store.get('statusModifications') - let currentStatusModifications = statusModifications[instanceName] = - (statusModifications[instanceName] || {favorites: {}, reblogs: {}}) - currentStatusModifications.favorites[statusId] = favorited - store.set({statusModifications: statusModifications}) } catch (e) { console.error(e) toast.say(`Failed to ${favorited ? 'favorite' : 'unfavorite'}. ` + (e.message || '')) + store.setStatusFavorited(instanceName, statusId, !favorited) // undo optimistic update } } diff --git a/routes/_actions/reblog.js b/routes/_actions/reblog.js index 0069dac6..e751aa4c 100644 --- a/routes/_actions/reblog.js +++ b/routes/_actions/reblog.js @@ -10,18 +10,16 @@ export async function setReblogged (statusId, reblogged) { } let instanceName = store.get('currentInstance') let accessToken = store.get('accessToken') + let networkPromise = reblogged + ? reblogStatus(instanceName, accessToken, statusId) + : unreblogStatus(instanceName, accessToken, statusId) + store.setStatusReblogged(instanceName, statusId, reblogged) // optimistic update try { - await (reblogged - ? reblogStatus(instanceName, accessToken, statusId) - : unreblogStatus(instanceName, accessToken, statusId)) + await networkPromise await database.setStatusReblogged(instanceName, statusId, reblogged) - let statusModifications = store.get('statusModifications') - let currentStatusModifications = statusModifications[instanceName] = - (statusModifications[instanceName] || {favorites: {}, reblogs: {}}) - currentStatusModifications.reblogs[statusId] = reblogged - store.set({statusModifications: statusModifications}) } catch (e) { console.error(e) toast.say(`Failed to ${reblogged ? 'boost' : 'unboost'}. ` + (e.message || '')) + store.setStatusReblogged(instanceName, statusId, !reblogged) // undo optimistic update } } diff --git a/routes/_store/mixins/mixins.js b/routes/_store/mixins/mixins.js index bfa8ed12..47f001d3 100644 --- a/routes/_store/mixins/mixins.js +++ b/routes/_store/mixins/mixins.js @@ -1,7 +1,9 @@ import { timelineMixins } from './timelineMixins' import { instanceMixins } from './instanceMixins' +import { statusMixins } from './statusMixins' export function mixins (Store) { instanceMixins(Store) timelineMixins(Store) + statusMixins(Store) } diff --git a/routes/_store/mixins/statusMixins.js b/routes/_store/mixins/statusMixins.js new file mode 100644 index 00000000..fbca4b4e --- /dev/null +++ b/routes/_store/mixins/statusMixins.js @@ -0,0 +1,22 @@ +function getStatusModifications (store, instanceName) { + let statusModifications = store.get('statusModifications') + statusModifications[instanceName] = statusModifications[instanceName] || { + favorites: {}, + reblogs: {} + } + return statusModifications +} + +export function statusMixins (Store) { + Store.prototype.setStatusFavorited = function (instanceName, statusId, favorited) { + let statusModifications = getStatusModifications(this, instanceName) + statusModifications[instanceName].favorites[statusId] = favorited + this.set({statusModifications}) + } + + Store.prototype.setStatusReblogged = function (instanceName, statusId, reblogged) { + let statusModifications = getStatusModifications(this, instanceName) + statusModifications[instanceName].reblogs[statusId] = reblogged + this.set({statusModifications}) + } +} diff --git a/routes/_utils/sync.js b/routes/_utils/sync.js index 9c4195db..51344c31 100644 --- a/routes/_utils/sync.js +++ b/routes/_utils/sync.js @@ -16,3 +16,18 @@ export async function cacheFirstUpdateAfter (networkFetcher, dbFetcher, dbUpdate } } } + +// Make a change that we optimistically show to the user as successful, but which +// actually depends on a network operation. In the unlikely event that the network +// operation fails, revert the changes +export async function optimisticUpdate (doImmediately, networkUpdater, onSuccess, onFailure) { + let networkPromise = networkUpdater() + doImmediately() + try { + let response = await networkPromise + onSuccess(response) + } catch (e) { + console.error(e) + onFailure(e) + } +} diff --git a/tests/spec/105-deletes.js b/tests/spec/105-deletes.js index 7af353e1..04bc801d 100644 --- a/tests/spec/105-deletes.js +++ b/tests/spec/105-deletes.js @@ -1,8 +1,7 @@ import { foobarRole } from '../roles' import { clickToNotificationsAndBackHome, forceOffline, forceOnline, getNthStatus, getUrl, homeNavButton, - notificationsNavButton, - sleep + notificationsNavButton } from '../utils' import { deleteAs, postAs, postReplyAs } from '../serverActions'