Show reacting people next to reaction buttons

This change replaces the reaction count on the reaction buttons under
the post with micro avatars of the people reacting. This makes the
whole thing feel more personal IMHO.

Performance concerns: because the posts by themselves only contain
reaction counts, this means executing an extra API call is done to
fetch the list of users who reacted. This was already being done when
hovering a reaction button, and my Raspberry Pi is doing pretty fine
despite this patch.

Further development was done to lazify the API call, so now
reaction avatars are now fetched only when the reaction bar
slides into view. This should lower the load a bit.

Borrowed some ideas from code at https://medium.com/js-dojo/lazy-rendering-in-vue-to-improve-performance-dcccd445d5f

TODO: check there might be a glitch when adding a reaction
because it is already in view
This commit is contained in:
Michał Sidor 2022-05-29 13:13:48 +02:00 committed by Michcio
parent 191b2692d2
commit 8840724a7c
4 changed files with 96 additions and 4 deletions

View File

@ -15,6 +15,7 @@
"@rollup/pluginutils": "^4.2.1",
"@syuilo/aiscript": "0.11.1",
"@vitejs/plugin-vue": "^3.1.0",
"@vueuse/core": "9.1.0",
"abort-controller": "3.0.0",
"autobind-decorator": "2.4.0",
"autosize": "5.0.1",

View File

@ -8,7 +8,10 @@
@click="toggleReaction()"
>
<MkEmoji class="icon" :emoji="reaction" :custom-emojis="note.emojis" :is-reaction="true" :normal="true"/>
<span class="count">{{ count }}</span>
<span v-if="users === undefined" class="count">{{ count }}</span>
<span v-if="users !== undefined">
<MkAvatar v-for="u in users" class="user" :style="{ height: '2em', width: '2em' }" :key="u.id" :user="u" />
</span>
</button>
</template>
@ -25,6 +28,7 @@ const props = defineProps<{
count: number;
isInitial: boolean;
note: foundkey.entities.Note;
users?: foundkey.entities.UserLite[];
}>();
const buttonRef = ref<HTMLElement>();

View File

@ -1,14 +1,16 @@
<template>
<div class="tdflqwzn" :class="{ isMe }">
<XReaction v-for="(count, reaction) in note.reactions" :key="reaction" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note"/>
<div ref="targetEl" class="tdflqwzn" :class="{ isMe }">
<XReaction v-for="(count, reaction) in note.reactions" :key="reaction" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :users="usersMap[reaction]" :note="note"/>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import { useIntersectionObserver } from '@vueuse/core';
import * as foundkey from 'foundkey-js';
import XReaction from './reactions-viewer.reaction.vue';
import { $i } from '@/account';
import * as os from '@/os';
const props = defineProps<{
note: foundkey.entities.Note;
@ -17,6 +19,39 @@ const props = defineProps<{
const initialReactions = new Set(Object.keys(props.note.reactions));
const isMe = computed(() => $i && $i.id === props.note.userId);
const usersMap = ref({});
const superloaded = ref(false);
const targetEl = ref();
async function updateReactions(currentValue): Promise<void> {
const reactions = await os.api('notes/reactions', {
noteId: currentValue.id,
limit: 100,
});
const users = {};
for (const reaction of reactions) {
if (users[reaction.type] === undefined) {
users[reaction.type] = [];
}
users[reaction.type].push(reaction.user);
}
usersMap.value = users;
superloaded.value = true;
}
async function prepareFetch(note: foundkey.entities.Note): Promise<void> {
superloaded.value = false;
const { stop } = useIntersectionObserver(targetEl, ([{ isIntersecting }]) => {
if (isIntersecting) {
superloaded.value = true;
stop();
updateReactions(note);
}
});
}
onMounted(() => prepareFetch(props.note));
watch(props.note, prepareFetch, { deep: true });
</script>
<style lang="scss" scoped>

View File

@ -2453,6 +2453,13 @@ __metadata:
languageName: node
linkType: hard
"@types/web-bluetooth@npm:^0.0.15":
version: 0.0.15
resolution: "@types/web-bluetooth@npm:0.0.15"
checksum: 4e3b3b1c0baf6735690ce0c5ffaac53de3dbd16362316cbc5e32970bcb1e1baf16dae0a9f30fe86256ad0ee22a4533423f443835273efc54b15235086ebda85b
languageName: node
linkType: hard
"@types/web-push@npm:3.3.2":
version: 3.3.2
resolution: "@types/web-push@npm:3.3.2"
@ -2752,6 +2759,34 @@ __metadata:
languageName: node
linkType: hard
"@vueuse/core@npm:9.1.0":
version: 9.1.0
resolution: "@vueuse/core@npm:9.1.0"
dependencies:
"@types/web-bluetooth": ^0.0.15
"@vueuse/metadata": 9.1.0
"@vueuse/shared": 9.1.0
vue-demi: "*"
checksum: 41a7d3a247ef007908028804923767c20bef7417491a762420a2f4a3da8f7a14da80f5126afaf545fddfa5c77e75cd96c966c884fd4627d8c8a082caecc21386
languageName: node
linkType: hard
"@vueuse/metadata@npm:9.1.0":
version: 9.1.0
resolution: "@vueuse/metadata@npm:9.1.0"
checksum: 128657916aaed7b2a011ed60a1213254ef49ed9b486e61925afdc16da887091e3c55803ee1a2a3beff0180a4acca8fba80bb862fc17b14b3ae5f9c61fed246d3
languageName: node
linkType: hard
"@vueuse/shared@npm:9.1.0":
version: 9.1.0
resolution: "@vueuse/shared@npm:9.1.0"
dependencies:
vue-demi: "*"
checksum: 18aae2c37b1cfabc97f53c16300d5daf072a7a9d8a0ae98a77d14022ead47e33053307838d3d43100ec234b4cf8d5430a7e8be011869fd5eecac2a6dca7a1453
languageName: node
linkType: hard
"abab@npm:^2.0.3, abab@npm:^2.0.5, abab@npm:^2.0.6":
version: 2.0.6
resolution: "abab@npm:2.0.6"
@ -4554,6 +4589,7 @@ __metadata:
"@typescript-eslint/eslint-plugin": ^5.36.2
"@typescript-eslint/parser": ^5.36.2
"@vitejs/plugin-vue": ^3.1.0
"@vueuse/core": 9.1.0
abort-controller: 3.0.0
autobind-decorator: 2.4.0
autosize: 5.0.1
@ -16831,6 +16867,22 @@ __metadata:
languageName: node
linkType: hard
"vue-demi@npm:*":
version: 0.13.11
resolution: "vue-demi@npm:0.13.11"
peerDependencies:
"@vue/composition-api": ^1.0.0-rc.1
vue: ^3.0.0-0 || ^2.6.0
peerDependenciesMeta:
"@vue/composition-api":
optional: true
bin:
vue-demi-fix: bin/vue-demi-fix.js
vue-demi-switch: bin/vue-demi-switch.js
checksum: 0fbe9bf8ab7fe498ffa2bbd0cfc8f6f43a6bbaa5eda3e20ef1b70dca7c8b0ddb216a7ff2f632b694fe0735805638975abb441c621ec0bd2e6d4656353f316c15
languageName: node
linkType: hard
"vue-eslint-parser@npm:^9.0.1":
version: 9.0.3
resolution: "vue-eslint-parser@npm:9.0.3"