feat(emoji-picker) add lazy loading to improve initial load performance
This commit is contained in:
		
							parent
							
								
									0b1b98f93e
								
							
						
					
					
						commit
						eb54661949
					
				| @ -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 v-for="{ group, emojiInfos } in emojisGroups" :key="group"> | ||||||
|  |       <!-- Collapsible Group Header with Chevron Icons --> | ||||||
|       <div |       <div | ||||||
|       v-for="{ group, emojiInfos } in emojisGroups" |         mt-4 text-20px font-bold | ||||||
|       v-else |         style="cursor: pointer;" | ||||||
|       :key="group" |         @click="toggleGroup(group)" | ||||||
|       > |       > | ||||||
|       <div mt-4 text-20px font-bold> |         <n-icon | ||||||
|         {{ group }} |           :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> | ||||||
|  | |||||||
| @ -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'), | ||||||
| }); | }); | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user