Files
windmill/frontend/src/lib/components/common/button/Button.svelte
T
ea74f584b1 refactor(frontend): capture refactor UI (#5591)
* migrate postgresEditorInner to svelte 5 syntaxe

* Add postgress

* add postgres capture section

* migrate kafka trigger config to svelte 5

* use snippets in kafka config

* feat: Update KafkaTriggerEditorInner with editMode, hideTarget and other props from WebsocketTriggerEditorInner

* feat: Create KafkaTriggerPanelV2 following WebsocketTriggersPanelV2 model

* feat: Integrate KafkaTriggerPanelV2 into TriggersEditorV2

* feat: add kafka capture panel

* use svelte 5 syntaxe for nats config

* use snippet in nats config

* feat: add editMode for nats trigger config

* fix import

* create NatsTriggerPanelV2.svelte for NATS trigger

* integrate NatsTriggerPanelV2 into TriggersEditorV2.svelte

* Create NatsCapture.svelte and update CaptureWrapperV2.svelte to use it

* migrate MqttTriggerEditorInner to svelte 5

* add mqtt trigger to new trigger panel

* migrate SqsTriggerEditorInner to svelte 5 syntaxe

* feat: add sqs triggers panel

* Add badge to MQTT config section

* remove unnecessary binding

* add gcp to the trigger list

* add prop size to GCP icon

* add gcp trigger utils

* migrate GcpTriggerEditorInner to svelte5 syntaxe

* add gcp trigger panel

* Revert "add gcp trigger utils"

This reverts commit 58f0df9985.

* fix type error

* add gcp to trigger bar

* add capture gcp panel

* add schedule poll panel

* feat: add trigger table horizontal bar

* add http trigger label

* make triggerbar vertical

* nit

* add trigger delete button for drafts

* enable edit from table for all triggers

* fix automatic edit mode for draft trigger

* fix proxy dispatching issue

* fix primary star positionning

* fix apply args to preprocessor

* automatically load bigpayloads

* clean capture wrapper code

* nit

* fix capture config not passed

* use Url component

* remove unused prop captureTable

* use new schedule panel for scheduled poll

* update triggersWrapperV2

* improve webhook capture panel

* remove debug log

* save draft with flow draft

* add path in draft label when available

* remove flowcard title

* use trigger label for trigger badge dropdown

* break from table to sidebar at 1000px

* remove triggers badge allways show sidebar behavior

* fix multiple primary addition

* improve primary schedule deploy logic

* remove debug logs

* gather imports

* add path for draft primary schedule

* handle redirection

* Add confirmation modal to deploy triggers

* remove debug log

* Save draft route trigger on deploy

* Allow draft for deployed triggers

* add component trigger editor toolbar

* improve update trigger utility function

* Add reset button for deployed draft triggers

* allow draft trigger for kafka triggers

* allow draft trigger for websocket triggers

* allow draft trigger for postgres triggers

* Add toggle to trigger toolbar

* allow draft for nats triggers

* allow draft for mqtt triggers

* allow draft sqs triggers

* allow draft for gcp triggers

* revert changes on the schedule editor

* update all triggers to use the same toolbar

* remove debug logs

* remove PrimarySchedule Panel and merge primary schedule logic with all triggers

* load schema from draft when editing a draft schedule

* prevent overflow in trigger table

* fix path saving for draft schedule

* fix trigger badge dropdown display

* get draft status from trigger

* remove unused event

* nit

* Add deploy badge to trigger

* replace checkbox with toggle for the modal

* fix selection of triggers with draft

* fix nit

* fix nit

* add missing config for loading schedule

* save schedule from config

* save http route from cfg

* save websocket trigger from cfg

* save postgres trigger from cfg

* save kafka trigger from cfg

* save sqs trigger from cfg

* save nats trigger from cfg

* save mqtt triggers from cfg

* save gcp trigger from config

* nit

* fix path update when editing a trigger

* remove unused saveCb

* fix trigger node navigation

* remove debug log

* add min size for trigger editor capture panel

* Integrate new triggers Editor to script builder

* remove previous triggersEdior page

* Remove former triggers Editor

* Migrate detail page to new triggers editor UI

* Remove unused dev TriggersBadge

* migrate triggers to V2

* Migrate capture components to V2

* improve triggers badge UI

* fix trigger panel with scheduled poll

* fix auto select all draft on deploy

* simplify permission check

* Add custom label to every trigger editor

* fix draft not deleting when saving triggers and add option to deploy now

* remove unused utility function

* fix triggers loading from draft

* make advanced route section collapsible

* handle subflow triggers display

* center triggers badge

* fix panel redirection when adding a new triggers from node

* Remove unnecessary alerts

* Fix proxy issue in schedule editor inner

* Remove unused schedule pages

* nit

* nit cli panel

* Migrate selectedTrigger store to V2

* handle extra header button for trigger editor panel

* prevent reload on toggle schedule enable

* remove unused utility function

* remove primaryScheduleStore

* handle triggerCount with drafts

* fix editing in Schedule Poll Mode

* Remove triggers default value context store

* fix triggers label max width

* Remove dbg logs

* improve no trigger selected message

* nit

* Do not show enable toggle for http triggers

* Adapt ui to dark mode

* nit

* remove captureOn context value

* improve dark mode

* fix nit

* fix merge issue

* nit

* nit

* fix schedule permission initialisation

* nit

* Disable deploy now if needed

* Improve utilitu function for handling with triggers

* improve trigger badge look

* fix kafka

* nit

* fix schedule display

* fix toggling edit mode

* fix edit/cancel edit

* nit

* prevent capture config update in editor

* migrate webhook panel to svelte 5

* fix postgres trigger creation

* fix bind issue in nats triggers

* fix modal triggers message flow/script

* fix apply args for script

* fix gcp triggers update when creating

* fix gcp capture

* nits

* add loading state for deploying all triggers

* remove default mqtt

* update base_endpoint for gcp

* nit

* fix

* fix

* remove base endpoint

* add catch in case of error while creating a trigger

* Add animation is capture panels

* show preprocessor extra when there is a preprocessor

* Add loading state for captures

* add missing fields for capture

* migrate URL to svelte 5

* show icon by default in confirmation modal

* do not show draft triggers in detail badge list

* migrate to new captures

* revert unwanted change

* migrate to preprocesor v2

* nit

* fix email capture

* nits and fix

* nits

* update repo ref

* Feat: auto save draft instad of save button

* use triggerIndex as selection

* Add modified tag to triggers

* Update trigger modal

* fix trigger node select

* fix schedule page

* migrate TriggersEditor to svelte 5

* remove unused props

* fix schedule editor init

* fix store reactivity issue

* use class instead of store for triggers context

* nit

* nit

* fix schedule init

* Add postgres error message

* nit

* fix deploy new script

* load schema in shedule editor

* improve transitions

* Improve capture indication

* revert unwanted changes

* improve update

* nit

* migrate captures section to svelte 5

* only show trash on hover triggers row

* Add token count

* improve trigger display

* Add relevent naming for triggers

* Use wrapper div insted of width binding in flow card

* add trigger sorting

* nit

* handle reload from URL for draft triggers

* dirty script and flow draft with draft triggers

* handle cloud hosted triggers

* nit

* nit

* fix nats reactivity

* remove debug log

* handle deployed draft triggers in the flow editor

* Add warning for event stream capture

---------

Co-authored-by: dieriba <dieriba.pro@gmail.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-05-21 23:45:19 +02:00

305 lines
8.2 KiB
Svelte

<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { ButtonType } from './model'
import { twMerge } from 'tailwind-merge'
import Dropdown from '$lib/components/DropdownV2.svelte'
import { getModifierKey, type Item } from '$lib/utils'
import { Loader2, ChevronDown } from 'lucide-svelte'
import { createTooltip } from '@melt-ui/svelte'
import type { Placement } from '@floating-ui/core'
import { conditionalMelt } from '$lib/utils'
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
export let size: ButtonType.Size = 'md'
export let spacingSize: ButtonType.Size = size
export let color: ButtonType.Color | string = 'blue'
export let variant: ButtonType.Variant = 'contained'
export let btnClasses: string = ''
export let wrapperClasses: string = ''
export let wrapperStyle: string = ''
export let disabled: boolean = false
export let href: string | undefined = undefined
export let target: '_self' | '_blank' | undefined = undefined
export let iconOnly: boolean = false
export let loadUntilNav: boolean = false
export let clickableWhileLoading = false
export let element: ButtonType.Element | undefined = undefined
export let id: string = ''
export let nonCaptureEvent: boolean = false
export let propagateEvent: boolean = false
export let loading = false
export let title: string | undefined = undefined
export let style: string = ''
export let download: string | undefined = undefined
export let startIcon: ButtonType.Icon | undefined = undefined
export let endIcon: ButtonType.Icon | undefined = undefined
export let shortCut:
| { key?: string; hide?: boolean; Icon?: any; withoutModifier?: boolean }
| undefined = undefined
export let tooltipPopover:
| {
placement?: Placement
openDelay?: number
closeDelay?: number
portal?: string
}
| undefined = undefined
type MenuItem = {
label: string
onClick?: (e?: Event) => void
href?: string
icon?: any
disabled?: boolean
}
export let dropdownItems: MenuItem[] | (() => MenuItem[]) | undefined = undefined
export let hideDropdown: boolean = false
function computeDropdowns(menuItems: MenuItem[] | (() => MenuItem[])): Item[] {
const items = typeof menuItems === 'function' ? menuItems() : menuItems
return items.map((item) => ({
displayName: item.label,
action: item.onClick ? (e) => item.onClick?.(e) : undefined,
icon: item.icon,
disabled: item.disabled ?? false,
href: item.href
}))
}
export function focus() {
element?.focus({})
}
const dispatch = createEventDispatcher()
const dispatchIfMounted = createDispatcherIfMounted(dispatch)
// Order of classes: border, border modifier, bg, bg modifier, text, text modifier, everything else
async function onClick(event: MouseEvent) {
if (!nonCaptureEvent) {
event.preventDefault()
if (!propagateEvent) {
// by default events are not propagated, added this prop so that we can
event.stopPropagation()
}
dispatch('click', event)
}
}
function getColorClass(color, variant) {
if (color in ButtonType.ColorVariants) {
return ButtonType.ColorVariants[color][variant]
} else {
return color
}
}
$: buttonClass = twMerge(
'w-full',
getColorClass(color, variant),
variant === 'border' ? 'border' : '',
ButtonType.FontSizeClasses[size],
ButtonType.SpacingClasses[spacingSize][variant],
'focus-visible:ring-2 font-semibold',
dropdownItems && dropdownItems.length > 0 ? 'rounded-l-md h-full' : 'rounded-md',
'justify-center items-center text-center whitespace-nowrap inline-flex gap-2',
btnClasses,
'active:opacity-80 transition-all',
disabled ? '!bg-surface-disabled !text-tertiary cursor-not-allowed' : '',
loading ? 'cursor-wait' : ''
)
const iconMap = {
xs3: 12,
xs2: 14,
xs: 14,
sm: 16,
md: 16,
lg: 18,
xl: 18
}
const iconOnlyPadding = {
xs3: 'm-[0.5px] qhd:m-[1px]',
xs2: 'm-[1px] qhd:m-[1.125px]',
xs: 'm-[1px] qhd:m-[1.125px]',
sm: 'm-[2px] qhd:m-[2.25px]',
md: 'm-[2px] qhd:m-[2.25px]',
lg: 'm-[5px] qhd:m-[5.625px]',
xl: 'm-[5px] qhd:m-[5.625px]'
}
$: lucideIconSize = (iconMap[size] ?? 12) * 1
const {
elements: { trigger, content },
states: { open },
options: { openDelay }
} = tooltipPopover
? createTooltip({
positioning: {
placement: tooltipPopover?.placement
},
closeDelay: tooltipPopover?.closeDelay,
group: true,
portal: tooltipPopover?.portal
})
: {
elements: { trigger: undefined, content: undefined },
states: { open: undefined },
options: { openDelay: undefined }
}
$: tooltipPopover && openDelay !== undefined && ($openDelay = tooltipPopover?.openDelay) //This option is reactive
$: $open !== undefined && dispatchIfMounted('tooltipOpen', $open)
</script>
<div
class={twMerge(
dropdownItems && dropdownItems.length > 0 && variant === 'contained'
? ButtonType.ColorVariants[color].divider
: '',
wrapperClasses,
'flex flex-row',
disabled ? 'divide-text-disabled' : ''
)}
style={wrapperStyle}
data-interactive
>
{#if href && !disabled}
<a
bind:this={element}
on:pointerdown
on:focus
on:blur
on:mouseenter
on:mouseleave
on:click={() => {
loading = true
dispatch('click', event)
if (!loadUntilNav) {
loading = false
}
}}
{href}
{download}
class={buttonClass}
{id}
{target}
tabindex={disabled ? -1 : 0}
{...$$restProps}
{style}
>
{#if loading}
<Loader2 class={twMerge('animate-spin', iconOnlyPadding[size])} size={lucideIconSize} />
{:else if startIcon?.icon}
<svelte:component
this={startIcon.icon}
class={twMerge(startIcon?.classes, iconOnlyPadding[size])}
size={lucideIconSize}
{...startIcon.props}
/>
{/if}
{#if !iconOnly}
<slot />
{/if}
{#if endIcon?.icon}
<svelte:component
this={endIcon.icon}
class={twMerge(endIcon?.classes, iconOnlyPadding[size])}
size={lucideIconSize}
/>
{/if}
{#if shortCut && !shortCut.hide}
<div class="flex flex-row items-center !text-md opacity-60 gap-0 font-normal">
{#if shortCut.withoutModifier !== true}{getModifierKey()}{/if}{#if shortCut.Icon}<shortCut.Icon
class="w-4 h-4"
size={lucideIconSize}
/>{:else}{shortCut.key}{/if}
</div>
{/if}
</a>
{:else}
<button
bind:this={element}
on:pointerdown
on:click={onClick}
on:focus
on:blur
on:mouseenter
on:mouseleave
class={buttonClass}
{id}
tabindex={disabled ? -1 : 0}
{title}
{...$$restProps}
disabled={disabled || (loading && !clickableWhileLoading)}
{style}
use:conditionalMelt={trigger}
{...$trigger}
>
{#if loading}
<Loader2 class={twMerge('animate-spin', iconOnlyPadding[size])} size={lucideIconSize} />
{:else if startIcon?.icon}
<svelte:component
this={startIcon.icon}
class={twMerge(startIcon?.classes, iconOnlyPadding[size])}
size={lucideIconSize}
{...startIcon.props}
/>
{/if}
{#if !iconOnly}
<slot />
{/if}
{#if endIcon?.icon}
<svelte:component
this={endIcon.icon}
class={twMerge(endIcon?.classes, iconOnlyPadding[size])}
size={lucideIconSize}
/>
{/if}
{#if shortCut && !shortCut.hide}
{@const Icon = shortCut.Icon}
<div class="flex flex-row items-center !text-md opacity-60 gap-0 font-normal">
{#if shortCut.withoutModifier !== true}{getModifierKey()}{/if}{#if shortCut.Icon}<Icon
size={lucideIconSize}
/>{:else}{shortCut.key}{/if}
</div>
{/if}
</button>
{#if tooltipPopover && $open}
<div use:conditionalMelt={content} {...$content} class="z-[20000]">
<slot name="tooltip" />
</div>
{/if}
{/if}
{#if dropdownItems && dropdownItems.length > 0}
<Dropdown
items={computeDropdowns(dropdownItems)}
class="h-auto w-fit"
hidePopup={hideDropdown}
usePointerDownOutside
on:open={() => dispatch('dropdownOpen', true)}
on:close={() => dispatch('dropdownOpen', false)}
>
<svelte:fragment slot="buttonReplacement">
<div
class={twMerge(
buttonClass,
'rounded-md m-0 p-0 center-center h-full',
variant === 'border' ? 'border-0 border-r border-y ' : 'border-0',
'rounded-r-md !rounded-l-none',
size === 'xs2' ? '!w-8' : '!w-10'
)}
>
<ChevronDown size={lucideIconSize} />
</div>
</svelte:fragment>
</Dropdown>
{/if}
</div>