mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 00:03:00 +00:00
* feat: rework git sync settings into a flat repo list and setup modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: add the connect step for gitlab and github in git sync setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: open the github app install page from the git sync setup next step Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: let git sync setup set the repository folder Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: add the configure sync step to git sync setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: even out the git sync filter settings columns Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: pin the filter input to the bottom of the filter list Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: frame the git sync path filters in a box with an empty hint Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: use a multiselect for the git sync path filters Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: grey out the skipped connect step in git sync setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: move the check job line to the bottom of the setup modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hide the check job line once the repository check resolved Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: center the repository check loader in the setup modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: stop the setup modal shifting when the check job id arrives Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: move the existing resource hint to the setup modal footer Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: always show the existing resource option when one exists Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: drop the card and collapse from the git sync filter settings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: use a success alert for the pull from git notice Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: make initializing the repository a step of the git sync setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: hide the push job lines once they succeed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: reword the push step intro Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: copy button on the cli snippet and a taller changes list Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: match the push step loading to the check step Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: stop the push step shifting when a job id arrives Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: let the changes list fill the push step Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: end the git sync setup on a done step instead of a modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: delete a git sync repository from a row menu with a confirmation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: lift the git sync done message off the notices Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: restore github app installation transfer between instances Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address regressions from the git sync setup rework Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the overlay z-index floor in step with the ai chat Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hold the push state in the setup dialog, not in the step it destroys Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: block every dismissal while setup work is in flight and keep sync reachable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: regenerate the resource path when the repository changes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: configure the resource the connect step actually created Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a modal open when a drawer above it takes the click Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: snapshot the topmost surface for keyboard clicks too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: size the add repository buttons and explain git promotion Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * revert: drop the promotion-first action from the empty state Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: shrink the test connection result line Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: offer the fork sync and fork pull request toggles during setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: group promotion repositories under their own label Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: offer the deploy branch pull request toggle during setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: wait for the repository credential before saving the setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: ignore a superseded credential load and drop the bindable default Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: key setup readiness to the repository it was loaded for Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: tie setup readiness to the attempt that asked for it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: link to where the access token is created Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: say plainly how each provider connects Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: sync resources, variables, schedules and triggers by default Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: use the accent colour for the token help link Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: move the CI/CD deploy hint next to the pull toggle Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep setup gated when the connection could not be read Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: only explain pull-from-git where the workspace can turn it on Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: offer a save retry once the repository is initialized Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: say when pulling fell back to polling on the last setup step Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: clear the initialized flag when a new draft starts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
214 lines
5.7 KiB
Svelte
214 lines
5.7 KiB
Svelte
<script lang="ts" generics="Item extends { label?: string; value: any; }">
|
|
import { clickOutside, reorder } from '$lib/utils'
|
|
import { untrack } from 'svelte'
|
|
import { processItems, type ProcessedItem } from './utils.svelte'
|
|
import SelectDropdown from './SelectDropdown.svelte'
|
|
import CloseButton from '../common/CloseButton.svelte'
|
|
import DraggableTags from './DraggableTags.svelte'
|
|
import { Search } from 'lucide-svelte'
|
|
import { twMerge } from 'tailwind-merge'
|
|
import TextInput, {
|
|
inputBaseClass,
|
|
inputBorderClass,
|
|
inputSizeClasses
|
|
} from '../text_input/TextInput.svelte'
|
|
|
|
type Value = Item['value']
|
|
|
|
let {
|
|
items,
|
|
placeholder = 'Select items',
|
|
searchPlaceholder = 'Search...',
|
|
value = $bindable(),
|
|
class: className = '',
|
|
style,
|
|
listAutoWidth = true,
|
|
disabled = false,
|
|
disablePortal = false,
|
|
createText,
|
|
reorderable = true,
|
|
noItemsMsg,
|
|
selectedUlClass = '',
|
|
placeholderClass = '',
|
|
allowClear = true,
|
|
hideMainClearBtn = false,
|
|
size = 'md',
|
|
id,
|
|
error = false,
|
|
onOpen,
|
|
groupBy,
|
|
sortBy,
|
|
onCreateItem
|
|
}: {
|
|
items?: Item[]
|
|
value: Value[]
|
|
placeholder?: string
|
|
/** Placeholder of the dropdown's search field. Worth setting where the list is there to
|
|
* create entries rather than pick from, so the field says what to type. */
|
|
searchPlaceholder?: string
|
|
class?: string
|
|
style?: string
|
|
filterText?: string
|
|
disabled?: boolean
|
|
listAutoWidth?: boolean
|
|
containerStyle?: string
|
|
inputClass?: string
|
|
disablePortal?: boolean
|
|
createText?: string
|
|
reorderable?: boolean
|
|
noItemsMsg?: string
|
|
selectedUlClass?: string
|
|
placeholderClass?: string
|
|
allowClear?: boolean
|
|
hideMainClearBtn?: boolean
|
|
size?: keyof typeof inputSizeClasses
|
|
id?: string
|
|
error?: boolean
|
|
groupBy?: (item: Item) => string
|
|
sortBy?: (a: Item, b: Item) => number
|
|
onOpen?: () => void
|
|
onClear?: () => void
|
|
onCreateItem?: (value: string) => void
|
|
} = $props()
|
|
|
|
let filterText = $state<string>('')
|
|
let open = $state<boolean>(false)
|
|
let wrapperEl: HTMLDivElement | undefined = $state()
|
|
let searchInputEl: TextInput | undefined = $state()
|
|
|
|
let currentValue = $derived(value ?? [])
|
|
|
|
$effect(() => searchInputEl?.focus())
|
|
|
|
let processedItems: ProcessedItem<Value>[] = $derived.by(() => {
|
|
let args = { items, createText, filterText, groupBy, onCreateItem, sortBy }
|
|
return untrack(() => processItems(args))
|
|
})
|
|
|
|
$effect(() => {
|
|
if (!open) filterText = ''
|
|
})
|
|
$effect(() => {
|
|
open && untrack(() => onOpen?.())
|
|
})
|
|
|
|
let valueEntry = $derived(
|
|
currentValue.map(
|
|
(v) => processedItems.find((item) => item.value === v) ?? { value: v, label: v }
|
|
)
|
|
)
|
|
|
|
function onAddValue(item: ProcessedItem<Value>) {
|
|
if (item.__is_create && onCreateItem) {
|
|
const before = currentValue.length
|
|
onCreateItem(item.value)
|
|
// Cleared only once the entry exists: it is then in the list, and leaving it in the
|
|
// field would filter the list down to it. A consumer that refused the value (a
|
|
// duplicate, a name it rejects) keeps what was typed, to be corrected.
|
|
if ((value?.length ?? 0) !== before) filterText = ''
|
|
} else {
|
|
value = [...currentValue, item.value]
|
|
}
|
|
}
|
|
function onRemoveValue(item: ProcessedItem<Value>) {
|
|
value = currentValue.filter((v) => v !== item.value)
|
|
}
|
|
|
|
function clearValue() {
|
|
filterText = ''
|
|
value = []
|
|
}
|
|
|
|
export function getFilteredInputText() {
|
|
return filterText
|
|
}
|
|
</script>
|
|
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<div
|
|
bind:this={wrapperEl}
|
|
class={twMerge(
|
|
'flex items-center flex-wrap relative',
|
|
inputBaseClass,
|
|
inputSizeClasses[size],
|
|
inputBorderClass({ forceFocus: open && !disabled, error }),
|
|
disabled ? 'pointer-events-none' : '',
|
|
open && !disabled ? 'open' : '',
|
|
disabled ? 'disabled' : '',
|
|
className
|
|
)}
|
|
{style}
|
|
onpointerup={() => (open = true)}
|
|
use:clickOutside={{ onClickOutside: () => (open = false) }}
|
|
{id}
|
|
>
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
|
|
{#if currentValue.length === 0}
|
|
<span class={twMerge('text-xs h-full flex items-center flex-1 text-hint', placeholderClass)}>
|
|
{placeholder}
|
|
</span>
|
|
{:else}
|
|
<ul
|
|
class={twMerge(
|
|
'overflow-clip overflow-x-hidden h-full cursor-pointer items-center flex flex-wrap gap-1 py-0.5 flex-1 text-primary',
|
|
selectedUlClass
|
|
)}
|
|
role="list"
|
|
>
|
|
<DraggableTags
|
|
items={valueEntry}
|
|
{allowClear}
|
|
onRemove={onRemoveValue}
|
|
onReorder={reorderable
|
|
? (oldIdx, newIdx) => (value = reorder(currentValue, oldIdx, newIdx))
|
|
: undefined}
|
|
/>
|
|
</ul>
|
|
{/if}
|
|
{#if allowClear && !hideMainClearBtn && !!value?.length}
|
|
<CloseButton
|
|
noBg
|
|
class="ml-2 remove-all bg-transparent text-hint"
|
|
small
|
|
on:close={(e) => (clearValue(), e.stopPropagation())}
|
|
/>
|
|
{/if}
|
|
<SelectDropdown
|
|
{disablePortal}
|
|
onSelectValue={onAddValue}
|
|
{open}
|
|
processedItems={processedItems.filter((item) => !currentValue.includes(item.value))}
|
|
value={undefined}
|
|
{disabled}
|
|
{filterText}
|
|
getInputRect={wrapperEl && (() => wrapperEl!.getBoundingClientRect())}
|
|
{listAutoWidth}
|
|
{noItemsMsg}
|
|
class={twMerge(
|
|
'multiselect dropdown',
|
|
open && !disabled ? 'open' : '',
|
|
disabled ? 'disabled' : ''
|
|
)}
|
|
ulClass="options"
|
|
>
|
|
{#snippet header()}
|
|
{#if processedItems.length - currentValue.length > 0 || onCreateItem}
|
|
<div class="mx-2 mb-1 mt-2 flex items-center relative">
|
|
<TextInput
|
|
bind:this={searchInputEl}
|
|
bind:value={filterText}
|
|
inputProps={{
|
|
onblur: (e) => (e.preventDefault(), searchInputEl?.focus()),
|
|
placeholder: searchPlaceholder
|
|
}}
|
|
class="!pr-7 !bg-surface"
|
|
/>
|
|
<Search size={16} class="absolute right-2 text-primary" />
|
|
</div>
|
|
{/if}
|
|
{/snippet}
|
|
</SelectDropdown>
|
|
</div>
|