Chat avatar fixes (#9103)

* Ensured rejection doesn't hurt performance

* Added debounce and scroll removal

* Added debounce for keydown

* Fixed linting
This commit is contained in:
Keith Holliday
2017-09-29 16:03:43 -05:00
committed by Sabe Jones
parent dc3a02bc2e
commit 7671347d3a
4 changed files with 51 additions and 16 deletions

View File

@@ -138,12 +138,21 @@ export default {
// @TODO split up this file, it's too big // @TODO split up this file, it's too big
// Set up Error interceptors // Set up Error interceptors
axios.interceptors.response.use((response) => { axios.interceptors.response.use((response) => {
if (this.user) { if (this.user && response.data && response.data.notifications) {
this.$set(this.user, 'notifications', response.data.notifications); this.$set(this.user, 'notifications', response.data.notifications);
} }
return response; return response;
}, (error) => { }, (error) => {
if (error.response.status >= 400) { if (error.response.status >= 400) {
// Don't show errors from getting user details. These users have delete their account,
// but their chat message still exists.
let configExists = Boolean(error.response) && Boolean(error.response.config);
if (configExists && error.response.config.method === 'get' && error.response.config.url.indexOf('/api/v3/members/') !== -1) {
// @TODO: We resolve the promise because we need our caching to cache this user as tried
// Chat paging should help this, but maybe we can also find another solution..
return Promise.resolve(error);
}
this.$store.state.notificationStore.push({ this.$store.state.notificationStore.push({
title: 'Habitica', title: 'Habitica',
text: error.response.data.message, text: error.response.data.message,

View File

@@ -5,14 +5,14 @@
copy-as-todo-modal(:copying-message='copyingMessage', :group-name='groupName', :group-id='groupId') copy-as-todo-modal(:copying-message='copyingMessage', :group-name='groupName', :group-id='groupId')
report-flag-modal report-flag-modal
div(v-for="(msg, index) in chat", v-if='chat && canViewFlag(msg)') div(v-for="(msg, index) in messages", v-if='chat && canViewFlag(msg)')
// @TODO: is there a different way to do these conditionals? This creates an infinite loop // @TODO: is there a different way to do these conditionals? This creates an infinite loop
//.hr(v-if='displayDivider(msg)') //.hr(v-if='displayDivider(msg)')
.hr-middle(v-once) {{ msg.timestamp }} .hr-middle(v-once) {{ msg.timestamp }}
.row(v-if='user._id !== msg.uuid') .row(v-if='user._id !== msg.uuid')
div(:class='inbox ? "col-4" : "col-2"') div(:class='inbox ? "col-4" : "col-2"')
avatar( avatar(
v-if='cachedProfileData[msg.uuid]', v-if='cachedProfileData[msg.uuid] && !cachedProfileData[msg.uuid].rejected',
:member="cachedProfileData[msg.uuid]", :member="cachedProfileData[msg.uuid]",
:avatarOnly="true", :avatarOnly="true",
:hideClassBadge='true', :hideClassBadge='true',
@@ -89,7 +89,7 @@
| + {{ likeCount(msg) }} | + {{ likeCount(msg) }}
div(:class='inbox ? "col-4" : "col-2"') div(:class='inbox ? "col-4" : "col-2"')
avatar( avatar(
v-if='cachedProfileData[msg.uuid]', v-if='cachedProfileData[msg.uuid] && !cachedProfileData[msg.uuid].rejected',
:member="cachedProfileData[msg.uuid]", :member="cachedProfileData[msg.uuid]",
:avatarOnly="true", :avatarOnly="true",
:hideClassBadge='true', :hideClassBadge='true',
@@ -243,7 +243,7 @@ import axios from 'axios';
import moment from 'moment'; import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import { mapState } from 'client/libs/store'; import { mapState } from 'client/libs/store';
import throttle from 'lodash/throttle'; import debounce from 'lodash/debounce';
import markdownDirective from 'client/directives/markdown'; import markdownDirective from 'client/directives/markdown';
import Avatar from '../avatar'; import Avatar from '../avatar';
import styleHelper from 'client/mixins/styleHelper'; import styleHelper from 'client/mixins/styleHelper';
@@ -282,12 +282,10 @@ export default {
this.loadProfileCache(); this.loadProfileCache();
}, },
created () { created () {
window.addEventListener('scroll', throttle(() => { window.addEventListener('scroll', this.handleScroll);
this.loadProfileCache(window.scrollY / 1000);
}, 1000));
}, },
destroyed () { destroyed () {
// window.removeEventListener('scroll', this.handleScroll); window.removeEventListener('scroll', this.handleScroll);
}, },
data () { data () {
return { return {
@@ -313,6 +311,7 @@ export default {
cachedProfileData: {}, cachedProfileData: {},
currentProfileLoadedCount: 0, currentProfileLoadedCount: 0,
currentProfileLoadedEnd: 10, currentProfileLoadedEnd: 10,
loading: false,
}; };
}, },
filters: { filters: {
@@ -326,17 +325,22 @@ export default {
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({user: 'user.data'}),
// @TODO: We need a different lazy load mechnism.
// But honestly, adding a paging route to chat would solve this
messages () { messages () {
return this.chat; return this.chat;
}, },
}, },
watch: { watch: {
messages () { messages (oldValue, newValue) {
// @TODO: MAybe we should watch insert and remove? if (newValue.length === oldValue.length) return;
this.loadProfileCache(); this.loadProfileCache();
}, },
}, },
methods: { methods: {
handleScroll () {
this.loadProfileCache(window.scrollY / 1000);
},
isUserMentioned (message) { isUserMentioned (message) {
let user = this.user; let user = this.user;
@@ -363,7 +367,10 @@ export default {
if (!message.flagCount || message.flagCount < 2) return true; if (!message.flagCount || message.flagCount < 2) return true;
return this.user.contributor.admin; return this.user.contributor.admin;
}, },
async loadProfileCache (screenPosition) { loadProfileCache: debounce(function loadProfileCache (screenPosition) {
this._loadProfileCache(screenPosition);
}, 1000),
async _loadProfileCache (screenPosition) {
let promises = []; let promises = [];
// @TODO: write an explination // @TODO: write an explination
@@ -373,11 +380,10 @@ export default {
return; return;
} }
// @TODO: Not sure we need this hash
let aboutToCache = {}; let aboutToCache = {};
this.messages.forEach(message => { this.messages.forEach(message => {
let uuid = message.uuid; let uuid = message.uuid;
if (uuid && !this.cachedProfileData[uuid] && !aboutToCache[uuid]) { if (Boolean(uuid) && !this.cachedProfileData[uuid] && !aboutToCache[uuid]) {
if (uuid === 'system' || this.currentProfileLoadedCount === this.currentProfileLoadedEnd) return; if (uuid === 'system' || this.currentProfileLoadedCount === this.currentProfileLoadedEnd) return;
aboutToCache[uuid] = {}; aboutToCache[uuid] = {};
promises.push(axios.get(`/api/v3/members/${uuid}`)); promises.push(axios.get(`/api/v3/members/${uuid}`));
@@ -387,9 +393,21 @@ export default {
let results = await Promise.all(promises); let results = await Promise.all(promises);
results.forEach(result => { results.forEach(result => {
// We could not load the user. Maybe they were deleted. So, let's cache empty so we don't try again
if (!result || !result.data || result.status >= 400) {
return;
}
let userData = result.data.data; let userData = result.data.data;
this.$set(this.cachedProfileData, userData._id, userData); this.$set(this.cachedProfileData, userData._id, userData);
}); });
// Merge in any attempts that were rejected so we don't attempt again
for (let uuid in aboutToCache) {
if (!this.cachedProfileData[uuid]) {
this.$set(this.cachedProfileData, uuid, {rejected: true});
}
}
}, },
displayDivider (message) { displayDivider (message) {
if (this.currentDayDividerDisplay !== moment(message.timestamp).day()) { if (this.currentDayDividerDisplay !== moment(message.timestamp).day()) {

View File

@@ -431,6 +431,7 @@
</style> </style>
<script> <script>
import debounce from 'lodash/debounce';
import extend from 'lodash/extend'; import extend from 'lodash/extend';
import groupUtilities from 'client/mixins/groupsUtilities'; import groupUtilities from 'client/mixins/groupsUtilities';
import styleHelper from 'client/mixins/styleHelper'; import styleHelper from 'client/mixins/styleHelper';
@@ -655,7 +656,10 @@ export default {
}; };
document.body.removeChild(div); document.body.removeChild(div);
}, },
updateCarretPosition (eventUpdate) { updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
_updateCarretPosition (eventUpdate) {
if (eventUpdate.metaKey && eventUpdate.keyCode === 13) { if (eventUpdate.metaKey && eventUpdate.keyCode === 13) {
this.sendMessage(); this.sendMessage();
return; return;

View File

@@ -350,6 +350,7 @@
</style> </style>
<script> <script>
import debounce from 'lodash/debounce';
import { mapState } from 'client/libs/store'; import { mapState } from 'client/libs/store';
import { TAVERN_ID } from '../../../common/script/constants'; import { TAVERN_ID } from '../../../common/script/constants';
@@ -527,7 +528,10 @@ export default {
}; };
document.body.removeChild(div); document.body.removeChild(div);
}, },
updateCarretPosition (eventUpdate) { updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
_updateCarretPosition (eventUpdate) {
if (eventUpdate.metaKey && eventUpdate.keyCode === 13) { if (eventUpdate.metaKey && eventUpdate.keyCode === 13) {
this.sendMessage(); this.sendMessage();
return; return;