Files
windmill/frontend/src/lib/components/ChannelSelector.svelte
Alexander Petric 408911dbf6 camelcase (#7091)
2025-11-07 18:51:33 +00:00

150 lines
3.8 KiB
Svelte

<script lang="ts">
import Select from './select/Select.svelte'
import { WorkspaceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { debounce } from '$lib/utils'
interface ChannelItem {
channel_id?: string
channel_name?: string
}
interface Props {
disabled?: boolean
placeholder?: string
selectedChannel?: ChannelItem | undefined
containerClass?: string
minWidth?: string
channels?: ChannelItem[]
teamId?: string
onError?: (error: Error) => void
onSelectedChannelChange?: (channel: ChannelItem | undefined) => void
}
let {
disabled = false,
placeholder = 'Select channel',
selectedChannel = $bindable(undefined),
containerClass = 'w-64',
minWidth = '160px',
channels = undefined,
teamId,
onError,
onSelectedChannelChange
}: Props = $props()
let isFetching = $state(false)
let searchResults = $state<ChannelItem[]>([])
let selectedChannelId = $state<string | undefined>(selectedChannel?.channel_id)
const searchMode = !channels && !!teamId
let displayChannels = $derived.by(() => {
const baseChannels = channels || searchResults
if (selectedChannel && !baseChannels.find(c => c.channel_id === selectedChannel?.channel_id)) {
return [selectedChannel, ...baseChannels]
}
return baseChannels
})
$effect(() => {
const newChannel = selectedChannelId
? displayChannels.find(c => c.channel_id === selectedChannelId)
: undefined
if (newChannel?.channel_id !== selectedChannel?.channel_id) {
selectedChannel = newChannel
}
})
$effect(() => {
if (selectedChannel?.channel_id !== selectedChannelId) {
selectedChannelId = selectedChannel?.channel_id
}
})
let previousChannelId = $state<string | undefined>(undefined)
$effect(() => {
if (selectedChannel?.channel_id !== previousChannelId) {
previousChannelId = selectedChannel?.channel_id
onSelectedChannelChange?.(selectedChannel)
}
})
let searchFilterText = $state('')
const debouncedSearch = debounce(async (query: string) => {
await searchChannels(query)
}, 500)
$effect(() => {
if (searchMode) {
if (searchFilterText.length >= 1) {
debouncedSearch.debounced(searchFilterText)
} else if (searchFilterText.length === 0) {
searchResults = []
}
}
})
async function searchChannels(query: string) {
if (!query || !teamId) return
isFetching = true
try {
const response = await WorkspaceService.listAvailableTeamsChannels({
workspace: $workspaceStore!,
teamId: teamId,
search: query
})
searchResults = response || []
isFetching = false
return searchResults
} catch (error) {
isFetching = false
onError?.(error)
console.error('Error searching channels:', error)
searchResults = []
return []
}
}
</script>
<div class={containerClass}>
<div class="flex items-center gap-2">
<div class="flex-grow" style="min-width: {minWidth};">
{#if searchMode}
<Select
containerStyle={'min-width: ' + minWidth}
items={displayChannels.filter(channel => channel.channel_id && channel.channel_name).map((channel) => ({
label: channel.channel_name ?? 'Unknown Channel',
value: channel.channel_id ?? ''
}))}
placeholder={isFetching ? "Searching..." : (teamId ? "Search channels..." : "Select a team first")}
clearable
disabled={disabled || isFetching || !teamId}
bind:filterText={searchFilterText}
bind:value={selectedChannelId}
/>
{:else}
<Select
containerStyle={'min-width: ' + minWidth}
items={displayChannels.filter(channel => channel.channel_id && channel.channel_name).map((channel) => ({
label: channel.channel_name ?? 'Unknown Channel',
value: channel.channel_id ?? ''
}))}
{placeholder}
clearable
disabled={disabled || displayChannels.length === 0}
bind:value={selectedChannelId}
/>
{/if}
</div>
</div>
</div>