route v1
This commit is contained in:
		| @@ -19,31 +19,33 @@ | ||||
|         <div class="max-h-72 overflow-auto results-scroll"> | ||||
|           <template v-if="results.length"> | ||||
|             <ul class="divide-y divide-slate-100 dark:divide-slate-800"> | ||||
|               <li v-for="(r, idx) in results" :key="r.key" :class="[ | ||||
|               <li v-for="(resultItem, idx) in results" :key="resultItem.key" :class="[ | ||||
|                 'flex cursor-pointer items-center justify-between gap-3 px-2 py-3 hover:bg-slate-50 dark:hover:bg-slate-800', | ||||
|                 idx === activeIndex ? 'bg-slate-100 dark:bg-slate-800' : '' | ||||
|               ]" @mouseenter="activeIndex = idx" @click="activate(r)"> | ||||
|               ]" @mouseenter="activeIndex = idx" @click="selectResult(resultItem)"> | ||||
|                 <div class="flex items-center gap-3"> | ||||
|                   <img v-if="coverUrlFor(r)" :src="coverUrlFor(r)" alt="" loading="lazy" | ||||
|                   <img v-if="coverUrlFor(resultItem)" :src="coverUrlFor(resultItem)" alt="" loading="lazy" | ||||
|                     class="h-10 w-10 rounded object-cover ring-1 ring-slate-200 dark:ring-slate-700" /> | ||||
|                   <span | ||||
|                     class="inline-flex min-w-[68px] items-center justify-center rounded-md border px-2 py-0.5 text-xs font-semibold uppercase text-slate-600 dark:text-slate-300 dark:border-slate-600">{{ | ||||
|                       r.type }}</span> | ||||
|                   <span class="text-slate-900 dark:text-slate-100">{{ r.label }}</span> | ||||
|                       resultItem.type }}</span> | ||||
|                   <span class="text-slate-900 dark:text-slate-100">{{ resultItem.label }}</span> | ||||
|                 </div> | ||||
|                 <div class="flex items-center gap-2"> | ||||
|                   <span v-if="r.sublabel" class="text-sm text-slate-500 dark:text-slate-400">{{ r.sublabel }}</span> | ||||
|                   <button | ||||
|                     v-if="r.type === 'TRACK'" | ||||
|                     class="p-1 rounded hover:bg-slate-100 dark:hover:bg-slate-800" | ||||
|                     aria-label="Toggle favorite" | ||||
|                     @click.stop="fav.toggle(r.payload)" | ||||
|                   > | ||||
|                     <svg v-if="fav.isFavorite(r.payload.id)" class="h-5 w-5 text-rose-500" viewBox="0 0 24 24" fill="currentColor"> | ||||
|                       <path d="M12 21s-6.716-4.35-9.428-7.062C.86 12.226.5 10.64.5 9.5.5 6.462 2.962 4 6 4c1.657 0 3.157.806 4 2.09C10.843 4.806 12.343 4 14 4c3.038 0 5.5 2.462 5.5 5.5 0 1.14-.36 2.726-2.072 4.438C18.716 16.65 12 21 12 21z"/> | ||||
|                   <span v-if="resultItem.sublabel" class="text-sm text-slate-500 dark:text-slate-400">{{ | ||||
|                     resultItem.sublabel }}</span> | ||||
|                   <button v-if="resultItem.type === 'TRACK'" | ||||
|                     class="p-1 rounded hover:bg-slate-100 dark:hover:bg-slate-800" aria-label="Toggle favorite" | ||||
|                     @click.stop="fav.toggle(resultItem.payload)"> | ||||
|                     <svg v-if="fav.isFavorite(resultItem.payload.id)" class="h-5 w-5 text-rose-500" viewBox="0 0 24 24" | ||||
|                       fill="currentColor"> | ||||
|                       <path | ||||
|                         d="M12 21s-6.716-4.35-9.428-7.062C.86 12.226.5 10.64.5 9.5.5 6.462 2.962 4 6 4c1.657 0 3.157.806 4 2.09C10.843 4.806 12.343 4 14 4c3.038 0 5.5 2.462 5.5 5.5 0 1.14-.36 2.726-2.072 4.438C18.716 16.65 12 21 12 21z" /> | ||||
|                     </svg> | ||||
|                     <svg v-else class="h-5 w-5 text-slate-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                       <path d="M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.6l-1-1a5.5 5.5 0 0 0-7.8 7.8l1 1L12 21l7.8-7.6 1-1a5.5 5.5 0 0 0 0-7.8z"/> | ||||
|                     <svg v-else class="h-5 w-5 text-slate-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" | ||||
|                       stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                       <path | ||||
|                         d="M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.6l-1-1a5.5 5.5 0 0 0-7.8 7.8l1 1L12 21l7.8-7.6 1-1a5.5 5.5 0 0 0 0-7.8z" /> | ||||
|                     </svg> | ||||
|                   </button> | ||||
|                 </div> | ||||
| @@ -107,12 +109,12 @@ const results = computed<ResultItem[]>(() => { | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|   for (const t of data.tracks) { | ||||
|     const artistName = typeof t.artist === 'object' && t.artist ? (t.artist as any).name ?? '' : String(t.artist) | ||||
|     const label = t.title | ||||
|   for (const track of data.tracks) { | ||||
|     const artistName = typeof track.artist === 'object' && track.artist ? (track.artist as any).name ?? '' : String(track.artist) | ||||
|     const label = track.title | ||||
|     const sub = artistName | ||||
|     if (normalized(label).includes(q) || normalized(sub).includes(q)) { | ||||
|       out.push({ key: `track:${t.id}`, type: 'TRACK', label, sublabel: sub, payload: t }) | ||||
|       out.push({ key: `track:${track.id}`, type: 'TRACK', label, sublabel: sub, payload: track }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -124,34 +126,34 @@ const move = (delta: number) => { | ||||
|   activeIndex.value = (activeIndex.value + delta + results.value.length) % results.value.length | ||||
| } | ||||
|  | ||||
| const coverUrlFor = (r: ResultItem): string | undefined => { | ||||
|   if (r.type === 'BOX') { | ||||
|     return `/${r.payload.id}/cover.jpg` | ||||
| const coverUrlFor = (ResultItem: ResultItem): string | undefined => { | ||||
|   if (ResultItem.type === 'BOX') { | ||||
|     return `/${ResultItem.payload.id}/cover.jpg` | ||||
|   } | ||||
|   if (r.type === 'TRACK') { | ||||
|     const t = r.payload | ||||
|     if (t && t.type === 'playlist' && t.coverId) return t.coverId as string | ||||
|     if (t && t.coverId) return t.coverId as string | ||||
|     return `/${t.boxId}/cover.jpg` | ||||
|   if (ResultItem.type === 'TRACK') { | ||||
|     const track = ResultItem.payload | ||||
|     if (track && track.type === 'playlist' && track.coverId) return track.coverId as string | ||||
|     if (track && track.coverId) return track.coverId as string | ||||
|     return `/${track.boxId}/cover.jpg` | ||||
|   } | ||||
|   if (r.type === 'ARTIST') { | ||||
|     const tracks = data.getTracksByArtistId(r.payload.id) | ||||
|   if (ResultItem.type === 'ARTIST') { | ||||
|     const tracks = data.getTracksByArtistId(ResultItem.payload.id) | ||||
|     if (tracks && tracks.length) { | ||||
|       const t = tracks[0]! | ||||
|       if (t.type === 'playlist' && t.coverId) return t.coverId as string | ||||
|       if (t.coverId) return t.coverId as string | ||||
|       return `/${t.boxId}/cover.jpg` | ||||
|       const track = tracks[0]! | ||||
|       if (track.type === 'playlist' && track.coverId) return track.coverId as string | ||||
|       if (track.coverId) return track.coverId as string | ||||
|       return `/${track.boxId}/cover.jpg` | ||||
|     } | ||||
|   } | ||||
|   return undefined | ||||
| } | ||||
|  | ||||
| const activate = (r: ResultItem) => { | ||||
|   if (r.type === 'BOX') { | ||||
|     ui.selectBox(r.payload.id) | ||||
|     nextTick(() => ui.scrollToBox(r.payload)) | ||||
|   } else if (r.type === 'ARTIST') { | ||||
|     const tracks = data.getTracksByArtistId(r.payload.id) | ||||
| const selectResult = (ResultItem: ResultItem) => { | ||||
|   if (ResultItem.type === 'BOX') { | ||||
|     ui.selectBox(ResultItem.payload.id) | ||||
|     nextTick(() => ui.scrollToBox(ResultItem.payload)) | ||||
|   } else if (ResultItem.type === 'ARTIST') { | ||||
|     const tracks = data.getTracksByArtistId(ResultItem.payload.id) | ||||
|     if (tracks && tracks.length) { | ||||
|       const track = tracks[0]! | ||||
|       const box = data.getBoxById(track.boxId) | ||||
| @@ -159,19 +161,25 @@ const activate = (r: ResultItem) => { | ||||
|         ui.selectBox(box.id) | ||||
|       } | ||||
|     } | ||||
|   } else if (r.type === 'TRACK') { | ||||
|     const box = data.getBoxById(r.payload.boxId) | ||||
|     if (box) { | ||||
|       ui.selectBox(box.id) | ||||
|       player.playTrack(r.payload) | ||||
|   } else if (ResultItem.type === 'TRACK') { | ||||
|     const track = ResultItem.payload | ||||
|     // If the selected track is a favorite, just play it without navigating/selecting its box | ||||
|     if (fav.isFavorite(track.id)) { | ||||
|       player.playTrack(track) | ||||
|     } else { | ||||
|       const box = data.getBoxById(track.boxId) | ||||
|       if (box) { | ||||
|         ui.selectBox(box.id) | ||||
|         player.playTrack(track) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   close() | ||||
| } | ||||
|  | ||||
| const confirm = () => { | ||||
|   const r = results.value[activeIndex.value] | ||||
|   if (r) activate(r) | ||||
|   const ResultItem = results.value[activeIndex.value] | ||||
|   if (ResultItem) selectResult(ResultItem) | ||||
| } | ||||
|  | ||||
| watch( | ||||
| @@ -199,7 +207,7 @@ watch( | ||||
|  | ||||
| .results-scroll { | ||||
|   scrollbar-width: thin; | ||||
|   scrollbar-color: rgba(100,116,139,0.6) transparent; | ||||
|   scrollbar-color: rgba(100, 116, 139, 0.6) transparent; | ||||
| } | ||||
|  | ||||
| .results-scroll::-webkit-scrollbar { | ||||
| @@ -211,17 +219,17 @@ watch( | ||||
| } | ||||
|  | ||||
| .results-scroll::-webkit-scrollbar-thumb { | ||||
|   background-color: rgba(100,116,139,0.6); | ||||
|   background-color: rgba(100, 116, 139, 0.6); | ||||
|   border-radius: 9999px; | ||||
|   border: 2px solid transparent; | ||||
|   background-clip: content-box; | ||||
| } | ||||
|  | ||||
| .dark .results-scroll { | ||||
|   scrollbar-color: rgba(148,163,184,0.5) transparent; | ||||
|   scrollbar-color: rgba(148, 163, 184, 0.5) transparent; | ||||
| } | ||||
|  | ||||
| .dark .results-scroll::-webkit-scrollbar-thumb { | ||||
|   background-color: rgba(148,163,184,0.5); | ||||
|   background-color: rgba(148, 163, 184, 0.5); | ||||
| } | ||||
| </style> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user