@@ -305,6 +305,11 @@ type PanelViewerRequestSubmitInput =
305305 requestKind : "regular" | "vip" ;
306306 requestMode : "random" | "choice" ;
307307 vipTokenCost ?: number ;
308+ }
309+ | {
310+ requestKind : "regular" | "vip" ;
311+ requestMode : "favorite" ;
312+ vipTokenCost ?: number ;
308313 } ;
309314
310315type PanelDropTargetState = {
@@ -1145,10 +1150,14 @@ function ExtensionPanelAppContent(props: {
11451150 requestMode : "catalog" ,
11461151 requestedPath : input . requestedPath ,
11471152 }
1148- : {
1149- query : input . query . trim ( ) ,
1150- requestMode : input . requestMode ,
1151- } ) ,
1153+ : "query" in input
1154+ ? {
1155+ query : input . query . trim ( ) ,
1156+ requestMode : input . requestMode ,
1157+ }
1158+ : {
1159+ requestMode : input . requestMode ,
1160+ } ) ,
11521161 requestKind : input . requestKind ,
11531162 vipTokenCost : input . vipTokenCost ,
11541163 itemId : editingRequestItemId ?? undefined ,
@@ -2472,7 +2481,10 @@ export function ExtensionPanelModeratorPreview() {
24722481 }
24732482
24742483 async function handleSubmitRequest ( input : PanelViewerRequestSubmitInput ) {
2475- const normalizedQuery = "query" in input ? input . query . trim ( ) : null ;
2484+ const normalizedQuery =
2485+ "query" in input && typeof input . query === "string"
2486+ ? input . query . trim ( )
2487+ : null ;
24762488 const song =
24772489 "songId" in input
24782490 ? ( searchResults ?. items . find (
@@ -2521,10 +2533,14 @@ export function ExtensionPanelModeratorPreview() {
25212533 requestedPath :
25222534 "songId" in input ? input . requestedPath : undefined ,
25232535 }
2524- : {
2525- query : normalizedQuery ?? "" ,
2526- requestMode : input . requestMode ,
2527- } ) ,
2536+ : "query" in input
2537+ ? {
2538+ query : normalizedQuery ?? "" ,
2539+ requestMode : input . requestMode ,
2540+ }
2541+ : {
2542+ requestMode : input . requestMode ,
2543+ } ) ,
25282544 requestKind : input . requestKind ,
25292545 vipTokenCost : input . vipTokenCost ,
25302546 replaceExisting : false ,
@@ -3039,18 +3055,21 @@ function PanelPlaylistRow(props: {
30393055 ( ! props . canManagePlaylist && canOpenViewerActions ) ;
30403056 const isActionTrayOpen = props . expandedActionItemId === props . itemId ;
30413057 const confirmingRemove = props . confirmingRemoveItemId === props . itemId ;
3042- const itemHasLyrics =
3043- props . canManagePlaylist &&
3044- playlistDisplayItemHasLyrics ( {
3045- songHasLyrics : props . item . songHasLyrics === true ,
3046- songPartsJson : getString ( props . item , "songPartsJson" ) ?? undefined ,
3047- } ) ;
30483058 const canReorder = props . canReorderPlaylist && ! isCurrent ;
30493059 const isDragging = props . draggingItemId === props . itemId ;
30503060 const dropEdge =
30513061 props . dropTargetState ?. itemId === props . itemId
30523062 ? props . dropTargetState . edge
30533063 : null ;
3064+ const itemHasLyrics =
3065+ props . canManagePlaylist &&
3066+ playlistDisplayItemHasLyrics ( {
3067+ songHasLyrics :
3068+ typeof props . item . songHasLyrics === "boolean"
3069+ ? props . item . songHasLyrics
3070+ : null ,
3071+ songPartsJson : getString ( props . item , "songPartsJson" ) ?? undefined ,
3072+ } ) ;
30543073
30553074 useEffect ( ( ) => {
30563075 const element = itemRef . current ;
@@ -3303,6 +3322,11 @@ function PanelPlaylistRow(props: {
33033322 { getPanelRequestedPathLabel ( props . item ) }
33043323 </ span >
33053324 ) : null }
3325+ { itemHasLyrics ? (
3326+ < span className = "inline-flex h-5 items-center border border-(--border-strong) bg-(--panel-soft) px-1.5 text-[9px] leading-none font-medium uppercase tracking-[0.12em] text-(--muted)" >
3327+ { t ( "queue.lyrics" ) }
3328+ </ span >
3329+ ) : null }
33063330 { ( isVipRequest &&
33073331 getPanelStoredVipTokenCost ( props . item ) > 1 ) ||
33083332 ( ! isVipRequest &&
@@ -3626,19 +3650,47 @@ function PanelSpecialRequestControls(props: {
36263650 pendingAction : string | null ;
36273651 isEditingRequest : boolean ;
36283652 onSubmit : (
3629- requestMode : "random" | "choice" ,
3653+ requestMode : "random" | "favorite" | " choice",
36303654 requestKind : "regular" | "vip"
36313655 ) => void ;
36323656} ) {
36333657 const { t } = useLocaleTranslation ( "extension" ) ;
36343658 const normalizedQuery = props . query . trim ( ) ;
3635- const regularDisabledReason = getPanelSpecialRequestDisabledReason ( {
3659+ const randomDisabledReason = getPanelSpecialRequestDisabledReason ( {
3660+ query : normalizedQuery ,
3661+ requestMode : "random" ,
3662+ canRequest : props . canRequest ,
3663+ t,
3664+ } ) ;
3665+ const randomVipDisabledReason = getPanelSpecialRequestDisabledReason ( {
3666+ query : normalizedQuery ,
3667+ requestMode : "random" ,
3668+ canRequest : props . canVipRequest ,
3669+ fallbackReason : props . vipDisabledReason ?? t ( "vip.insufficient" ) ,
3670+ t,
3671+ } ) ;
3672+ const choiceDisabledReason = getPanelSpecialRequestDisabledReason ( {
3673+ query : normalizedQuery ,
3674+ requestMode : "choice" ,
3675+ canRequest : props . canRequest ,
3676+ t,
3677+ } ) ;
3678+ const choiceVipDisabledReason = getPanelSpecialRequestDisabledReason ( {
3679+ query : normalizedQuery ,
3680+ requestMode : "choice" ,
3681+ canRequest : props . canVipRequest ,
3682+ fallbackReason : props . vipDisabledReason ?? t ( "vip.insufficient" ) ,
3683+ t,
3684+ } ) ;
3685+ const favoriteDisabledReason = getPanelSpecialRequestDisabledReason ( {
36363686 query : normalizedQuery ,
3687+ requestMode : "favorite" ,
36373688 canRequest : props . canRequest ,
36383689 t,
36393690 } ) ;
3640- const vipDisabledReason = getPanelSpecialRequestDisabledReason ( {
3691+ const favoriteVipDisabledReason = getPanelSpecialRequestDisabledReason ( {
36413692 query : normalizedQuery ,
3693+ requestMode : "favorite" ,
36423694 canRequest : props . canVipRequest ,
36433695 fallbackReason : props . vipDisabledReason ?? t ( "vip.insufficient" ) ,
36443696 t,
@@ -3652,8 +3704,8 @@ function PanelSpecialRequestControls(props: {
36523704 < div className = "grid gap-2" >
36533705 < PanelSpecialRequestRow
36543706 label = { t ( "requests.randomSong" ) }
3655- disabledReason = { regularDisabledReason }
3656- vipDisabledReason = { vipDisabledReason }
3707+ disabledReason = { randomDisabledReason }
3708+ vipDisabledReason = { randomVipDisabledReason }
36573709 busy = { props . pendingAction != null }
36583710 regularPending = {
36593711 props . pendingAction ===
@@ -3675,10 +3727,33 @@ function PanelSpecialRequestControls(props: {
36753727 onRegularClick = { ( ) => props . onSubmit ( "random" , "regular" ) }
36763728 onVipClick = { ( ) => props . onSubmit ( "random" , "vip" ) }
36773729 />
3730+ < PanelSpecialRequestRow
3731+ label = { t ( "requests.randomFavorite" ) }
3732+ disabledReason = { favoriteDisabledReason }
3733+ vipDisabledReason = { favoriteVipDisabledReason }
3734+ busy = { props . pendingAction != null }
3735+ regularPending = {
3736+ props . pendingAction ===
3737+ getPanelViewerRequestActionKey ( {
3738+ requestMode : "favorite" ,
3739+ requestKind : "regular" ,
3740+ } )
3741+ }
3742+ vipPending = {
3743+ props . pendingAction ===
3744+ getPanelViewerRequestActionKey ( {
3745+ requestMode : "favorite" ,
3746+ requestKind : "vip" ,
3747+ } )
3748+ }
3749+ isEditingRequest = { props . isEditingRequest }
3750+ onRegularClick = { ( ) => props . onSubmit ( "favorite" , "regular" ) }
3751+ onVipClick = { ( ) => props . onSubmit ( "favorite" , "vip" ) }
3752+ />
36783753 < PanelSpecialRequestRow
36793754 label = { t ( "requests.streamerChoice" ) }
3680- disabledReason = { regularDisabledReason }
3681- vipDisabledReason = { vipDisabledReason }
3755+ disabledReason = { choiceDisabledReason }
3756+ vipDisabledReason = { choiceVipDisabledReason }
36823757 busy = { props . pendingAction != null }
36833758 regularPending = {
36843759 props . pendingAction ===
@@ -4892,16 +4967,19 @@ function getPanelViewerRequestActionKey(input: PanelViewerRequestSubmitInput) {
48924967 return `${ input . songId } :${ input . requestKind } :${ input . requestedPath ?? "none" } ` ;
48934968 }
48944969
4895- return `special:${ input . requestMode } :${ input . requestKind } :${ input . query . trim ( ) . toLowerCase ( ) } ` ;
4970+ return "query" in input
4971+ ? `special:${ input . requestMode } :${ input . requestKind } :${ input . query . trim ( ) . toLowerCase ( ) } `
4972+ : `special:${ input . requestMode } :${ input . requestKind } ` ;
48964973}
48974974
48984975function getPanelSpecialRequestDisabledReason ( input : {
48994976 query : string ;
4977+ requestMode : "random" | "favorite" | "choice" ;
49004978 canRequest : boolean ;
49014979 fallbackReason ?: string ;
49024980 t : TFunction ;
49034981} ) {
4904- if ( input . query . length < 2 ) {
4982+ if ( input . requestMode !== "favorite" && input . query . length < 2 ) {
49054983 return input . t ( "search.typeAtLeastTwo" ) ;
49064984 }
49074985
0 commit comments