11<script setup lang="ts">
2- import type { ProviderMetadata } from ' @proj-airi/stage-ui/stores/providers'
3- import type { Ref } from ' vue'
4-
5- import { IconStatusItem } from ' @proj-airi/stage-ui/components'
2+ import { IconStatusItem , RippleGrid } from ' @proj-airi/stage-ui/components'
3+ import { useRippleGridState } from ' @proj-airi/stage-ui/composables/use-ripple-grid-state'
64import { useScrollToHash } from ' @proj-airi/stage-ui/composables/useScrollToHash'
75import { useProvidersStore } from ' @proj-airi/stage-ui/stores/providers'
8- import { breakpointsTailwind , useBreakpoints } from ' @vueuse/core'
96import { storeToRefs } from ' pinia'
107import { computed } from ' vue'
11- import { onBeforeRouteLeave , useRoute } from ' vue-router'
12-
13- import { useProvidersPageStore } from ' ./store'
14-
15- interface ProviderBlock {
16- id: string
17- icon: string
18- title: string
19- description: string
20- providersRef: Ref <ProviderMetadata []>
21- }
22-
23- interface ProviderRenderData extends ProviderMetadata {
24- renderIndex: number
25- }
26-
27- interface ComputedProviderBlock {
28- id: string
29- icon: string
30- title: string
31- description: string
32- providers: ProviderRenderData []
33- }
8+ import { useRoute } from ' vue-router'
349
3510const route = useRoute ()
3611const providersStore = useProvidersStore ()
37- const providersPageStore = useProvidersPageStore ()
38- const breakpoints = useBreakpoints (breakpointsTailwind )
12+ const { lastClickedIndex, setLastClickedIndex } = useRippleGridState ()
3913
4014const {
4115 allChatProvidersMetadata,
4216 allAudioSpeechProvidersMetadata,
4317 allAudioTranscriptionProvidersMetadata,
4418} = storeToRefs (providersStore )
4519
46- const { lastClickedProviderIndex } = storeToRefs (providersPageStore )
47-
48- const providerBlocksConfig: ProviderBlock [] = [
20+ const providerBlocksConfig = [
4921 {
5022 id: ' chat' ,
5123 icon: ' i-solar:chat-square-like-bold-duotone' ,
@@ -69,7 +41,7 @@ const providerBlocksConfig: ProviderBlock[] = [
6941 },
7042]
7143
72- const providerBlocks = computed < ComputedProviderBlock []> (() => {
44+ const providerBlocks = computed (() => {
7345 let globalIndex = 0
7446 return providerBlocksConfig .map (block => ({
7547 id: block .id ,
@@ -83,48 +55,13 @@ const providerBlocks = computed<ComputedProviderBlock[]>(() => {
8355 }))
8456})
8557
86- const cols = computed (() => {
87- if (breakpoints .greaterOrEqual (' xl' ).value ) {
88- return 3
89- }
90- if (breakpoints .greaterOrEqual (' sm' ).value ) {
91- return 2
92- }
93- return 1
94- })
95-
96- function getAnimationDelay(renderIndex : number ) {
97- const numCols = cols .value
98-
99- const currentRow = Math .floor (renderIndex / numCols )
100- const currentCol = renderIndex % numCols
101-
102- const clickedRow = Math .floor (lastClickedProviderIndex .value / numCols )
103- const clickedCol = lastClickedProviderIndex .value % numCols
104-
105- // manhattan distance
106- const distance = Math .abs (currentRow - clickedRow ) + Math .abs (currentCol - clickedCol )
107-
108- return distance * 80
109- }
110-
111- function handleProviderClick(renderIndex : number ) {
112- providersPageStore .setLastClickedProviderIndex (renderIndex )
113- }
114-
11558useScrollToHash (() => route .hash , {
11659 auto: true , // automatically react to route hash
11760 offset: 16 , // header + margin spacing
11861 behavior: ' smooth' , // smooth scroll animation
11962 maxRetries: 15 , // retry if target element isn't ready
12063 retryDelay: 150 , // wait between retries
12164})
122-
123- onBeforeRouteLeave ((to , from ) => {
124- if (! to .path .startsWith (' /settings/providers/' )) {
125- providersPageStore .resetLastClickedProviderIndex ()
126- }
127- })
12865 </script >
12966
13067<template >
@@ -145,47 +82,41 @@ onBeforeRouteLeave((to, from) => {
14582 </div >
14683 </div >
14784
148- <template v-for =" (block , blockIndex ) in providerBlocks " :key =" block .id " >
149- <div flex =" ~ row items-center gap-2" :class =" { 'my-5': blockIndex > 0 }" >
150- <div :id =" block.id" :class =" block.icon" text =" neutral-500 dark:neutral-400 4xl" />
151- <div >
85+ <RippleGrid
86+ :sections =" providerBlocks"
87+ :get-items =" block => block.providers"
88+ :columns =" { default: 1, sm: 2, xl: 3 }"
89+ :origin-index =" lastClickedIndex"
90+ @item-click =" ({ globalIndex }) => setLastClickedIndex(globalIndex)"
91+ >
92+ <template #header =" { section: block } " >
93+ <div flex =" ~ row items-center gap-2" >
94+ <div :id =" block.id" :class =" block.icon" text =" neutral-500 dark:neutral-400 4xl" />
15295 <div >
153- <span text =" neutral-300 dark:neutral-500 sm sm:base" >{{ block.description }}</span >
154- </div >
155- <div flex text-nowrap text =" 2xl sm:3xl" font-normal >
15696 <div >
157- {{ block.title }}
97+ <span text =" neutral-300 dark:neutral-500 sm sm:base" >{{ block.description }}</span >
98+ </div >
99+ <div flex text-nowrap text =" 2xl sm:3xl" font-normal >
100+ <div >
101+ {{ block.title }}
102+ </div >
158103 </div >
159104 </div >
160105 </div >
161- </div >
162- <div grid =" ~ cols-1 sm:cols-2 xl:cols-3 gap-4" >
163- <div
164- v-for =" provider of block.providers"
165- :key =" provider.id"
166- v-motion
167- :initial =" { opacity: 0, y: 10 }"
168- :enter =" { opacity: 1,
169- y: 0,
170- transition: {
171- duration: 250,
172- delay: getAnimationDelay(provider.renderIndex),
173- } }"
106+ </template >
107+
108+ <template #item =" { item: provider } " >
109+ <IconStatusItem
110+ :title =" provider.localizedName || 'Unknown'"
111+ :description =" provider.localizedDescription"
112+ :icon =" provider.icon"
113+ :icon-color =" provider.iconColor"
114+ :icon-image =" provider.iconImage"
174115 :to =" `/settings/providers/${provider.category}/${provider.id}`"
175- @click =" handleProviderClick(provider.renderIndex)"
176- >
177- <IconStatusItem
178- :title =" provider.localizedName || 'Unknown'"
179- :description =" provider.localizedDescription"
180- :icon =" provider.icon"
181- :icon-color =" provider.iconColor"
182- :icon-image =" provider.iconImage"
183- :to =" `/settings/providers/${provider.category}/${provider.id}`"
184- :configured =" provider.configured"
185- />
186- </div >
187- </div >
188- </template >
116+ :configured =" provider.configured"
117+ />
118+ </template >
119+ </RippleGrid >
189120 </div >
190121 <div
191122 v-motion
0 commit comments