glm/fix-prop-picker-position (#6991)

* fix button fill container

* fix popover overflow

* Update script picker to new guidelines

* fix expand sublow button

* fix scroll

* fix popover dark mode

* fix refresh button

* nit

* fix popover oferflow

* nit
This commit is contained in:
Guilhem
2025-10-29 22:03:12 +01:00
committed by GitHub
parent 6c5ae0626f
commit 27c959fe54
35 changed files with 283 additions and 285 deletions
+1 -1
View File
@@ -29,7 +29,7 @@
"text-emphasis-inverse": "#f3f4f6",
"reserved-ai": "#a02cde",
"component-virtual-node": "#dce0f1",
"text-accent": "#5074f6",
"text-accent": "#2652df",
"border-selected": "#a0affa",
"surface-sunken": "#e8e8ef",
"text-tertiary": "#505c70",
@@ -58,7 +58,7 @@
>
Deploy
{#snippet tooltip()}
<div class="flex flex-row gap-2 w-80 p-4 bg-surface rounded-lg shadow-lg border z-[5001]">
<div class="flex flex-row gap-2 w-80 p-4 bg-surface rounded-lg shadow-lg dark:border z-[5001]">
<input
type="text"
placeholder="Deployment message"
@@ -169,7 +169,7 @@
{@render menu?.()}
{:else}
<div
class="bg-surface-tertiary dark:border-gray-700 w-56 origin-top-right rounded-lg shadow-lg focus:outline-none overflow-y-auto py-1 max-h-[50vh]"
class="bg-surface-tertiary dark:border w-56 origin-top-right rounded-lg shadow-lg focus:outline-none overflow-y-auto py-1 max-h-[50vh]"
style={customWidth ? `width: ${customWidth}px` : ''}
>
<DropdownV2Inner {aiId} items={computeItems} meltItem={item} />
@@ -179,10 +179,8 @@
<Popover
documentationLink="https://www.windmill.dev/docs/integrations/git_repository#github-app"
bind:this={githubAppPopover}
floatingConfig={{
placement: 'bottom'
}}
disabled={!$enterpriseLicense || githubState.loadingGithubInstallations}
contentClasses="overflow-auto"
>
{#snippet trigger()}
<Button
@@ -111,7 +111,7 @@
<FlowInputEditor title="History">
<svelete:fragment slot="action">
<div class="center-center">
<RefreshButton {loading} on:click={() => historicInputs?.refresh()} />
<RefreshButton {loading} onClick={() => historicInputs?.refresh()} />
</div>
</svelete:fragment>
<HistoricInputs
@@ -13,9 +13,9 @@
<div class="max-w-min">
<ToggleButtonGroup id="flow-editor-flow-providers" bind:selected>
{#snippet children({ item })}
<ToggleButton value="all" label="All" {item} />
<ToggleButton value="hub" icon={WindmillIcon2} label="Hub" {item} />
<ToggleButton value="workspace" icon={Building} label="Workspace" {item} />
<ToggleButton size="sm" value="all" label="All" {item} />
<ToggleButton size="sm" value="hub" icon={WindmillIcon2} label="Hub" {item} />
<ToggleButton size="sm" value="workspace" icon={Building} label="Workspace" {item} />
{/snippet}
</ToggleButtonGroup>
</div>
@@ -8,7 +8,7 @@
</script>
<div
class="shadow-lg max-w-sm break-words py-2 px-3 rounded-md text-xs font-normal text-primary bg-surface-secondary whitespace-normal text-left"
class="shadow-lg max-w-sm break-words py-2 px-3 rounded-md text-xs font-normal text-primary bg-surface-secondary whitespace-normal text-left dark:border"
>
{#if markdownTooltip}
<div class="prose-sm">
@@ -3,15 +3,15 @@
import { RefreshCw } from 'lucide-svelte'
import Popover from '$lib/components/Popover.svelte'
import { twMerge } from 'tailwind-merge'
interface Props {
loading: boolean
size?: ButtonType.Size
size?: ButtonType.UnifiedSize
light?: boolean
onClick?: () => void
}
let { loading, size = 'xs2', light = false }: Props = $props()
let { loading, size = 'md', onClick }: Props = $props()
let buttonHover = $state(false)
</script>
@@ -21,15 +21,11 @@
on:mouseenter={() => (buttonHover = true)}
on:mouseleave={() => (buttonHover = false)}
color="light"
{size}
variant="border"
on:click
>
<RefreshCw
class={twMerge(loading ? 'animate-spin ' : '', light ? 'text-secondary' : '')}
size="14"
/>
</Button>
unifiedSize={size}
variant="subtle"
{onClick}
startIcon={{ icon: RefreshCw, props: { class: loading ? 'animate-spin' : '' } }}
></Button>
{#snippet text()}
{#if loading}
{#if buttonHover}
@@ -230,19 +230,19 @@ export namespace ButtonType {
}
export const UnifiedMinHeightClasses: Record<ButtonType.UnifiedSize, string> = {
sm: 'min-h-6',
sm: 'min-h-7',
md: 'min-h-8',
lg: 'min-h-10'
}
export const UnifiedHeightClasses: Record<ButtonType.UnifiedSize, string> = {
sm: 'h-6',
sm: 'h-7',
md: 'h-8',
lg: 'h-10'
}
export const UnifiedIconSizes: Record<ButtonType.UnifiedSize, number> = {
sm: 14,
sm: 13,
md: 14,
lg: 18
}
@@ -40,7 +40,7 @@
<Portal name="popup-v2" {target}>
{#if open}
<div
class="border rounded-lg shadow-lg bg-surface z5000"
class="dark:border rounded-lg shadow-lg bg-surface z5000"
style="position:absolute"
use:floatingContent
transition:fly={{ duration: 100, y: -16 }}
@@ -78,14 +78,15 @@
},
placeholder: `Search ${trigger ? 'triggers' : 'scripts'} ${disableAi ? '' : 'or AI gen'}`
}}
size="sm"
/>
</div>
<div class="absolute inset-y-0 right-3 flex items-center pointer-events-none">
{#if loading}
<Loader2 size={16} class="animate-spin text-gray-400" />
<Loader2 size={12} class="animate-spin text-gray-400" />
{/if}
{#if funcDesc?.length === 0 && !loading && !disableAi}
<Wand2 size={14} class="fill-current opacity-70 text-ai" />
<Wand2 size={12} class="fill-current opacity-70 text-ai" />
{/if}
</div>
</div>
@@ -14,12 +14,10 @@
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
import { type Script, type ScriptLang, type HubScriptKind } from '$lib/gen'
import ListFiltersQuick from '$lib/components/home/ListFiltersQuick.svelte'
import { Folder, User } from 'lucide-svelte'
import { Folder, User, X } from 'lucide-svelte'
import type { FlowEditorContext } from '../../flows/types'
import { twMerge } from 'tailwind-merge'
import { fade } from 'svelte/transition'
import { flip } from 'svelte/animate'
import Scrollable from '$lib/components/Scrollable.svelte'
import { Button } from '$lib/components/common'
import { SettingsIcon } from 'lucide-svelte'
import DefaultScriptsInner from '$lib/components/DefaultScriptsInner.svelte'
@@ -37,7 +35,6 @@
funcDesc: string
owners?: string[]
loading?: boolean
small?: boolean
kind: 'trigger' | 'script' | 'preprocessor' | 'failure' | 'approval'
selectedKind?: 'script' | 'flow' | 'approval' | 'trigger' | 'preprocessor' | 'failure'
displayPath?: boolean
@@ -52,7 +49,6 @@
funcDesc,
owners = $bindable([]),
loading = $bindable(false),
small = false,
kind,
selectedKind = kind,
displayPath = false,
@@ -172,10 +168,7 @@
]
let topLevelNodes: [string, string][] = $state([])
function computeToplevelNodeChoices(
funcDesc: string,
preFilter: 'all' | 'workspace' | 'hub'
) {
function computeToplevelNodeChoices(funcDesc: string, preFilter: 'all' | 'workspace' | 'hub') {
if (funcDesc.length > 0 && preFilter == 'all' && kind == 'script') {
topLevelNodes = allToplevelNodes.filter((node) =>
node[0].toLowerCase().startsWith(funcDesc.toLowerCase())
@@ -194,7 +187,7 @@
selectedByKeyboard = 0
}
let scrollable: Scrollable | undefined = $state()
let scrollable: HTMLElement | undefined = $state()
function onKeyDown(e: KeyboardEvent) {
let length =
topLevelNodes?.length +
@@ -204,11 +197,11 @@
hubCompletions.length
if (e.key === 'ArrowDown') {
selectedByKeyboard = (selectedByKeyboard + 1) % length
scrollable?.scrollIntoView(selectedByKeyboard * 32)
scrollable?.scrollTo({ top: selectedByKeyboard * 32, behavior: 'smooth' })
e.preventDefault()
} else if (e.key === 'ArrowUp') {
selectedByKeyboard = (selectedByKeyboard - 1 + length) % length
scrollable?.scrollIntoView(selectedByKeyboard * 32)
scrollable?.scrollTo({ top: selectedByKeyboard * 32, behavior: 'smooth' })
e.preventDefault()
}
}
@@ -249,9 +242,9 @@
</script>
<svelte:window onkeydown={onKeyDown} />
<div class="flex flex-row grow min-w-0 divide-x relative {!small ? 'shadow-inset' : ''}">
<div class="flex flex-row grow min-w-0 divide-x relative bg-surface-tertiary rounded-md">
{#if selectedKind != 'preprocessor'}
<Scrollable shiftedShadow scrollableClass="w-32 grow-0 shrink-0 ">
<div class="h-full overflow-auto p-2 w-36 shrink-0 gap-1 flex flex-col">
{#if ['script', 'trigger', 'approval', 'preprocessor', 'failure'].includes(selectedKind)}
{#if (preFilter === 'all' && owners.length > 0) || preFilter === 'workspace'}
{#if preFilter !== 'workspace'}
@@ -265,22 +258,21 @@
animate:flip={{ duration: 100 }}
class="w-full px-0.5"
>
<button
class={twMerge(
'w-full text-left text-2xs text-primary font-normal py-2 px-3 hover:bg-surface-hover transition-all whitespace-nowrap flex flex-row gap-2 items-center rounded-md',
owner === selected?.name ? 'bg-surface-hover' : ''
)}
onclick={() => {
<Button
selected={owner === selected?.name}
onClick={() => {
selected = selected?.name == owner ? undefined : { kind: 'owner', name: owner }
}}
variant="subtle"
unifiedSize="sm"
btnClasses="justify-start"
startIcon={{
icon: owner.startsWith('f/') ? Folder : User,
props: { width: 14, height: 14 }
}}
>
{#if owner.startsWith('f/')}
<Folder class="mr-0.5" size={14} />
{:else}
<User class="mr-0.5" size={14} />
{/if}
{owner.slice(2)}
</button>
</Button>
</div>
{/each}
<div class="pb-1.5"></div>
@@ -309,29 +301,31 @@
{#if owners.length > 0}
{#each owners as owner (owner)}
<div in:fade={{ duration: 50 }} animate:flip={{ duration: 100 }}>
<button
class={twMerge(
'w-full text-left text-2xs text-primary font-normal py-2 px-3 hover:bg-surface-hover transition-all whitespace-nowrap flex flex-row gap-2 items-center rounded-md',
owner === selected?.name ? 'bg-surface-hover' : ''
)}
onclick={() => {
<Button
selected={owner === selected?.name}
variant="subtle"
unifiedSize="sm"
btnClasses="justify-start"
startIcon={{
icon: owner.startsWith('f/') ? Folder : User
}}
onClick={() => {
selected = selected?.name == owner ? undefined : { kind: 'owner', name: owner }
}}
>
{#if owner.startsWith('f/')}
<Folder class="mr-0.5" size={14} />
{:else}
<User class="mr-0.5" size={14} />
{/if}
{owner.slice(2)}
</button>
</Button>
</div>
{/each}
{/if}
{/if}
</Scrollable>
</div>
{/if}
<Scrollable id="flow-editor-flow-atoms" bind:this={scrollable} scrollableClass="grow min-w-0">
<div
bind:this={scrollable}
id="flow-editor-flow-atoms"
class="h-full overflow-auto grow min-w-0 p-2 gap-1 flex flex-col"
>
{#if kind == 'script'}
{#each topLevelNodes as [label, kind], i (label)}
<FlowToplevelNode
@@ -352,19 +346,18 @@
{#if $userStore?.is_admin || $userStore?.is_super_admin}
{#if !openScriptSettings}
<Button
on:click={() => (openScriptSettings = true)}
onClick={() => (openScriptSettings = true)}
startIcon={{ icon: SettingsIcon }}
size="xs2"
btnClasses="!text-primary"
unifiedSize="sm"
variant="subtle"
title="Edit global default scripts"
/>
{:else}
<Button
on:click={() => (openScriptSettings = false)}
startIcon={{ icon: SettingsIcon }}
onClick={() => (openScriptSettings = false)}
startIcon={{ icon: X }}
variant="accent"
size="xs2"
unifiedSize="sm"
>
Close
</Button>
@@ -472,6 +465,7 @@
{refreshCount}
/>
{/await}
<div class="pb-1"></div>
{/if}
{#if selectedKind != 'preprocessor' && selectedKind != 'flow'}
{#if (!selected || selected?.kind === 'integrations') && (preFilter === 'hub' || preFilter === 'all')}
@@ -504,18 +498,5 @@
{/await}
{/if}
{/if}
</Scrollable>
</div>
</div>
<style>
.shadow-inset::before {
box-shadow: inset 25px 0px 12px -30px rgba(94, 129, 172, 0.5);
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
content: '';
pointer-events: none;
}
</style>
@@ -106,7 +106,7 @@
<!-- Index 0 is used by the tutorial to identify the first "Add step" -->
<InsertModulePopover
{disableAi}
placement={'bottom-center'}
placement={'bottom'}
on:new={(e) => {
insertFailureModule(e.detail.inlineScript)
}}
@@ -115,19 +115,17 @@
}}
kind="failure"
>
{#snippet trigger({ toggleOpen })}
{#snippet trigger()}
<Button
unifiedSize="sm"
wrapperClasses="min-w-36"
title={`Add failure module`}
variant="default"
id={`flow-editor-add-step-error-handler-button`}
onClick={() => toggleOpen()}
nonCaptureEvent
startIcon={{ icon: Bug }}
>
<div class="flex items-center gap-1">
<Bug size={14} />
<span class="text-xs w-20">Error Handler</span>
</div>
Error Handler
</Button>
{/snippet}
</InsertModulePopover>
@@ -17,7 +17,8 @@
Play,
Loader2,
TriangleAlert,
Timer
Timer,
Maximize2
} from 'lucide-svelte'
import { createEventDispatcher, getContext } from 'svelte'
import { fade } from 'svelte/transition'
@@ -83,6 +84,7 @@
flowJob?: Job | undefined
isOwner?: boolean
enableTestRun?: boolean
maximizeSubflow?: () => void
}
let {
@@ -115,7 +117,8 @@
onUpdateMock,
onEditInput,
flowJob,
enableTestRun = false
enableTestRun = false,
maximizeSubflow = undefined
}: Props = $props()
let colorClasses = $derived(getNodeColorClasses(nodeState, selected))
@@ -472,7 +475,7 @@
{#if id !== 'preprocessor'}
<button
class={twMerge(
'trash center-center p-1 text-secondary shadow-sm bg-surface duration-0 hover:bg-blue-400 hover:text-white',
'trash center-center p-1 text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-accent-hover hover:text-white',
hover ? 'block' : '!hidden',
'shadow-md rounded-md',
'group-hover:block'
@@ -483,6 +486,25 @@
<Move size={12} />
</button>
{/if}
{#if maximizeSubflow !== undefined && (hover || selected)}
<button
title="Expand subflow"
class={twMerge(
'center-center text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-accent-hover hover:text-white p-1',
selected || hover ? 'block' : '!hidden',
'group-hover:block',
'shadow-md rounded-md'
)}
onclick={(e) => {
e.stopPropagation()
e.preventDefault()
maximizeSubflow?.()
}}
>
<Maximize2 size={12} />
</button>
{/if}
<button
class={twMerge(
'trash center-center text-secondary shadow-sm bg-surface duration-0 hover:bg-red-400 hover:text-white p-1',
@@ -31,7 +31,7 @@
const { selectedId, flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
</script>
<div class="flex flex-row gap-2 h-8 p-1 rounded-md bg-surface">
<div class="flex flex-row gap-2 p-1 rounded-md bg-surface">
{#if !disableSettings}
<Button
unifiedSize="sm"
@@ -62,12 +62,11 @@
startIcon={{ icon: DollarSign }}
selected={$selectedId === 'constants'}
variant="default"
title="Static Inputs"
iconOnly
onClick={() => ($selectedId = 'constants')}
/>
{#snippet text()}
Static Inputs
Static inputs
{/snippet}
</Popover>
{/if}
@@ -3,16 +3,15 @@
import { Plus } from 'lucide-svelte'
type Props = {
onPress: () => void
Icon?: IconType
title?: string
id?: string
}
let { onPress, Icon = Plus, title, id }: Props = $props()
let { Icon = Plus, title, id }: Props = $props()
</script>
<button class="center-center p-2 group" onpointerdown={onPress}>
<button class="center-center p-2 group">
<div
{title}
{id}
@@ -45,11 +45,9 @@
<div
id="flow-editor-insert-module"
class="flex flex-col h-[400px] {small
? 'w-[450px]'
: 'w-[650px]'} pt-1 pr-1 pl-1 gap-1.5 resize overflow-auto {small
class="flex flex-col h-full {small ? 'w-[450px]' : 'w-[650px]'} gap-2 {small
? 'min-w-[450px]'
: 'min-w-[650px]'} min-h-[400px]"
: 'min-w-[650px]'}"
onwheel={(e) => {
e.stopPropagation()
}}
@@ -69,12 +67,19 @@
{#if selectedKind != 'preprocessor' && selectedKind != 'flow'}
<ToggleHubWorkspaceQuick bind:selected={preFilter} />
{/if}
<RefreshButton size="md" light {loading} on:click={() => (refreshCount.val += 1)} />
<RefreshButton
size="sm"
light
{loading}
onClick={() => {
refreshCount.val += 1
}}
/>
</div>
<div class="flex flex-row grow min-h-0">
<div class="flex flex-row grow min-h-0 gap-2">
{#if kind === 'script'}
<div class="flex-none flex flex-col text-xs text-primary">
<div class="flex-none flex flex-col text-xs text-primary overflow-auto gap-1">
<TopLevelNode
label="Action"
selected={selectedKind === 'script'}
@@ -182,7 +187,6 @@
on:pickScript
on:pickFlow
{preFilter}
{small}
{displayPath}
refreshCount={refreshCount.val}
/>
@@ -1,15 +1,8 @@
<script lang="ts">
import InsertModuleInner from './InsertModuleInner.svelte'
import type { ComputeConfig } from 'svelte-floating-ui'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import PopupV2 from '$lib/components/common/popup/PopupV2.svelte'
import { flip, offset } from 'svelte-floating-ui/dom'
// import type { Writable } from 'svelte/store'
type Alignment = 'start' | 'end' | 'center'
type Side = 'top' | 'bottom'
type Placement = `${Side}-${Alignment}`
import type { Placement } from '@floating-ui/core'
interface Props {
allowTrigger?: boolean
@@ -17,38 +10,46 @@
kind?: 'script' | 'trigger' | 'preprocessor' | 'failure'
placement?: Placement
disableAi?: boolean
trigger: import('svelte').Snippet<[{ toggleOpen: () => void }]>
trigger: import('svelte').Snippet
gutter?: number
}
let {
funcDesc = $bindable(''),
kind = 'script',
placement = 'bottom-center',
disableAi = false,
trigger,
allowTrigger = false
trigger: triggerSnippet,
allowTrigger = false,
gutter = 0
}: Props = $props()
let floatingConfig: ComputeConfig = {
strategy: 'fixed',
// @ts-ignore
placement,
middleware: [offset(8), flip()],
autoUpdate: true
}
let open = $state(false)
let popover: Popover
$effect(() => {
!open && (funcDesc = '')
!popover?.isOpened() && (funcDesc = '')
})
</script>
<PopupV2 {floatingConfig} bind:open target="#flow-editor">
{#snippet button()}
{@render trigger?.({ toggleOpen: () => (open = !open) })}
<Popover
bind:this={popover}
portal="#flow-editor"
contentClasses="p-2 max-w-lg h-[400px] !resize bg-surface"
class="inline-block"
usePointerDownOutside
floatingConfig={{
placement: 'bottom',
strategy: 'absolute',
gutter,
overflowPadding: 16,
flip: true,
fitViewport: true,
overlap: false
}}
>
{#snippet trigger()}
{@render triggerSnippet?.()}
{/snippet}
{#snippet children({ close })}
{#snippet content({ close })}
<InsertModuleInner
on:close={() => close()}
on:insert
@@ -60,4 +61,4 @@
{disableAi}
/>
{/snippet}
</PopupV2>
</Popover>
@@ -45,6 +45,7 @@
onEditInput?: (moduleId: string, key: string) => void
flowJob?: Job | undefined
isOwner?: boolean
maximizeSubflow?: () => void
}
let {
@@ -64,7 +65,8 @@
onUpdateMock,
onEditInput,
flowJob,
isOwner = false
isOwner = false,
maximizeSubflow
}: Props = $props()
const { selectedId } = getContext<{
@@ -251,6 +253,7 @@
{flowJob}
{isOwner}
enableTestRun
{maximizeSubflow}
>
{#snippet icon()}
{@const size =
@@ -1,9 +1,9 @@
<script lang="ts">
import type { SupportedLanguage } from '$lib/common'
import Button from '$lib/components/common/button/Button.svelte'
import LanguageIcon from '$lib/components/common/languageIcons/LanguageIcon.svelte'
import { sendUserToast } from '$lib/toast'
import { createEventDispatcher } from 'svelte'
import { twMerge } from 'tailwind-merge'
export let label: string
export let lang: SupportedLanguage | 'docker' | 'javascript' | undefined = undefined
@@ -32,26 +32,22 @@
<svelte:window on:keydown={handleKeydown} />
<button
<Button
id={`flow-editor-new-${lang}`}
class={twMerge(
'px-3 py-2 gap-2 w-full text-left hover:bg-surface-hover flex flex-row items-center transition-all rounded-md',
selected ? 'bg-surface-hover' : ''
)}
on:click={click}
{selected}
onClick={click}
role="menuitem"
variant="subtle"
unifiedSize="sm"
btnClasses="justify-start"
>
{#if lang}
<LanguageIcon {lang} width={14} height={14} />
<LanguageIcon {lang} width={13} height={13} />
{/if}
<span
class="grow truncate text-left text-2xs font-normal {eeRestricted
? 'text-secondary'
: 'text-primary'}"
>
<span class="grow truncate text-left {eeRestricted ? 'text-disabled' : ''}">
{label}{#if eeRestricted}&nbsp;(EE){/if}
</span>
{#if selected}
<kbd class="!text-xs">&crarr;</kbd>
{/if}
</button>
</Button>
@@ -19,4 +19,4 @@
<svelte:window on:keydown={handleKeydown} />
<TopLevelNode class="px-3" {label} {selected} returnIcon onSelect={click} />
<TopLevelNode {label} {selected} returnIcon onSelect={click} />
@@ -46,6 +46,7 @@
import { usePromise } from '$lib/svelte5Utils.svelte'
import { userStore } from '$lib/stores'
import { get } from 'svelte/store'
import Button from '$lib/components/common/button/Button.svelte'
let hubNotAvailable = $state(false)
@@ -161,7 +162,7 @@
<svelte:window onkeydown={onKeyDown} />
{#if hubNotAvailable}
<div class="text-2xs text-red-400 ftext-2xs font-light text-center py-2 px-3 items-center">
<div class="text-2xs text-red-400 font-normal text-center py-2 px-3 items-center">
Hub not available
</div>
{:else if loading}
@@ -169,7 +170,7 @@
<Skeleton layout={[0.1, [1.5]]} />
{/each}
{:else if items.length > 0 && apps.length > 0}
<ul>
<ul class="gap-1 flex flex-col">
{#each items as item, index (item.path)}
<li class="w-full">
<Popover class="w-full" placement="right" forceOpen={index === selected}>
@@ -183,33 +184,30 @@
</div>
</div>
{/snippet}
<button
class="px-3 py-2 gap-2 flex flex-row w-full hover:bg-surface-hover transition-all items-center rounded-md {index ===
selected
? 'bg-surface-hover'
: ''}"
onclick={() => dispatch('pickScript', item)}
<Button
selected={selected === index}
variant="subtle"
unifiedSize="sm"
btnClasses="justify-start"
onClick={() => dispatch('pickScript', item)}
>
<div class={classNames('flex justify-center items-center')}>
{#if item['app'] in APP_TO_ICON_COMPONENT}
{@const SvelteComponent = APP_TO_ICON_COMPONENT[item['app']]}
<SvelteComponent height={14} width={14} />
<SvelteComponent height={13} width={13} />
{:else}
<div
class="w-[14px] h-[14px] text-gray-400 flex flex-row items-center justify-center"
>
<Circle size="12" />
<div class="text-gray-400 flex flex-row items-center justify-center">
<Circle size="13" />
</div>
{/if}
</div>
<div class="flex flex-col grow min-w-0">
<div
class="grow truncate text-left text-2xs text-primary font-normal leading-tight py-0.5"
<div class="grow truncate text-left font-normal leading-tight py-0.5"
>{item.summary ?? ''}</div
>
{#if displayPath && item.path}
<div class="grow truncate text-left text-2xs text-secondary font-[220]">
<div class="grow truncate text-left text-2xs font-thin">
{item.path}
</div>
{/if}
@@ -217,7 +215,7 @@
{#if index === selected}
<kbd class="!text-xs">&crarr;</kbd>
{/if}
</button>
</Button>
</Popover>
</li>
{/each}
@@ -11,18 +11,17 @@
Square,
Zap
} from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
import type { ComponentType } from 'svelte'
import Button from '$lib/components/common/button/Button.svelte'
interface Props {
label: string
selected?: boolean
returnIcon?: boolean
onSelect: () => void
class?: string
}
let { label, selected, returnIcon, onSelect, class: className }: Props = $props()
let { label, selected, returnIcon, onSelect }: Props = $props()
interface IconConfig {
icon: ComponentType
@@ -48,24 +47,19 @@
</script>
{#snippet iconWithText(icon: ComponentType, showChevron = false, iconClass = '')}
{@const Icon = icon}
<Icon size={14} class={iconClass} />
{label}
{#if showChevron}
<ChevronRight size={12} class="ml-auto" color="#4c566a" />
<ChevronRight size={12} class="ml-auto text-secondary" />
{/if}
{/snippet}
<button
<Button
id={`flow-editor-flow-kind-${label.replaceAll(' ', '-').toLowerCase()}`}
class={twMerge(
'w-full text-left py-2 px-1.5 hover:bg-surface-hover text-xs font-medium transition-all whitespace-nowrap flex flex-row gap-2 items-center rounded-md',
selected ? 'bg-surface-hover' : '',
className
)}
onpointerdown={onSelect}
role="menuitem"
tabindex="-1"
{selected}
onClick={onSelect}
variant="subtle"
unifiedSize="sm"
startIcon={{ icon: config?.icon }}
>
<span class="grow flex items-center gap-2">
{#if config}
@@ -75,4 +69,4 @@
{#if returnIcon && selected}
<kbd class="!text-xs text-right">&crarr;</kbd>
{/if}
</button>
</Button>
@@ -43,6 +43,7 @@
import { usePromise } from '$lib/svelte5Utils.svelte'
import { get } from 'svelte/store'
import { userStore } from '$lib/stores'
import Button from '$lib/components/common/button/Button.svelte'
type Item = {
path: string
@@ -151,7 +152,7 @@
{kind == 'flow' ? 'No flows found.' : 'No scripts found.'}
</div>
{/if}
<ul>
<ul class="gap-1 flex flex-col">
{#each filteredWithOwner ?? [] as { path, hash, summary, marked }, index}
<li class="w-full">
<Popover class="w-full " placement="right" forceOpen={index === selected}>
@@ -163,26 +164,24 @@
</div>
</div>
{/snippet}
<button
class="px-3 py-2 gap-2 flex flex-row w-full hover:bg-surface-hover transition-all items-center rounded-md {index ===
selected
? 'bg-surface-hover'
: ''}"
onclick={() => {
<Button
selected={selected === index}
variant="subtle"
unifiedSize="sm"
btnClasses="justify-start transition-all"
onClick={() => {
if (kind == 'flow') {
dispatch('pickFlow', { path: path })
} else {
dispatch('pickScript', { path: path, hash: lockHash ? hash : undefined, kind })
}
}}
startIcon={{
icon: kind == 'flow' ? BarsStaggered : Code2
}}
>
{#if kind == 'flow'}
<BarsStaggered size={14} class="shrink-0" />
{:else}
<Code2 size={14} />
{/if}
<div class="flex flex-col grow min-w-0">
<div class="grow min-w-0 truncate text-left text-2xs text-primary font-normal">
<div class="grow min-w-0 truncate text-left">
{#if marked}
{@html marked}
{:else}
@@ -190,7 +189,7 @@
{/if}
</div>
{#if displayPath && path}
<div class="grow min-w-0 truncate text-left text-2xs text-secondary font-[220]">
<div class="grow min-w-0 truncate text-left text-2xs font-thin">
{path}
</div>
{/if}
@@ -198,7 +197,7 @@
{#if index === selected}
<kbd class="!text-xs">&crarr;</kbd>
{/if}
</button>
</Button>
</Popover>
</li>
{/each}
@@ -160,6 +160,7 @@
class="flex-1 h-full"
bind:isOpen={inputOpen}
bind:this={inputPopover}
portal="#flow-graph-v2"
>
{#snippet trigger({ isOpen })}
<Button
@@ -206,6 +207,7 @@
closeOnOtherPopoverOpen
class="flex-1 h-full"
bind:isOpen={outputOpen}
portal="#flow-graph-v2"
>
{#snippet trigger({ isOpen })}
<AnimatedButton
@@ -597,7 +597,7 @@
nodesDraggable={false}
--background-color={false}
>
<div class="absolute inset-0 !bg-surface-secondary h-full"></div>
<div class="absolute inset-0 !bg-surface-secondary h-full" id="flow-graph-v2"></div>
<Controls position="top-right" orientation="horizontal" showLock={false}>
{#if download}
<ControlButton
@@ -129,13 +129,10 @@
flow: e.detail
})
}}
gutter={0}
>
{#snippet trigger({ toggleOpen })}
<InsertModuleButton
onPress={toggleOpen}
title={`Add step`}
id={`flow-editor-add-step-${data.index ?? 0}`}
/>
{#snippet trigger()}
<InsertModuleButton title={`Add step`} id={`flow-editor-add-step-${data.index ?? 0}`} />
{/snippet}
</InsertModulePopover>
</div>
@@ -58,12 +58,8 @@
})
}}
>
{#snippet trigger({ toggleOpen })}
<InsertModuleButton
onPress={toggleOpen}
title={`Add preprocessor step`}
id={`flow-editor-add-step-0`}
/>
{#snippet trigger()}
<InsertModuleButton title={`Add preprocessor step`} id={`flow-editor-add-step-0`} />
{/snippet}
</InsertModulePopover>
</div>
@@ -1,8 +1,6 @@
<script lang="ts">
import { preventDefault, stopPropagation } from 'svelte/legacy'
import MapItem from '$lib/components/flows/map/MapItem.svelte'
import { GitBranchPlus, Maximize2 } from 'lucide-svelte'
import { GitBranchPlus } from 'lucide-svelte'
import NodeWrapper from './NodeWrapper.svelte'
import type { ModuleN } from '../../graphBuilder.svelte'
import { jobToGraphModuleState } from '$lib/components/modulesTest.svelte'
@@ -41,21 +39,6 @@
<NodeWrapper offset={data.offset}>
{#snippet children({ darkMode })}
{#if data.module.value.type == 'flow'}
<button
title="Expand subflow"
class="z-50 absolute -top-[10px] right-[25px] rounded-full h-[20px] w-[20px] center-center text-primary bg-surface duration-0 hover:bg-surface-hover"
onclick={stopPropagation(
preventDefault(() => {
if (data.module.value.type == 'flow') {
data.eventHandlers.expandSubflow(data.id, data.module.value.path)
}
})
)}
>
<Maximize2 size={12} />
</button>
{/if}
<MapItem
moduleId={data.id}
mod={data.module}
@@ -100,6 +83,11 @@
onEditInput={data.eventHandlers.editInput}
flowJob={data.flowJob}
isOwner={data.isOwner}
maximizeSubflow={data.module.value.type == 'flow' && 'path' in data.module.value
? () => {
data.eventHandlers.expandSubflow(data.id, data.module.value['path'])
}
: undefined}
/>
<div class="absolute -bottom-10 left-1/2 transform -translate-x-1/2 z-10">
@@ -26,7 +26,7 @@
/>
<button
title="Unexpand subflow"
class="z-50 absolute -top-[10px] right-[25px] rounded-full h-[20px] w-[20px] center-center text-primary bg-surface duration-0 hover:bg-surface-hover"
class="z-50 absolute -top-[10px] right-[25px] rounded-md h-[20px] w-[20px] center-center text-primary bg-surface duration-0 hover:bg-surface-accent-hover hover:text-white"
onclick={stopPropagation(
preventDefault(() => {
data.eventHandlers.minimizeSubflow(data.subflowId)
@@ -1,8 +1,8 @@
<script lang="ts">
import { Folder, User, Circle } from 'lucide-svelte'
import { APP_TO_ICON_COMPONENT } from '../icons'
import { twMerge } from 'tailwind-merge'
import { createEventDispatcher } from 'svelte'
import { Button } from '../common'
export let filters: string[]
export let selectedFilter:
@@ -12,8 +12,20 @@
export let resourceType = false
function getIconComponent(name: string) {
return APP_TO_ICON_COMPONENT[name] || APP_TO_ICON_COMPONENT[name.split('_')[0]]
function getIconComponent(name: string, resourceType: boolean) {
if (resourceType) {
const icon = APP_TO_ICON_COMPONENT[name] || APP_TO_ICON_COMPONENT[name.split('_')[0]]
if (icon) {
return { icon: icon, props: { width: 13, height: 13 } }
} else {
return { icon: Circle, props: { class: 'text-gray-400' } }
}
} else if (name.startsWith('u/')) {
return { icon: User }
} else if (name.startsWith('f/')) {
return { icon: Folder }
}
return { icon: undefined }
}
const dispatch = createEventDispatcher()
@@ -21,38 +33,22 @@
{#if Array.isArray(filters) && filters.length > 0}
{#each filters as filter (filter)}
{@const icon = getIconComponent(filter, resourceType)}
<div>
<button
class={twMerge(
'w-full text-left py-2 px-3 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center rounded-md',
filter === selectedAppFilter ? 'bg-surface-hover' : ''
)}
on:click={() => {
<Button
selected={filter === selectedAppFilter}
onClick={() => {
selectedFilter =
selectedAppFilter == filter ? undefined : { kind: 'integrations', name: filter }
dispatch('selected')
}}
variant="subtle"
startIcon={icon}
unifiedSize="sm"
btnClasses="justify-start"
>
<div class="flex justify-center flex-row items-center gap-2">
{#if resourceType}
{@const icon = getIconComponent(filter)}
{#if icon}
<svelte:component this={icon} height="14px" width="14px" />
{:else}
<div
class="w-[14px] h-[14px] text-gray-400 flex flex-row items-center justify-center"
>
<Circle size="12" />
</div>
{/if}
{:else if filter.startsWith('u/')}
<User class="mr-0.5" size={14} />
{:else if filter.startsWith('f/')}
<Folder class="mr-0.5" size={14} />
{/if}
<span class="text-left text-2xs text-primary font-normal">{filter}</span>
</div>
</button>
{filter}
</Button>
</div>
{/each}
{/if}
@@ -1,10 +1,22 @@
<script lang="ts">
interface Props {
height?: string;
width?: string;
height?: string
width?: string
}
let { height = '24px', width = '24px' }: Props = $props();
let { height = '24px', width = '24px' }: Props = $props()
</script>
<svg {width} {height} viewBox="0 0 256 236.624" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><title>Asana</title><g><path d="M200.324957,125.270044 C169.575962,125.270044 144.649915,150.19729 144.649915,180.947483 C144.649915,211.696478 169.575962,236.623724 200.324957,236.623724 C231.073952,236.623724 256,211.696478 256,180.947483 C256,150.19729 231.073952,125.270044 200.324957,125.270044 L200.324957,125.270044 Z M55.6754021,125.274837 C24.9270063,125.274837 0,150.19729 0,180.947483 C0,211.696478 24.9270063,236.623724 55.6754021,236.623724 C86.425116,236.623724 111.35332,211.696478 111.35332,180.947483 C111.35332,150.19729 86.425116,125.274837 55.6754021,125.274837 L55.6754021,125.274837 Z M183.674444,55.674204 C183.674444,86.425116 158.748396,111.354638 128.000599,111.354638 C97.2505258,111.354638 72.3247177,86.425116 72.3247177,55.674204 C72.3247177,24.9294026 97.2505258,0 128.000599,0 C158.748396,0 183.674444,24.9294026 183.674444,55.674204 L183.674444,55.674204 Z" fill="#F06A6A"/></g></svg>
<svg
{width}
{height}
viewBox="0 0 256 236.624"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid"
><title>Asana</title><g
><path
d="M200.324957,125.270044 C169.575962,125.270044 144.649915,150.19729 144.649915,180.947483 C144.649915,211.696478 169.575962,236.623724 200.324957,236.623724 C231.073952,236.623724 256,211.696478 256,180.947483 C256,150.19729 231.073952,125.270044 200.324957,125.270044 L200.324957,125.270044 Z M55.6754021,125.274837 C24.9270063,125.274837 0,150.19729 0,180.947483 C0,211.696478 24.9270063,236.623724 55.6754021,236.623724 C86.425116,236.623724 111.35332,211.696478 111.35332,180.947483 C111.35332,150.19729 86.425116,125.274837 55.6754021,125.274837 L55.6754021,125.274837 Z M183.674444,55.674204 C183.674444,86.425116 158.748396,111.354638 128.000599,111.354638 C97.2505258,111.354638 72.3247177,86.425116 72.3247177,55.674204 C72.3247177,24.9294026 97.2505258,0 128.000599,0 C158.748396,0 183.674444,24.9294026 183.674444,55.674204 L183.674444,55.674204 Z"
fill="#F06A6A"
/></g
></svg
>
@@ -1,19 +1,26 @@
<script lang="ts">
interface Props {
size?: number
height?: number
width?: number
}
let { size = 24 }: Props = $props()
let { size = undefined, height: heightProp = 24, width: widthProp = 24 }: Props = $props()
const { width, height } = $derived(
size ? { width: size, height: size } : { width: widthProp, height: heightProp }
)
</script>
<svg
role="img"
viewBox="0 0 24 24"
width="{size}px"
height="{size}px"
{width}
{height}
xmlns="http://www.w3.org/2000/svg"
style="fill:currentcolor;"
><title>Nextcloud icon</title><path
d="M12.018 6.537c-2.5 0-4.6 1.712-5.241 4.015-.56-1.232-1.793-2.105-3.225-2.105A3.569 3.569 0 0 0 0 12a3.569 3.569 0 0 0 3.552 3.553c1.432 0 2.664-.874 3.224-2.106.641 2.304 2.742 4.016 5.242 4.016 2.487 0 4.576-1.693 5.231-3.977.569 1.21 1.783 2.067 3.198 2.067A3.568 3.568 0 0 0 24 12a3.569 3.569 0 0 0-3.553-3.553c-1.416 0-2.63.858-3.199 2.067-.654-2.284-2.743-3.978-5.23-3.977zm0 2.085c1.878 0 3.378 1.5 3.378 3.378 0 1.878-1.5 3.378-3.378 3.378A3.362 3.362 0 0 1 8.641 12c0-1.878 1.5-3.378 3.377-3.378zm-8.466 1.91c.822 0 1.467.645 1.467 1.468s-.644 1.467-1.467 1.468A1.452 1.452 0 0 1 2.085 12c0-.823.644-1.467 1.467-1.467zm16.895 0c.823 0 1.468.645 1.468 1.468s-.645 1.468-1.468 1.468A1.452 1.452 0 0 1 18.98 12c0-.823.644-1.467 1.467-1.467z"
/></svg
>
<path
d="M12.018 6.537c-2.5 0-4.6 1.712-5.241 4.015-.56-1.232-1.793-2.105-3.225-2.105A3.569 3.569 0 0 0 0 12a3.569 3.569 0 0 0 3.552 3.553c1.432 0 2.664-.874 3.224-2.106.641 2.304 2.742 4.016 5.242 4.016 2.487 0 4.576-1.693 5.231-3.977.569 1.21 1.783 2.067 3.198 2.067A3.568 3.568 0 0 0 24 12a3.569 3.569 0 0 0-3.553-3.553c-1.416 0-2.63.858-3.199 2.067-.654-2.284-2.743-3.978-5.23-3.977zm0 2.085c1.878 0 3.378 1.5 3.378 3.378 0 1.878-1.5 3.378-3.378 3.378A3.362 3.362 0 0 1 8.641 12c0-1.878 1.5-3.378 3.377-3.378zm-8.466 1.91c.822 0 1.467.645 1.467 1.468s-.644 1.467-1.467 1.468A1.452 1.452 0 0 1 2.085 12c0-.823.644-1.467 1.467-1.467zm16.895 0c.823 0 1.468.645 1.468 1.468s-.645 1.468-1.468 1.468A1.452 1.452 0 0 1 18.98 12c0-.823.644-1.467 1.467-1.467z"
/>
</svg>
@@ -43,6 +43,9 @@
let fullScreen = false
const dispatch = createEventDispatcher()
// Dynamic portal: use 'body' when fullscreen, otherwise use the provided portal
$: dynamicPortal = fullScreen ? 'body' : portal
function clearTimers() {
clearDebounceClose()
}
@@ -54,11 +57,11 @@
const {
elements: { trigger, content, arrow, close: closeElement, overlay },
states,
options: { closeOnOutsideClick: closeOnOutsideClickOption, positioning },
options: { closeOnOutsideClick: closeOnOutsideClickOption, positioning, portal: portalOption },
ids: { content: popoverId }
} = createPopover({
forceVisible: true,
portal,
portal: dynamicPortal,
disableFocusTrap,
escapeBehavior,
onOpenChange: ({ curr, next }) => {
@@ -87,8 +90,16 @@
$positioning = floatingConfig ?? {
placement,
strategy: 'absolute',
x: undefined,
y: undefined
gutter: 8,
overflowPadding: 16,
flip: true,
fitViewport: true,
overlap: false
}
// Update portal reactively when fullscreen state changes
$: if (portalOption) {
$portalOption = dynamicPortal
}
export let isOpen = false
@@ -171,7 +182,7 @@
use:melt={$content}
transition:fly={{ duration: enableFlyTransition ? 100 : 0, y: -16 }}
class={twMerge(
'relative border rounded-md bg-surface-tertiary shadow-lg',
'relative dark:border rounded-md bg-surface-tertiary shadow-lg',
fullScreen
? `fixed !top-1/2 !left-1/2 !-translate-x-1/2 !-translate-y-1/2 !resize-none`
: 'w-fit',
@@ -189,11 +200,11 @@
<div class="absolute top-0 right-0 z-10">
<Button
on:click={() => (fullScreen = !fullScreen)}
color="light"
size="xs2"
variant="subtle"
unifiedSize="sm"
btnClasses="text-secondary"
iconOnly
startIcon={fullScreen ? { icon: Minimize2 } : { icon: Maximize2 }}
btnClasses="text-gray-400"
/>
</div>
{/if}
@@ -145,7 +145,7 @@
<div class="text-xs text-primary whitespace-nowrap"
>{`${totalNumberOfAlerts === 1000 ? '1000+' : (totalNumberOfAlerts ?? '?')} items`}
</div>
<RefreshButton {loading} on:click={refreshAlerts} />
<RefreshButton {loading} onClick={refreshAlerts} />
</List>
</List>
</div>