forked from FoundKeyGang/FoundKey
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:
parent
191b2692d2
commit
8840724a7c
|
@ -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",
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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>
|
||||
|
|
52
yarn.lock
52
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue