1
- import { ChevronsUpDown , Plus } from 'lucide-react' ;
1
+ import { ChevronsUpDown , Languages , Plus } from 'lucide-react' ;
2
2
3
3
import { ChannelCodeLabel } from '@/vdb/components/shared/channel-code-label.js' ;
4
4
import {
@@ -7,80 +7,191 @@ import {
7
7
DropdownMenuItem ,
8
8
DropdownMenuLabel ,
9
9
DropdownMenuSeparator ,
10
+ DropdownMenuSub ,
11
+ DropdownMenuSubContent ,
12
+ DropdownMenuSubTrigger ,
10
13
DropdownMenuTrigger ,
11
14
} from '@/vdb/components/ui/dropdown-menu.js' ;
12
15
import { SidebarMenu , SidebarMenuButton , SidebarMenuItem , useSidebar } from '@/vdb/components/ui/sidebar.js' ;
13
16
import { useChannel } from '@/vdb/hooks/use-channel.js' ;
17
+ import { useLocalFormat } from '@/vdb/hooks/use-local-format.js' ;
18
+ import { useServerConfig } from '@/vdb/hooks/use-server-config.js' ;
19
+ import { useUserSettings } from '@/vdb/hooks/use-user-settings.js' ;
14
20
import { Trans } from '@/vdb/lib/trans.js' ;
15
21
import { Link } from '@tanstack/react-router' ;
22
+ import { useState } from 'react' ;
23
+ import { ManageLanguagesDialog } from './manage-languages-dialog.js' ;
24
+
25
+ /**
26
+ * Convert the channel code to initials.
27
+ * Splits by punctuation like '-' and '_' and takes the first letter of each part
28
+ * up to 3 parts.
29
+ *
30
+ * If no splits, takes the first 3 letters.
31
+ */
32
+ function getChannelInitialsFromCode ( code : string ) {
33
+ const parts = code . split ( / [ - _ ] / ) ;
34
+ if ( parts . length > 1 ) {
35
+ return parts
36
+ . filter ( part => part . length > 0 )
37
+ . slice ( 0 , 3 )
38
+ . map ( part => part [ 0 ] )
39
+ . join ( '' ) ;
40
+ } else {
41
+ return code . slice ( 0 , 3 ) ;
42
+ }
43
+ }
16
44
17
45
export function ChannelSwitcher ( ) {
18
46
const { isMobile } = useSidebar ( ) ;
19
47
const { channels, activeChannel, selectedChannel, setSelectedChannel } = useChannel ( ) ;
48
+ const serverConfig = useServerConfig ( ) ;
49
+ const { formatLanguageName } = useLocalFormat ( ) ;
50
+ const {
51
+ settings : { contentLanguage } ,
52
+ setContentLanguage,
53
+ } = useUserSettings ( ) ;
54
+ const [ showManageLanguagesDialog , setShowManageLanguagesDialog ] = useState ( false ) ;
20
55
21
56
// Use the selected channel if available, otherwise fall back to the active channel
22
57
const displayChannel = selectedChannel || activeChannel ;
23
58
59
+ // Get available languages from server config
60
+ const availableLanguages = serverConfig ?. availableLanguages || [ ] ;
61
+ const hasMultipleLanguages = availableLanguages . length > 1 ;
62
+
63
+ // Reorder channels to put the currently selected one first
64
+ const orderedChannels = displayChannel
65
+ ? [ displayChannel , ...channels . filter ( ch => ch . id !== displayChannel . id ) ]
66
+ : channels ;
67
+
68
+ console . log ( displayChannel ) ;
69
+
24
70
return (
25
- < SidebarMenu >
26
- < SidebarMenuItem >
27
- < DropdownMenu >
28
- < DropdownMenuTrigger asChild >
29
- < SidebarMenuButton
30
- size = "lg"
31
- className = "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
32
- >
33
- < div className = "bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg" >
34
- < span className = "truncate font-semibold text-xs" >
35
- { displayChannel ?. defaultCurrencyCode }
36
- </ span >
37
- </ div >
38
- < div className = "grid flex-1 text-left text-sm leading-tight" >
39
- < span className = "truncate font-semibold" >
40
- < ChannelCodeLabel code = { displayChannel ?. code } />
41
- </ span >
42
- < span className = "truncate text-xs" >
43
- Default Language: { displayChannel ?. defaultLanguageCode ?. toUpperCase ( ) }
44
- </ span >
45
- </ div >
46
- < ChevronsUpDown className = "ml-auto" />
47
- </ SidebarMenuButton >
48
- </ DropdownMenuTrigger >
49
- < DropdownMenuContent
50
- className = "w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
51
- align = "start"
52
- side = { isMobile ? 'bottom' : 'right' }
53
- sideOffset = { 4 }
54
- >
55
- < DropdownMenuLabel className = "text-muted-foreground text-xs" >
56
- < Trans > Channels</ Trans >
57
- </ DropdownMenuLabel >
58
- { channels . map ( ( channel , index ) => (
59
- < DropdownMenuItem
60
- key = { channel . code }
61
- onClick = { ( ) => setSelectedChannel ( channel . id ) }
62
- className = "gap-2 p-2"
71
+ < >
72
+ < SidebarMenu >
73
+ < SidebarMenuItem >
74
+ < DropdownMenu >
75
+ < DropdownMenuTrigger asChild >
76
+ < SidebarMenuButton
77
+ size = "lg"
78
+ className = "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
63
79
>
64
- < div className = "flex size-8 items-center justify-center rounded border " >
65
- < span className = "truncate font-semibold text-xs" >
66
- { channel . defaultCurrencyCode }
80
+ < div className = "bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg " >
81
+ < span className = "truncate font-semibold text-xs uppercase " >
82
+ { getChannelInitialsFromCode ( displayChannel ?. code || '' ) }
67
83
</ span >
68
84
</ div >
69
- < ChannelCodeLabel code = { channel . code } />
70
- </ DropdownMenuItem >
71
- ) ) }
72
- < DropdownMenuSeparator />
73
- < DropdownMenuItem className = "gap-2 p-2 cursor-pointer" asChild >
74
- < Link to = { '/channels/new' } >
75
- < div className = "bg-background flex size-6 items-center justify-center rounded-md border" >
76
- < Plus className = "size-4" />
85
+ < div className = "grid flex-1 text-left text-sm leading-tight" >
86
+ < span className = "truncate font-semibold" >
87
+ < ChannelCodeLabel code = { displayChannel ?. code } />
88
+ </ span >
89
+ < span className = "truncate text-xs" >
90
+ { hasMultipleLanguages ? (
91
+ < span className = "cursor-pointer hover:text-foreground" >
92
+ Language: { formatLanguageName ( contentLanguage ) }
93
+ </ span >
94
+ ) : (
95
+ < span > Language: { formatLanguageName ( contentLanguage ) } </ span >
96
+ ) }
97
+ </ span >
77
98
</ div >
78
- < div className = "text-muted-foreground font-medium" > Add channel</ div >
79
- </ Link >
80
- </ DropdownMenuItem >
81
- </ DropdownMenuContent >
82
- </ DropdownMenu >
83
- </ SidebarMenuItem >
84
- </ SidebarMenu >
99
+ < ChevronsUpDown className = "ml-auto" />
100
+ </ SidebarMenuButton >
101
+ </ DropdownMenuTrigger >
102
+ < DropdownMenuContent
103
+ className = "w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
104
+ align = "start"
105
+ side = { isMobile ? 'bottom' : 'right' }
106
+ sideOffset = { 4 }
107
+ >
108
+ < DropdownMenuLabel className = "text-muted-foreground text-xs" >
109
+ < Trans > Channels</ Trans >
110
+ </ DropdownMenuLabel >
111
+ { orderedChannels . map ( ( channel , index ) => (
112
+ < div key = { channel . code } >
113
+ < DropdownMenuItem
114
+ onClick = { ( ) => setSelectedChannel ( channel . id ) }
115
+ className = "gap-2 p-2"
116
+ >
117
+ < div className = "flex size-8 items-center justify-center rounded border" >
118
+ < span className = "truncate font-semibold text-xs uppercase" >
119
+ { getChannelInitialsFromCode ( channel . code ) }
120
+ </ span >
121
+ </ div >
122
+ < ChannelCodeLabel code = { channel . code } />
123
+ { channel . id === displayChannel ?. id && (
124
+ < span className = "ml-auto text-xs text-muted-foreground" >
125
+ Current
126
+ </ span >
127
+ ) }
128
+ </ DropdownMenuItem >
129
+ { /* Show language sub-menu for the current channel */ }
130
+ { channel . id === displayChannel ?. id && (
131
+ < DropdownMenuSub >
132
+ < DropdownMenuSubTrigger className = "gap-2 p-2 pl-4" >
133
+ < Languages className = "w-4 h-4" />
134
+ < div className = "flex gap-1 ml-2" >
135
+ < span className = "text-muted-foreground" > Content: </ span >
136
+ { formatLanguageName ( contentLanguage ) }
137
+ </ div >
138
+ </ DropdownMenuSubTrigger >
139
+ < DropdownMenuSubContent >
140
+ { channel . availableLanguageCodes ?. map ( languageCode => (
141
+ < DropdownMenuItem
142
+ key = { `${ channel . code } -${ languageCode } ` }
143
+ onClick = { ( ) => setContentLanguage ( languageCode ) }
144
+ className = { `gap-2 p-2 ${ contentLanguage === languageCode ? 'bg-accent' : '' } ` }
145
+ >
146
+ < div className = "flex w-6 h-5 items-center justify-center rounded border" >
147
+ < span className = "truncate font-medium text-xs" >
148
+ { languageCode . toUpperCase ( ) }
149
+ </ span >
150
+ </ div >
151
+ < span > { formatLanguageName ( languageCode ) } </ span >
152
+ { contentLanguage === languageCode && (
153
+ < span className = "ml-auto text-xs text-muted-foreground" >
154
+ Active
155
+ </ span >
156
+ ) }
157
+ </ DropdownMenuItem >
158
+ ) ) }
159
+ < DropdownMenuSeparator />
160
+ < DropdownMenuItem
161
+ onClick = { ( ) => setShowManageLanguagesDialog ( true ) }
162
+ className = "gap-2 p-2"
163
+ >
164
+ < Languages className = "w-4 h-4" />
165
+ < span >
166
+ < Trans > Manage Languages</ Trans >
167
+ </ span >
168
+ </ DropdownMenuItem >
169
+ </ DropdownMenuSubContent >
170
+ </ DropdownMenuSub >
171
+ ) }
172
+ { /* Add separator after the current channel group */ }
173
+ { channel . id === displayChannel ?. id &&
174
+ index === 0 &&
175
+ orderedChannels . length > 1 && < DropdownMenuSeparator /> }
176
+ </ div >
177
+ ) ) }
178
+ < DropdownMenuSeparator />
179
+ < DropdownMenuItem className = "gap-2 p-2 cursor-pointer" asChild >
180
+ < Link to = { '/channels/new' } >
181
+ < div className = "bg-background flex size-6 items-center justify-center rounded-md border" >
182
+ < Plus className = "size-4" />
183
+ </ div >
184
+ < div className = "text-muted-foreground font-medium" > Add channel</ div >
185
+ </ Link >
186
+ </ DropdownMenuItem >
187
+ </ DropdownMenuContent >
188
+ </ DropdownMenu >
189
+ </ SidebarMenuItem >
190
+ </ SidebarMenu >
191
+ < ManageLanguagesDialog
192
+ open = { showManageLanguagesDialog }
193
+ onClose = { ( ) => setShowManageLanguagesDialog ( false ) }
194
+ />
195
+ </ >
85
196
) ;
86
197
}
0 commit comments