feat(emoji-picker) add lazy loading to improve initial load performance

This commit is contained in:
chadmin 2024-10-26 19:25:21 -07:00
parent 0b1b98f93e
commit eb54661949
2 changed files with 57 additions and 11 deletions

View file

@ -2,6 +2,7 @@
import emojiUnicodeData from 'unicode-emoji-json'; import emojiUnicodeData from 'unicode-emoji-json';
import emojiKeywords from 'emojilib'; import emojiKeywords from 'emojilib';
import _ from 'lodash'; import _ from 'lodash';
import { IconChevronRight, IconSearch } from '@tabler/icons-vue';
import type { EmojiInfo } from './emoji.types'; import type { EmojiInfo } from './emoji.types';
import { useFuzzySearch } from '@/composable/fuzzySearch'; import { useFuzzySearch } from '@/composable/fuzzySearch';
import useDebouncedRef from '@/composable/debouncedref'; import useDebouncedRef from '@/composable/debouncedref';
@ -36,10 +37,36 @@ const { searchResult } = useFuzzySearch({
isCaseSensitive: false, isCaseSensitive: false,
}, },
}); });
const emojisPerPage = 30; // Number of emojis to load per group initially
// Tracks how many emojis are shown per group and the collapsed state of each group
const groupLoadLimits = ref(
emojisGroups.reduce((acc, group) => {
acc[group.group] = { limit: emojisPerPage, collapsed: false };
return acc;
}, {} as Record<string, { limit: number; collapsed: boolean }>),
);
// Toggles the visibility of the emoji group
function toggleGroup(group: string) {
groupLoadLimits.value[group].collapsed = !groupLoadLimits.value[group].collapsed;
};
// Loads more emojis incrementally
function loadMoreEmojis(group: string) {
groupLoadLimits.value[group].limit += emojisPerPage;
};
// Loads all emojis in the group at once
function loadAllEmojis(group: string) {
groupLoadLimits.value[group].limit = emojisGroups.find(g => g.group === group)?.emojiInfos.length || 0;
};
</script> </script>
<template> <template>
<div mx-auto max-w-2400px important:flex-1> <div mx-auto max-w-2400px important:flex-1>
<!-- Emoji Search Bar -->
<div flex items-center gap-3> <div flex items-center gap-3>
<c-input-text <c-input-text
v-model:value="searchQuery" v-model:value="searchQuery"
@ -47,11 +74,12 @@ const { searchResult } = useFuzzySearch({
mx-auto max-w-600px mx-auto max-w-600px
> >
<template #prefix> <template #prefix>
<icon-mdi-search mr-6px color-black op-70 dark:color-white /> <n-icon :component="IconSearch" mr-6px color-black op-70 dark:color-white />
</template> </template>
</c-input-text> </c-input-text>
</div> </div>
<!-- Emoji Search Results -->
<div v-if="searchQuery.trim().length > 0"> <div v-if="searchQuery.trim().length > 0">
<div <div
v-if="searchResult.length === 0" v-if="searchResult.length === 0"
@ -71,16 +99,34 @@ const { searchResult } = useFuzzySearch({
</div> </div>
</div> </div>
<div <div v-for="{ group, emojiInfos } in emojisGroups" :key="group">
v-for="{ group, emojiInfos } in emojisGroups" <!-- Collapsible Group Header with Chevron Icons -->
v-else <div
:key="group" mt-4 text-20px font-bold
> style="cursor: pointer;"
<div mt-4 text-20px font-bold> @click="toggleGroup(group)"
{{ group }} >
<n-icon
:component="IconChevronRight"
:class="{ 'rotate-0': groupLoadLimits[group].collapsed, 'rotate-90': !groupLoadLimits[group].collapsed }"
mr-1 text-16px lh-1 op-50 transition
/>
<span>{{ group }}</span>
</div> </div>
<emoji-grid :emoji-infos="emojiInfos" /> <!-- Emoji Grid, conditionally displayed based on collapse state -->
<div v-show="!groupLoadLimits[group].collapsed">
<emoji-grid :emoji-infos="emojiInfos.slice(0, groupLoadLimits[group].limit)" />
<div v-if="groupLoadLimits[group].limit < emojiInfos.length" style="display: flex; gap: 8px; margin-top: 8px; justify-content: center;">
<c-button @click="loadMoreEmojis(group)">
Load More
</c-button>
<c-button @click="loadAllEmojis(group)">
Load All
</c-button>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>

View file

@ -1,4 +1,4 @@
import { MoodSmile } from '@vicons/tabler'; import { IconMoodSmile } from '@tabler/icons-vue';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin'; import { translate } from '@/plugins/i18n.plugin';
@ -8,6 +8,6 @@ export const tool = defineTool({
description: translate('tools.emoji-picker.description'), description: translate('tools.emoji-picker.description'),
keywords: ['emoji', 'picker', 'unicode', 'copy', 'paste'], keywords: ['emoji', 'picker', 'unicode', 'copy', 'paste'],
component: () => import('./emoji-picker.vue'), component: () => import('./emoji-picker.vue'),
icon: MoodSmile, icon: IconMoodSmile,
createdAt: new Date('2023-08-07'), createdAt: new Date('2023-08-07'),
}); });