mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 16:02:11 +00:00
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:
@@ -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} (EE){/if}
|
||||
</span>
|
||||
{#if selected}
|
||||
<kbd class="!text-xs">↵</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">↵</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">↵</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">↵</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>
|
||||
|
||||
Reference in New Issue
Block a user