mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 16:02:11 +00:00
fix(frontend): prevent deploy popover to show if deploy dropdown is open (#5542)
* prevent deploy popover to show if deploy dropdown is open * wip * Revert "wip" This reverts commit85434654af. * Revert "prevent deploy popover to show if deploy dropdown is open" This reverts commitedd9eda156. * add prop to hide popup fro dropdown * feat: button can have tooltip * improve deploy tooltip and dropdown behavior * rename tooltip to tooltipPopover * nit * add deploy button component * use svelte 5 runes * use new deploy button for script builder * add delay to deploy popover when dropdown is open * add delay to deploy popover when dropdown is open # Conflicts: # frontend/src/lib/components/DeployButton.svelte # frontend/src/lib/components/common/button/Button.svelte * Update frontend/src/lib/components/common/button/Button.svelte Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * remove unsused field * nit --------- Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/common'
|
||||
import { Save, CornerDownLeft } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
const {
|
||||
loading = false,
|
||||
loadingSave = false,
|
||||
newFlow = false,
|
||||
dropdownItems = []
|
||||
} = $props<{
|
||||
loading?: boolean
|
||||
loadingSave?: boolean
|
||||
newFlow?: boolean
|
||||
dropdownItems?: Array<{
|
||||
label: string
|
||||
onClick: () => void
|
||||
}>
|
||||
}>()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let msgInput: HTMLInputElement | undefined = $state(undefined)
|
||||
let hideDropdown = $state(false)
|
||||
let deploymentMsg = $state('')
|
||||
let dropdownOpen = $state(false)
|
||||
</script>
|
||||
|
||||
<Button
|
||||
disabled={loading}
|
||||
loading={loadingSave}
|
||||
size="xs"
|
||||
startIcon={{ icon: Save }}
|
||||
on:click={() => dispatch('save')}
|
||||
dropdownItems={!newFlow ? dropdownItems : undefined}
|
||||
tooltipPopover={{
|
||||
placement: 'bottom-end',
|
||||
openDelay: dropdownOpen ? 200 : 0,
|
||||
closeDelay: 0,
|
||||
portal: 'body'
|
||||
}}
|
||||
on:tooltipOpen={async ({ detail }) => {
|
||||
if (detail) {
|
||||
// Use setTimeout to ensure DOM is updated
|
||||
setTimeout(() => {
|
||||
msgInput?.focus()
|
||||
}, 0)
|
||||
hideDropdown = true
|
||||
} else {
|
||||
hideDropdown = false
|
||||
}
|
||||
}}
|
||||
{hideDropdown}
|
||||
on:dropdownOpen={({ detail }) => (dropdownOpen = detail)}
|
||||
>
|
||||
Deploy
|
||||
<svelte:fragment slot="tooltip">
|
||||
<div class="flex flex-row gap-2 w-80 p-4 bg-surface rounded-lg shadow-lg border z-[5001]">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Deployment message"
|
||||
bind:value={deploymentMsg}
|
||||
onkeydown={async (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
dispatch('save', deploymentMsg)
|
||||
}
|
||||
}}
|
||||
bind:this={msgInput}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
on:click={async () => dispatch('save', deploymentMsg)}
|
||||
endIcon={{ icon: CornerDownLeft }}
|
||||
loading={loadingSave}
|
||||
>
|
||||
Deploy
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
@@ -23,6 +23,8 @@
|
||||
export let usePointerDownOutside = false
|
||||
export let closeOnOtherDropdownOpen = true
|
||||
export let fixedHeight = true
|
||||
export let hidePopup = false
|
||||
export let open = false
|
||||
|
||||
const {
|
||||
elements: { menu, item, trigger },
|
||||
@@ -50,7 +52,6 @@
|
||||
}
|
||||
})
|
||||
|
||||
let open = false
|
||||
const sync = createSync(states)
|
||||
$: sync.open(open, (v) => (open = Boolean(v)))
|
||||
|
||||
@@ -104,7 +105,7 @@
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if open}
|
||||
{#if open && !hidePopup}
|
||||
<div use:melt={$menu} data-menu class="z-[6000]">
|
||||
<div
|
||||
class="bg-surface border w-56 origin-top-right rounded-md shadow-md focus:outline-none overflow-y-auto py-1 max-h-[50vh]"
|
||||
|
||||
@@ -48,16 +48,7 @@
|
||||
import FlowPreviewButtons from './flows/header/FlowPreviewButtons.svelte'
|
||||
import type { FlowEditorContext, FlowInput, FlowInputEditorState } from './flows/types'
|
||||
import { cleanInputs, emptyFlowModuleState } from './flows/utils'
|
||||
import {
|
||||
Calendar,
|
||||
Pen,
|
||||
Save,
|
||||
DiffIcon,
|
||||
HistoryIcon,
|
||||
FileJson,
|
||||
type Icon,
|
||||
CornerDownLeft
|
||||
} from 'lucide-svelte'
|
||||
import { Calendar, Pen, Save, DiffIcon, HistoryIcon, FileJson, type Icon } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Awareness from './Awareness.svelte'
|
||||
import { getAllModules } from './flows/flowExplorer'
|
||||
@@ -79,12 +70,12 @@
|
||||
import { ignoredTutorials } from './tutorials/ignoredTutorials'
|
||||
import type DiffDrawer from './DiffDrawer.svelte'
|
||||
import FlowHistory from './flows/FlowHistory.svelte'
|
||||
import CustomPopover from './CustomPopover.svelte'
|
||||
import Summary from './Summary.svelte'
|
||||
import type { FlowBuilderWhitelabelCustomUi } from './custom_ui'
|
||||
import FlowYamlEditor from './flows/header/FlowYamlEditor.svelte'
|
||||
import { type TriggerContext, type ScheduleTrigger } from './triggers'
|
||||
import type { SavedAndModifiedValue } from './common/confirmationModal/unsavedTypes'
|
||||
import DeployButton from './DeployButton.svelte'
|
||||
|
||||
export let initialPath: string = ''
|
||||
export let pathStoreInit: string | undefined = undefined
|
||||
@@ -1256,9 +1247,6 @@
|
||||
]
|
||||
}
|
||||
|
||||
let deploymentMsg = ''
|
||||
let msgInput: HTMLInputElement | undefined = undefined
|
||||
|
||||
let flowPreviewButtons: FlowPreviewButtons
|
||||
</script>
|
||||
|
||||
@@ -1303,7 +1291,8 @@
|
||||
class="justify-between flex flex-row items-center pl-2.5 pr-6 space-x-4 scrollbar-hidden overflow-x-auto max-h-12 h-full relative"
|
||||
>
|
||||
{#if $copilotCurrentStepStore !== undefined}
|
||||
<div transition:fade class="absolute inset-0 bg-gray-500 bg-opacity-75 z-[900] !m-0"></div>
|
||||
<div transition:fade class="absolute inset-0 bg-gray-500 bg-opacity-75 z-[900] !m-0"
|
||||
></div>
|
||||
{/if}
|
||||
<div class="flex w-full max-w-md gap-4 items-center">
|
||||
<Summary
|
||||
@@ -1468,43 +1457,13 @@
|
||||
Draft
|
||||
</Button>
|
||||
|
||||
<CustomPopover appearTimeout={0} focusEl={msgInput}>
|
||||
<Button
|
||||
disabled={loading}
|
||||
loading={loadingSave}
|
||||
size="xs"
|
||||
startIcon={{ icon: Save }}
|
||||
on:click={async () => {
|
||||
await handleSaveFlow()
|
||||
}}
|
||||
dropdownItems={!newFlow ? dropdownItems : undefined}
|
||||
>
|
||||
Deploy
|
||||
</Button>
|
||||
<svelte:fragment slot="overlay">
|
||||
<div class="flex flex-row gap-2 w-80">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Deployment message"
|
||||
bind:value={deploymentMsg}
|
||||
on:keydown={async (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
await handleSaveFlow(deploymentMsg)
|
||||
}
|
||||
}}
|
||||
bind:this={msgInput}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
on:click={async () => await handleSaveFlow(deploymentMsg)}
|
||||
endIcon={{ icon: CornerDownLeft }}
|
||||
loading={loadingSave}
|
||||
>
|
||||
Deploy
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</CustomPopover>
|
||||
<DeployButton
|
||||
on:save={async ({ detail }) => await handleSaveFlow(detail)}
|
||||
{loading}
|
||||
{loadingSave}
|
||||
{newFlow}
|
||||
{dropdownItems}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
Calendar,
|
||||
CheckCircle,
|
||||
Code,
|
||||
CornerDownLeft,
|
||||
Pen,
|
||||
Plus,
|
||||
Rocket,
|
||||
@@ -65,7 +64,6 @@
|
||||
import { defaultScriptLanguages, processLangs } from '$lib/scripts'
|
||||
import DefaultScripts from './DefaultScripts.svelte'
|
||||
import { createEventDispatcher, onMount, setContext } from 'svelte'
|
||||
import CustomPopover from './CustomPopover.svelte'
|
||||
import Summary from './Summary.svelte'
|
||||
import type { ScriptBuilderWhitelabelCustomUi } from './custom_ui'
|
||||
import DeployOverrideConfirmationModal from '$lib/components/common/confirmationModal/DeployOverrideConfirmationModal.svelte'
|
||||
@@ -80,6 +78,8 @@
|
||||
import CaptureTable from './triggers/CaptureTable.svelte'
|
||||
import type { SavedAndModifiedValue } from './common/confirmationModal/unsavedTypes'
|
||||
import type { ScriptBuilderFunctionExports } from './scriptBuilder'
|
||||
import DeployButton from './DeployButton.svelte'
|
||||
|
||||
export let script: NewScript
|
||||
export let fullyLoaded: boolean = true
|
||||
export let initialPath: string = ''
|
||||
@@ -757,9 +757,6 @@
|
||||
|
||||
setContext('disableTooltips', customUi?.disableTooltips === true)
|
||||
|
||||
let deploymentMsg = ''
|
||||
let msgInput: HTMLInputElement | undefined = undefined
|
||||
|
||||
function langToLanguage(lang: SupportedLanguage | 'docker' | 'bunnative'): SupportedLanguage {
|
||||
if (lang == 'docker') {
|
||||
return 'bash'
|
||||
@@ -1575,41 +1572,13 @@
|
||||
<span class="hidden lg:flex"> Draft </span>
|
||||
</Button>
|
||||
|
||||
<CustomPopover appearTimeout={0} focusEl={msgInput}>
|
||||
<Button
|
||||
loading={loadingSave}
|
||||
size="xs"
|
||||
disabled={!fullyLoaded}
|
||||
startIcon={{ icon: Save }}
|
||||
on:click={() => handleEditScript(false)}
|
||||
dropdownItems={computeDropdownItems(initialPath, savedScript, diffDrawer)}
|
||||
>
|
||||
Deploy
|
||||
</Button>
|
||||
<svelte:fragment slot="overlay">
|
||||
<div class="flex flex-row gap-2 min-w-72">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Deployment message"
|
||||
bind:value={deploymentMsg}
|
||||
bind:this={msgInput}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleEditScript(false, deploymentMsg)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
on:click={() => handleEditScript(false, deploymentMsg)}
|
||||
endIcon={{ icon: CornerDownLeft }}
|
||||
loading={loadingSave}
|
||||
>
|
||||
Deploy
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</CustomPopover>
|
||||
<DeployButton
|
||||
loading={!fullyLoaded}
|
||||
{loadingSave}
|
||||
newFlow={false}
|
||||
dropdownItems={computeDropdownItems(initialPath, savedScript, diffDrawer)}
|
||||
on:save={({ detail }) => handleEditScript(false, detail)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
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'
|
||||
|
||||
export let size: ButtonType.Size = 'md'
|
||||
export let spacingSize: ButtonType.Size = size
|
||||
@@ -34,6 +37,14 @@
|
||||
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
|
||||
@@ -43,6 +54,7 @@
|
||||
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
|
||||
@@ -117,6 +129,28 @@
|
||||
}
|
||||
|
||||
$: 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 && dispatch('tooltipOpen', $open)
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -200,6 +234,8 @@
|
||||
{...$$restProps}
|
||||
disabled={disabled || (loading && !clickableWhileLoading)}
|
||||
{style}
|
||||
use:conditionalMelt={trigger}
|
||||
{...$trigger}
|
||||
>
|
||||
{#if loading}
|
||||
<Loader2 class={twMerge('animate-spin', iconOnlyPadding[size])} size={lucideIconSize} />
|
||||
@@ -230,10 +266,22 @@
|
||||
</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">
|
||||
<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(
|
||||
|
||||
Reference in New Issue
Block a user