@@ -73,7 +73,10 @@ import {
7373import { getPickNumbersForQueuedItems } from "~/lib/pick-order" ;
7474import {
7575 formatPlaylistItemSummaryLine ,
76+ getPlaylistDisplayParts ,
7677 getResolvedPlaylistCandidates ,
78+ playlistDisplayCandidateHasLyrics ,
79+ playlistDisplayItemHasLyrics ,
7780} from "~/lib/playlist/management-display" ;
7881import {
7982 getPlaylistEndpoint ,
@@ -121,6 +124,7 @@ export type PlaylistItem = {
121124 songCreator ?: string ;
122125 songTuning ?: string ;
123126 songPartsJson ?: string ;
127+ songHasLyrics ?: boolean | null ;
124128 songDurationText ?: string ;
125129 songUrl ?: string ;
126130 songSourceUpdatedAt ?: number | null ;
@@ -152,6 +156,7 @@ export type PlaylistCandidate = {
152156 creator ?: string ;
153157 tuning ?: string ;
154158 parts ?: string [ ] ;
159+ hasLyrics ?: boolean ;
155160 durationText ?: string ;
156161 year ?: number ;
157162 sourceUpdatedAt ?: number ;
@@ -180,7 +185,8 @@ const PLAYLIST_PREVIEW_CANDIDATES: PlaylistCandidate[] = [
180185 album : "Neon Noir" ,
181186 creator : "JohnCryx" ,
182187 tuning : "E Standard | A Standard" ,
183- parts : [ "lead" , "rhythm" , "bass" , "voice" ] ,
188+ parts : [ "lead" , "rhythm" , "bass" ] ,
189+ hasLyrics : true ,
184190 durationText : "3:49" ,
185191 sourceUpdatedAt : Date . parse ( "2025-12-08T00:00:00Z" ) ,
186192 downloads : 4284 ,
@@ -217,7 +223,8 @@ const PLAYLIST_PREVIEW_ITEM: PlaylistItem = {
217223 songAlbum : "Neon Noir" ,
218224 songCreator : "JohnCryx" ,
219225 songTuning : "E Standard | A Standard" ,
220- songPartsJson : JSON . stringify ( [ "lead" , "rhythm" , "bass" , "voice" ] ) ,
226+ songPartsJson : JSON . stringify ( [ "lead" , "rhythm" , "bass" ] ) ,
227+ songHasLyrics : true ,
221228 songDurationText : "3:49" ,
222229 songUrl : "https://customsforge.com/index.php?/customs/99081" ,
223230 songSourceUpdatedAt : Date . parse ( "2025-12-08T00:00:00Z" ) ,
@@ -250,6 +257,7 @@ type SearchResponse = {
250257 creator ?: string ;
251258 tuning ?: string ;
252259 parts ?: string [ ] ;
260+ hasLyrics ?: boolean ;
253261 durationText ?: string ;
254262 source : string ;
255263 sourceUrl ?: string ;
@@ -974,6 +982,7 @@ export function PlaylistManagementSurface(
974982 const isBlacklistedCharter =
975983 song . authorId != null &&
976984 blacklistedCharterIds . has ( song . authorId ) ;
985+ const displaySongParts = getPlaylistDisplayParts ( song . parts ) ;
977986
978987 return (
979988 < div
@@ -1019,8 +1028,10 @@ export function PlaylistManagementSurface(
10191028 { song . tuning ?? t ( "management.manual.noTuningInfo" ) }
10201029 </ p >
10211030 < p className = "mt-1 truncate text-sm text-(--muted)" >
1022- { song . parts ?. length
1023- ? song . parts . join ( ", " )
1031+ { displaySongParts . length > 0
1032+ ? displaySongParts
1033+ . map ( ( part ) => formatPathLabel ( part ) )
1034+ . join ( ", " )
10241035 : t ( "management.manual.noPathInfo" ) }
10251036 </ p >
10261037 </ div >
@@ -1057,6 +1068,7 @@ export function PlaylistManagementSurface(
10571068 creator : song . creator ,
10581069 tuning : song . tuning ,
10591070 parts : song . parts ?? [ ] ,
1071+ hasLyrics : song . hasLyrics ,
10601072 durationText : song . durationText ,
10611073 sourceUrl : song . sourceUrl ,
10621074 sourceId : song . sourceId ,
@@ -2267,6 +2279,7 @@ function PlaylistQueueItem(props: {
22672279 ! hasMultipleVersions && resolvedCandidates [ 0 ] ?. sourceUrl
22682280 ? resolvedCandidates [ 0 ] . sourceUrl
22692281 : null ;
2282+ const itemHasLyrics = playlistDisplayItemHasLyrics ( props . item ) ;
22702283
22712284 useEffect ( ( ) => {
22722285 const element = itemRef . current ;
@@ -2526,7 +2539,7 @@ function PlaylistQueueItem(props: {
25262539 unknownArtistLabel : t ( "management.manual.unknownArtist" ) ,
25272540 } ) }
25282541 </ p >
2529- { itemDurationText || compactTuning ? (
2542+ { itemDurationText || compactTuning || itemHasLyrics ? (
25302543 < p className = "flex flex-wrap items-center gap-x-1.5 gap-y-1 text-sm text-(--muted)" >
25312544 { itemDurationText ? (
25322545 < >
@@ -2540,6 +2553,12 @@ function PlaylistQueueItem(props: {
25402553 { compactTuning ? (
25412554 < span title = { compactTuningTitle } > { compactTuning } </ span >
25422555 ) : null }
2556+ { ( itemDurationText || compactTuning ) && itemHasLyrics ? (
2557+ < span aria-hidden = "true" > ·</ span >
2558+ ) : null }
2559+ { itemHasLyrics ? (
2560+ < span > { t ( "management.item.lyrics" ) } </ span >
2561+ ) : null }
25432562 </ p >
25442563 ) : null }
25452564 < div className = "flex flex-wrap items-center gap-x-3 gap-y-1" >
@@ -3118,6 +3137,8 @@ function PlaylistVersionsTable(props: {
31183137 const isBlacklistedCharter =
31193138 candidate . authorId != null &&
31203139 props . blacklistedCharterIds . has ( candidate . authorId ) ;
3140+ const displayParts = getPlaylistDisplayParts ( candidate . parts ) ;
3141+ const hasLyrics = playlistDisplayCandidateHasLyrics ( candidate ) ;
31213142
31223143 return (
31233144 < tr
@@ -3154,7 +3175,7 @@ function PlaylistVersionsTable(props: {
31543175 </ td >
31553176 < td className = "px-4 py-3" >
31563177 < div className = "flex flex-wrap gap-1" >
3157- { ( candidate . parts ?? [ ] ) . map ( ( part ) => (
3178+ { displayParts . map ( ( part ) => (
31583179 < span
31593180 key = { `${ candidate . id } -${ part } ` }
31603181 className = { getPlaylistPathBadgeClass ( part ) }
@@ -3163,7 +3184,12 @@ function PlaylistVersionsTable(props: {
31633184 { getPathAbbreviation ( part ) }
31643185 </ span >
31653186 ) ) }
3166- { ( candidate . parts ?? [ ] ) . length === 0 ? (
3187+ { hasLyrics ? (
3188+ < span className = "inline-flex h-6 items-center justify-center border border-(--border-strong) bg-(--panel) px-1.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-(--muted)" >
3189+ { t ( "management.versionsTable.lyrics" ) }
3190+ </ span >
3191+ ) : null }
3192+ { displayParts . length === 0 && ! hasLyrics ? (
31673193 < span className = "text-xs text-(--muted)" >
31683194 { t ( "management.versionsTable.unknown" ) }
31693195 </ span >
@@ -3243,10 +3269,6 @@ function getPathAbbreviation(path: string) {
32433269 return "R" ;
32443270 case "bass" :
32453271 return "B" ;
3246- case "lyrics" :
3247- case "voice" :
3248- case "vocals" :
3249- return "V" ;
32503272 default :
32513273 return path . slice ( 0 , 1 ) . toUpperCase ( ) ;
32523274 }
@@ -3260,10 +3282,6 @@ function getPlaylistPathBadgeClass(path: string) {
32603282 return "inline-flex h-6 min-w-6 items-center justify-center border border-sky-700/50 bg-sky-950 px-1.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-sky-100" ;
32613283 case "bass" :
32623284 return "inline-flex h-6 min-w-6 items-center justify-center border border-orange-700/50 bg-orange-950 px-1.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-orange-100" ;
3263- case "lyrics" :
3264- case "voice" :
3265- case "vocals" :
3266- return "inline-flex h-6 min-w-6 items-center justify-center border border-violet-700/50 bg-violet-950 px-1.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-violet-100" ;
32673285 default :
32683286 return "inline-flex h-6 min-w-6 items-center justify-center border border-(--border) bg-(--panel-strong) px-1.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-(--text)" ;
32693287 }
0 commit comments