replace ButtonDropdown with DropdownV2

This commit is contained in:
Guilhem
2025-02-11 21:36:12 +00:00
parent 8ef175e77b
commit 02567839a3
18 changed files with 360 additions and 540 deletions
+13 -12
View File
@@ -3,23 +3,14 @@
import { Menu, Menubar } from '$lib/components/meltComponents'
import { melt } from '@melt-ui/svelte'
import type { Placement } from '@floating-ui/core'
import type { Item } from '$lib/utils'
import DropdownV2Inner from './DropdownV2Inner.svelte'
type Item = {
displayName: string
action?: (e: CustomEvent<any>) => void
icon?: any
href?: string
disabled?: boolean
type?: 'action' | 'delete'
hide?: boolean | undefined
}
export let items: Item[] | (() => Item[]) | (() => Promise<Item[]>) = []
export let justifyEnd: boolean = true
export let disabled = false
export let placement: Placement = 'bottom-end'
export let usePointerDownOutside = false
async function computeItems(): Promise<Item[]> {
if (typeof items === 'function') {
@@ -31,7 +22,17 @@
</script>
<Menubar let:createMenu>
<Menu {createMenu} {placement} {justifyEnd} on:close on:open {disabled} let:item>
<Menu
{createMenu}
{placement}
{justifyEnd}
on:close
on:open
{disabled}
let:item
class={$$props.class}
{usePointerDownOutside}
>
<svelte:fragment slot="trigger" let:trigger>
<div use:melt={trigger}>
{#if $$slots.buttonReplacement}
@@ -7,6 +7,7 @@
displayName: string
action?: (e: CustomEvent<any>) => void
icon?: any
iconColor?: string
href?: string
disabled?: boolean
type?: 'action' | 'delete'
@@ -36,7 +37,7 @@
href={item?.href}
disabled={item?.disabled}
class={twMerge(
'px-4 py-2 text-primary font-normal hover:bg-surface-hover cursor-pointer text-xs transition-all',
'px-4 py-2 text-primary font-semibold hover:bg-surface-hover cursor-pointer text-xs transition-all',
'data-[highlighted]:bg-surface-hover',
'flex flex-row gap-2 items-center',
item?.disabled && 'text-gray-400 cursor-not-allowed',
@@ -47,7 +48,7 @@
item={meltItem}
>
{#if item.icon}
<svelte:component this={item.icon} size={14} />
<svelte:component this={item.icon} size={14} color={item.iconColor} />
{/if}
{item.displayName}
</MenuItem>
+9 -29
View File
@@ -73,14 +73,11 @@
import { loadFlowModuleState, pickScript } from './flows/flowStateUtils'
import FlowCopilotInputsModal from './copilot/FlowCopilotInputsModal.svelte'
import FlowBuilderTutorials from './FlowBuilderTutorials.svelte'
import Dropdown from '$lib/components/DropdownV2.svelte'
import FlowTutorials from './FlowTutorials.svelte'
import { ignoredTutorials } from './tutorials/ignoredTutorials'
import type DiffDrawer from './DiffDrawer.svelte'
import FlowHistory from './flows/FlowHistory.svelte'
import ButtonDropdown from './common/button/ButtonDropdown.svelte'
import { MenuItem } from '@rgossiaux/svelte-headlessui'
import { twMerge } from 'tailwind-merge'
import CustomPopover from './CustomPopover.svelte'
import Summary from './Summary.svelte'
import type { FlowBuilderWhitelabelCustomUi } from './custom_ui'
@@ -1364,33 +1361,16 @@
{/if}
<div>
{#if moreItems?.length > 0}
<ButtonDropdown hasPadding={false}>
<Dropdown items={moreItems} class="center-center">
<svelte:fragment slot="buttonReplacement">
<Button nonCaptureEvent size="xs" color="light">
<div class="flex flex-row items-center">
<MoreVertical size={14} />
</div>
</Button>
<Button
nonCaptureEvent
size="xs"
color="light"
startIcon={{ icon: MoreVertical }}
/>
</svelte:fragment>
<svelte:fragment slot="items">
{#each moreItems as item}
<MenuItem
on:click={item.action}
disabled={item.disabled}
class={item.disabled ? 'opacity-50' : ''}
>
<div
class={twMerge(
'text-primary flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
<svelte:component this={item.icon} size={14} />
{item.displayName}
</div>
</MenuItem>
{/each}
</svelte:fragment>
</ButtonDropdown>
</Dropdown>
{/if}
</div>
{#if customUi?.topBar?.tutorials != false}
@@ -1,21 +1,47 @@
<script lang="ts">
import { BookOpen } from 'lucide-svelte'
import ButtonDropdown from './common/button/ButtonDropdown.svelte'
import { BookOpen, CheckCircle, Circle, RefreshCw, CheckCheck } from 'lucide-svelte'
import Dropdown from '$lib/components/DropdownV2.svelte'
import Button from './common/button/Button.svelte'
import MenuItem from './common/menu/MenuItem.svelte'
import { classNames } from '$lib/utils'
import { resetAllTodos, skipAllTodos } from '$lib/tutorialUtils'
import ConfirmationModal from './common/confirmationModal/ConfirmationModal.svelte'
import TutorialItem from './tutorials/TutorialItem.svelte'
import FlowTutorials from './FlowTutorials.svelte'
import { tutorialsToDo } from '$lib/stores'
let targetTutorial: string | undefined = undefined
let flowTutorials: FlowTutorials | undefined = undefined
async function getTutorialItems() {
const tutorials = [
{ displayName: 'Simple flow tutorials', id: 'action' },
{ displayName: 'For loops tutorial', id: 'forloop' },
{ displayName: 'Branch one tutorial', id: 'branchone' },
{ displayName: 'Branch all tutorial', id: 'branchall' },
{ displayName: 'Error handler', id: 'error-handler' }
]
return [
...tutorials.map((tutorial, index) => ({
displayName: tutorial.displayName,
action: () => flowTutorials?.runTutorialById(tutorial.id),
icon: $tutorialsToDo.includes(index) ? Circle : CheckCircle,
iconColor: $tutorialsToDo.includes(index) ? undefined : 'green'
})),
{
displayName: 'Skip tutorials',
action: () => skipAllTodos(),
icon: CheckCheck
},
{
displayName: 'Reset tutorials',
action: () => resetAllTodos(),
icon: RefreshCw
}
]
}
</script>
<button on:pointerdown|stopPropagation>
<ButtonDropdown hasPadding={false}>
{#key $tutorialsToDo}
<Dropdown items={getTutorialItems}>
<svelte:fragment slot="buttonReplacement">
<Button
nonCaptureEvent
@@ -26,63 +52,8 @@
startIcon={{ icon: BookOpen }}
/>
</svelte:fragment>
<svelte:fragment slot="items">
<TutorialItem
on:click={() => flowTutorials?.runTutorialById('action')}
label="Simple flow tutorial"
index={0}
/>
<TutorialItem
on:click={() => flowTutorials?.runTutorialById('forloop')}
label="For loops tutorial"
index={1}
/>
<TutorialItem
on:click={() => flowTutorials?.runTutorialById('branchone')}
label="Branch one tutorial"
index={2}
/>
<TutorialItem
on:click={() => flowTutorials?.runTutorialById('branchall')}
label="Branch all tutorial"
index={3}
/>
<TutorialItem
on:click={() => flowTutorials?.runTutorialById('error-handler')}
label="Error handler"
index={4}
/>
<div class="border-t border-surface-hover" />
<MenuItem
on:click={() => {
resetAllTodos()
}}
>
<div
class={classNames(
'text-primary flex flex-row items-center text-left gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
Reset tutorials
</div>
</MenuItem>
<MenuItem on:click={() => skipAllTodos()}>
<div
class={classNames(
'text-primary flex flex-row items-center text-left gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
Skip tutorials
</div>
</MenuItem>
</svelte:fragment>
</ButtonDropdown>
</button>
</Dropdown>
{/key}
<FlowTutorials
bind:this={flowTutorials}
@@ -69,8 +69,7 @@
import DeploymentHistory from './DeploymentHistory.svelte'
import Awareness from '$lib/components/Awareness.svelte'
import { secondaryMenuLeftStore, secondaryMenuRightStore } from './settingsPanel/secondaryMenu'
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
import { MenuItem } from '@rgossiaux/svelte-headlessui'
import Dropdown from '$lib/components/DropdownV2.svelte'
import AppEditorTutorial from './AppEditorTutorial.svelte'
import AppTimeline from './AppTimeline.svelte'
import type DiffDrawer from '$lib/components/DiffDrawer.svelte'
@@ -1645,33 +1644,11 @@
<Awareness />
{/if}
<div class="flex flex-row gap-2 justify-end items-center overflow-visible">
<ButtonDropdown hasPadding={false}>
<Dropdown items={moreItems} class="center-center">
<svelte:fragment slot="buttonReplacement">
<Button nonCaptureEvent size="xs" color="light">
<div class="flex flex-row items-center">
<MoreVertical size={14} />
</div>
</Button>
<Button nonCaptureEvent size="xs" color="light" startIcon={{ icon: MoreVertical }} />
</svelte:fragment>
<svelte:fragment slot="items">
{#each moreItems as item}
<MenuItem
on:click={item.action}
disabled={item.disabled}
class={item.disabled ? 'opacity-50' : ''}
>
<div
class={classNames(
'text-primary flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
<svelte:component this={item.icon} size={14} />
{item.displayName}
</div>
</MenuItem>
{/each}
</svelte:fragment>
</ButtonDropdown>
</Dropdown>
<AppEditorTutorial bind:this={appEditorTutorial} />
<div class="hidden md:inline relative overflow-visible">
@@ -1,12 +1,8 @@
<script lang="ts">
import Button from '$lib/components/common/button/Button.svelte'
import AppTutorials from '../../AppTutorials.svelte'
import { BookOpen } from 'lucide-svelte'
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
import TutorialItem from '$lib/components/tutorials/TutorialItem.svelte'
import MenuItem from '$lib/components/common/menu/MenuItem.svelte'
import { classNames } from '$lib/utils'
import { BookOpen, CheckCircle, Circle, RefreshCw, CheckCheck } from 'lucide-svelte'
import Dropdown from '$lib/components/DropdownV2.svelte'
import { resetAllTodos, skipAllTodos } from '$lib/tutorialUtils'
import { getContext, onMount } from 'svelte'
import type { AppViewerContext } from '../types'
@@ -45,63 +41,60 @@
appTutorials?.runTutorialById('simpleapptutorial')
}
}
async function getTutorialItems() {
return [
{
displayName: 'App tutorial',
action: () => appTutorials?.runTutorialById('simpleapptutorial'),
index: 7,
icon: $tutorialsToDo.includes(7) ? Circle : CheckCircle,
iconColor: $tutorialsToDo.includes(7) ? undefined : 'green'
},
{
displayName: 'Background runnables',
action: () => appTutorials?.runTutorialById('backgroundrunnables'),
index: 5,
icon: $tutorialsToDo.includes(5) ? Circle : CheckCircle,
iconColor: $tutorialsToDo.includes(5) ? undefined : 'green'
},
{
displayName: 'Connection',
action: () => appTutorials?.runTutorialById('connection'),
index: 6,
icon: $tutorialsToDo.includes(6) ? Circle : CheckCircle,
iconColor: $tutorialsToDo.includes(6) ? undefined : 'green'
},
{
displayName: 'Reset tutorials',
action: () => resetAllTodos(),
icon: RefreshCw
},
{
displayName: 'Skip tutorials',
action: () => skipAllTodos(),
icon: CheckCheck
}
]
}
</script>
<ButtonDropdown hasPadding={false}>
<svelte:fragment slot="buttonReplacement">
<Button
nonCaptureEvent
size="xs"
color="light"
variant="border"
iconOnly
startIcon={{
icon: BookOpen
}}
/>
</svelte:fragment>
<svelte:fragment slot="items">
<TutorialItem
on:click={() => appTutorials?.runTutorialById('simpleapptutorial')}
label="App tutorial"
index={7}
/>
<TutorialItem
on:click={() => appTutorials?.runTutorialById('backgroundrunnables')}
label="Background runnables"
index={5}
/>
<TutorialItem
on:click={() => appTutorials?.runTutorialById('connection')}
label="Connection"
index={6}
/>
<div class="border-t border-surface-hover" />
<MenuItem
on:click={() => {
resetAllTodos()
}}
>
<div
class={classNames(
'text-primary flex flex-row items-center text-left gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
Reset tutorials
</div>
</MenuItem>
<MenuItem on:click={() => skipAllTodos()}>
<div
class={classNames(
'text-primary flex flex-row items-center text-left gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
Skip tutorials
</div>
</MenuItem>
</svelte:fragment>
</ButtonDropdown>
{#key $tutorialsToDo}
<Dropdown items={getTutorialItems} hasPadding={false}>
<svelte:fragment slot="buttonReplacement">
<Button
nonCaptureEvent
size="xs"
color="light"
variant="border"
iconOnly
startIcon={{
icon: BookOpen
}}
/>
</svelte:fragment>
</Dropdown>
{/key}
<AppTutorials
bind:this={appTutorials}
@@ -1,7 +1,6 @@
<script lang="ts">
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
import Dropdown from '$lib/components/DropdownV2.svelte'
import { classNames } from '$lib/utils'
import { MenuItem } from '@rgossiaux/svelte-headlessui'
import { createEventDispatcher, getContext } from 'svelte'
import type { AppViewerContext } from '../types'
import type { DecisionTreeNode } from './component'
@@ -69,81 +68,68 @@
}
$: onNodesChange(nodes)
async function getDropdownItems() {
return [
// Debug node items
...(nodes ?? []).map((node, index) => ({
displayName: `Debug node ${node.label}`,
action: () => {
$componentControl?.[id]?.setTab?.(index)
$debuggingComponents[id] = index
},
type: 'action' as const
})),
// Reset debug mode item
{
displayName: 'Reset debug mode',
action: () => {
$componentControl?.[id]?.setTab?.(0)
$debuggingComponents = Object.fromEntries(
Object.entries($debuggingComponents).filter(([key]) => key !== id)
)
},
type: 'delete' as const
}
]
}
</script>
{#key renderCount}
<button
title={'Debug tabs'}
class={classNames(
'px-1 text-2xs font-bold rounded cursor-pointer w-fit h-full',
componentIsDebugging
? ' hover:bg-red-300 hover:text-red-800'
: 'text-blue-600 hover:bg-blue-300 hover:text-blue-800'
)}
on:click={() => dispatch('triggerInlineEditor')}
on:pointerdown|stopPropagation
>
<ButtonDropdown hasPadding={false}>
<svelte:fragment slot="buttonReplacement">
<div class="px-1 w-fit">
{#if componentIsDebugging}
<div class="flex flex-row items-center gap-2">
{`${isSmall ? '' : 'Debugging node'} ${nodes[$debuggingComponents[id] ?? 0]?.id}`}
<button
on:click={() => {
$componentControl?.[id]?.setTab?.(0)
<Dropdown items={getDropdownItems} class="w-fit h-auto" usePointerDownOutside={true}>
<svelte:fragment slot="buttonReplacement">
<button
title={'Debug tabs'}
class={classNames(
'px-1 text-2xs font-bold rounded cursor-pointer w-fit h-full',
componentIsDebugging
? ' hover:bg-red-300 hover:text-red-800'
: 'text-blue-600 hover:bg-blue-300 hover:text-blue-800'
)}
on:click={() => dispatch('triggerInlineEditor')}
on:pointerdown|stopPropagation
>
{#if componentIsDebugging}
<div class="flex flex-row items-center gap-2">
{`${isSmall ? '' : 'Debugging node'} ${nodes[$debuggingComponents[id] ?? 0]?.id}`}
<button
on:click={() => {
$componentControl?.[id]?.setTab?.(0)
$debuggingComponents = Object.fromEntries(
Object.entries($debuggingComponents).filter(([key]) => key !== id)
)
}}
>
<X size={11} />
</button>
</div>
{:else if isSmall}
<div class="flex h-full w-fit items-center"><Bug size={11} /></div>
{:else}<div class="whitespace-nowrap h-full"
>{`Debug nodes (current node: ${currentNodeId})`}</div
>{/if}
</div>
</svelte:fragment>
<svelte:fragment slot="items">
{#each nodes ?? [] as node, index}
<MenuItem
on:click={() => {
$componentControl?.[id]?.setTab?.(index)
$debuggingComponents[id] = index
}}
>
<div
class={classNames(
'!text-tertiary text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
)}
$debuggingComponents = Object.fromEntries(
Object.entries($debuggingComponents).filter(([key]) => key !== id)
)
}}
>
{`Debug node ${node.label}`}
</div>
</MenuItem>
{/each}
<MenuItem
on:click={() => {
$componentControl?.[id]?.setTab?.(0)
$debuggingComponents = Object.fromEntries(
Object.entries($debuggingComponents).filter(([key]) => key !== id)
)
}}
>
<div
class={classNames(
'!text-red-600 text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
)}
>
{`Reset debug mode`}
<X size={11} />
</button>
</div>
</MenuItem>
</svelte:fragment>
</ButtonDropdown>
</button>
{:else if isSmall}
<div class="flex h-full w-fit items-center"><Bug size={11} /></div>
{:else}<div class="whitespace-nowrap h-full"
>{`Debug nodes (current node: ${currentNodeId})`}</div
>{/if}
</button>
</svelte:fragment>
</Dropdown>
{/key}
@@ -1,9 +1,7 @@
<script lang="ts">
import { Loader2, RefreshCw, TimerReset } from 'lucide-svelte'
import Button from '../../common/button/Button.svelte'
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
import { MenuItem } from '@rgossiaux/svelte-headlessui'
import { classNames } from '$lib/utils'
import Dropdown from '$lib/components/DropdownV2.svelte'
import { twMerge } from 'tailwind-merge'
import Badge from '$lib/components/common/badge/Badge.svelte'
import { createEventDispatcher } from 'svelte'
@@ -54,53 +52,20 @@
</div>
</Button>
<ButtonDropdown hasPadding={false}>
<slot:fragment slot="buttonReplacement">
<Dropdown {items} class="w-fit h-auto">
<svelte:fragment slot="buttonReplacement">
<div class="flex flex-row gap-2 text-xs hover:bg-surface-hover px-2 items-center h-7">
{#if interval}
<Badge color="blue" small>
{interval ? `Every ${interval / 1000}s` : 'Once'}
</Badge>
{/if}
<div class="flex justify-center items-center">
<TimerReset size={14} />
</div>
</div>
</slot:fragment>
<svelte:fragment slot="label">
<span
class={twMerge('text-xs min-w-[2rem] ', interval ? 'text-blue-500' : 'text-tertiary')}
>
{interval ? `${interval / 1000}s` : 'Once'}
</span>
</svelte:fragment>
<svelte:fragment slot="items">
{#each items ?? [] as { }, index}
<MenuItem
on:click={() => {
if (index === 0) {
dispatch('setInter', undefined)
} else {
dispatch('setInter', index * 5000)
}
}}
>
<div
class={classNames(
'!text-tertiary text-left px-4 py-2 gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
{#if index === 0}
Once
{:else}
{`Every ${index * 5} seconds`}
{/if}
</div>
</MenuItem>
{/each}
</svelte:fragment>
</ButtonDropdown>
</Dropdown>
</div>
{#if interval}
<div class="w-full bg-gray-200 rounded-full h-0.5 dark:bg-gray-700">
@@ -1,7 +1,6 @@
<script lang="ts">
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
import Dropdown from '$lib/components/DropdownV2.svelte'
import { classNames } from '$lib/utils'
import { MenuItem } from '@rgossiaux/svelte-headlessui'
import { createEventDispatcher, getContext } from 'svelte'
import type { AppViewerContext } from '../types'
import { Bug } from 'lucide-svelte'
@@ -17,22 +16,50 @@
export let isManuallySelected: boolean = false
let selected: number | null = null
async function getItems() {
return [
...tabs.map((_, index) => ({
displayName:
index === tabs.length - 1
? isConditionalDebugMode
? `Debug default condition`
: `Debug tab ${index + 1}`
: `Debug ${isConditionalDebugMode ? 'condition' : 'tab'} ${index + 1}`,
action: () => {
$componentControl?.[id]?.setTab?.(index)
selected = index
isManuallySelected = true
},
type: 'action' as const
})),
{
displayName: 'Reset debug mode',
action: () => {
$componentControl?.[id]?.setTab?.(-1)
selected = null
isManuallySelected = false
},
type: 'delete' as const
}
]
}
</script>
<button
title={isConditionalDebugMode ? 'Debug conditions' : 'Debug tabs'}
class={classNames(
'text-2xs font-bold w-fit h-full cursor-pointer rounded',
isManuallySelected
? 'hover:bg-red-200 hover:text-red-800'
: 'text-blue-600 hover:bg-blue-300 hover:text-blue-800'
)}
on:click={() => dispatch('triggerInlineEditor')}
on:pointerdown|stopPropagation
>
<ButtonDropdown hasPadding={false}>
{#key tabs}
<Dropdown items={getItems} class="w-fit h-auto" usePointerDownOutside>
<svelte:fragment slot="buttonReplacement">
<div class="px-1">
<button
title={isConditionalDebugMode ? 'Debug conditions' : 'Debug tabs'}
class={classNames(
'px-1 text-2xs font-bold w-fit h-full cursor-pointer rounded',
isManuallySelected
? 'hover:bg-red-200 hover:text-red-800'
: 'text-blue-600 hover:bg-blue-300 hover:text-blue-800'
)}
on:click={() => dispatch('triggerInlineEditor')}
on:pointerdown|stopPropagation
>
{#if isManuallySelected}
<div class="whitespace-nowrap">
{#if selected === tabs.length - 1}
@@ -51,45 +78,7 @@
{:else if isSmall}<Bug size={11} />{:else}
{isConditionalDebugMode ? `Debug conditions` : `Debug tabs`}
{/if}
</div>
</button>
</svelte:fragment>
<svelte:fragment slot="items">
{#each tabs ?? [] as { }, index}
<MenuItem
on:click={() => {
$componentControl?.[id]?.setTab?.(index)
selected = index
isManuallySelected = true
}}
>
<div
class={classNames(
'!text-tertiary text-left px-4 py-2 gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
{#if index === tabs.length - 1}
{isConditionalDebugMode ? `Debug default condition` : `Debug tab ${index + 1}`}
{:else}
{`Debug ${isConditionalDebugMode ? 'condition' : 'tab'} ${index + 1}`}
{/if}
</div>
</MenuItem>
{/each}
<MenuItem
on:click={() => {
$componentControl?.[id]?.setTab?.(-1)
selected = null
isManuallySelected = false
}}
>
<div
class={classNames(
'!text-red-600 dark:!text-red-400 text-left px-4 py-2 gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
{`Reset debug mode`}
</div>
</MenuItem>
</svelte:fragment>
</ButtonDropdown>
</button>
</Dropdown>
{/key}
@@ -4,13 +4,9 @@
import { Trash } from 'lucide-svelte'
import { sendUserToast } from '$lib/toast'
import { twMerge } from 'tailwind-merge'
import { createEventDispatcher } from 'svelte'
import NameEditor from './NameEditor.svelte'
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
import { classNames } from '$lib/utils'
import { MenuItem } from '@rgossiaux/svelte-headlessui'
import Dropdown from '$lib/components/DropdownV2.svelte'
import { ResourceService } from '$lib/gen'
export let row: {
@@ -52,6 +48,17 @@
sendUserToast('Component name updated:\n' + name)
}
function getItems() {
return [
{
action: toggleDelete,
icon: Trash,
displayName: 'Delete',
type: 'delete' as const
}
]
}
</script>
<tr>
@@ -69,23 +76,6 @@
</Cell>
<Cell last>
<div class={twMerge('flex flex-row gap-1 justify-end z-[10000]')}>
<button on:pointerdown|stopPropagation>
<ButtonDropdown target="#cc_portal" hasPadding={false}>
<svelte:fragment slot="items">
<MenuItem on:click={toggleDelete}>
<div
class={classNames(
'!text-red-600 flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
)}
>
<Trash size={16} />
Delete
</div>
</MenuItem>
</svelte:fragment>
</ButtonDropdown>
</button>
</div>
<Dropdown items={getItems()} />
</Cell>
</tr>
@@ -69,8 +69,6 @@
getCustomComponents()
</script>
<div id="cc_portal" />
<div class="p-2 flex flex-col items-start w-auto gap-2 relative">
<div class="w-full flex flex-col gap-y-2 pb-8">
<div>
@@ -2,17 +2,12 @@
import { deleteGroup, getGroup, updateGroup } from './groupUtils'
import { workspaceStore } from '$lib/stores'
import Cell from '$lib/components/table/Cell.svelte'
import Dropdown from '$lib/components/DropdownV2.svelte'
import { Trash } from 'lucide-svelte'
import { sendUserToast } from '$lib/toast'
import { twMerge } from 'tailwind-merge'
import { createEventDispatcher } from 'svelte'
import GroupNameEditor from './NameEditor.svelte'
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
import { classNames } from '$lib/utils'
import { MenuItem } from '@rgossiaux/svelte-headlessui'
export let row: {
name: string
path: string
@@ -27,6 +22,17 @@
dispatch('reloadGroups')
sendUserToast('Group deleted:\n' + row.name)
}
function getItems() {
return [
{
action: toggleDelete,
icon: Trash,
displayName: 'Delete',
type: 'delete' as const
}
]
}
</script>
<tr>
@@ -54,23 +60,6 @@
</Cell>
<Cell last>
<div class={twMerge('flex flex-row gap-1 justify-end z-[10000]')}>
<button on:pointerdown|stopPropagation>
<ButtonDropdown target="#group_portal" hasPadding={false}>
<svelte:fragment slot="items">
<MenuItem on:click={toggleDelete}>
<div
class={classNames(
'!text-red-600 flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
)}
>
<Trash size={16} />
Delete
</div>
</MenuItem>
</svelte:fragment>
</ButtonDropdown>
</button>
</div>
<Dropdown items={getItems()} />
</Cell>
</tr>
@@ -14,9 +14,7 @@
import ThemeNameEditor from './ThemeNameEditor.svelte'
import ThemeDrawer from './ThemeDrawer.svelte'
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
import { classNames } from '$lib/utils'
import { MenuItem } from '@rgossiaux/svelte-headlessui'
import Dropdown from '$lib/components/DropdownV2.svelte'
export let previewThemePath: string | undefined = undefined
@@ -134,6 +132,36 @@
}
}
async function getDropdownItems() {
return [
{
action: () => themeDrawer?.openDrawer(),
icon: Code,
displayName: 'View code',
type: 'action' as const
},
{
action: () => fork(row.path),
icon: GitBranch,
displayName: 'Fork',
type: 'action' as const
},
{
action: () => makeDefaultTheme(row.path),
icon: Pin,
displayName: 'Make default',
type: 'action' as const,
disabled: row.path === DEFAULT_THEME
},
{
action: toggleDelete,
icon: Trash,
displayName: 'Delete',
type: 'delete' as const,
disabled: row.path === DEFAULT_THEME
}
]
}
let themeDrawer: ThemeDrawer
</script>
@@ -172,55 +200,7 @@
<Button color="dark" size="xs2" on:click={apply}>Apply</Button>
{/if}
<button on:pointerdown|stopPropagation>
<ButtonDropdown hasPadding={false}>
<svelte:fragment slot="items">
<MenuItem on:click={() => themeDrawer?.openDrawer()}>
<div
class={classNames(
'!text-primary flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
)}
>
<Code size={16} />
View code
</div>
</MenuItem>
<MenuItem on:click={() => fork(row.path)}>
<div
class={classNames(
'!text-primary flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
)}
>
<GitBranch size={16} />
Fork
</div>
</MenuItem>
{#if row.path !== DEFAULT_THEME}
<MenuItem on:click={() => makeDefaultTheme(row.path)}>
<div
class={classNames(
'!text-primary flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
)}
>
<Pin size={16} />
Make default
</div>
</MenuItem>
<MenuItem on:click={toggleDelete}>
<div
class={classNames(
'!text-red-600 flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
)}
>
<Trash size={16} />
Delete
</div>
</MenuItem>
{/if}
</svelte:fragment>
</ButtonDropdown>
</button>
<Dropdown items={getDropdownItems} />
</div>
</Cell>
</tr>
@@ -2,10 +2,9 @@
import { createEventDispatcher } from 'svelte'
import { ButtonType } from './model'
import { twMerge } from 'tailwind-merge'
import ButtonDropdown from './ButtonDropdown.svelte'
import { MenuItem } from '@rgossiaux/svelte-headlessui'
import { classNames, getModifierKey } from '$lib/utils'
import { Loader2 } from 'lucide-svelte'
import Dropdown from '$lib/components/DropdownV2.svelte'
import { getModifierKey, type Item } from '$lib/utils'
import { Loader2, ChevronDown } from 'lucide-svelte'
export let size: ButtonType.Size = 'md'
export let spacingSize: ButtonType.Size = size
@@ -30,7 +29,6 @@
export let title: string | undefined = undefined
export let style: string = ''
export let download: string | undefined = undefined
export let portalTarget: string | undefined = undefined
export let startIcon: ButtonType.Icon | undefined = undefined
export let endIcon: ButtonType.Icon | undefined = undefined
export let shortCut:
@@ -45,12 +43,14 @@
}
export let dropdownItems: MenuItem[] | (() => MenuItem[]) | undefined = undefined
function computeDropdowns(): MenuItem[] | undefined {
if (typeof dropdownItems === 'function') {
return dropdownItems()
} else {
return dropdownItems
}
function computeDropdowns(menuItems: MenuItem[] | (() => MenuItem[])): Item[] {
const items = typeof menuItems === 'function' ? menuItems() : menuItems
return items.map((item) => ({
displayName: item.label,
action: item.onClick ? (e) => item.onClick?.(e) : undefined,
icon: item.icon,
href: item.href
}))
}
export function focus() {
@@ -230,32 +230,19 @@
{/if}
{#if dropdownItems && dropdownItems.length > 0}
<div
class={twMerge(
buttonClass,
'rounded-md m-0 p-0 h-auto !w-10',
variant === 'border' ? 'border-0 border-r border-y ' : 'border-0',
'rounded-r-md !rounded-l-none'
)}
>
<ButtonDropdown target={portalTarget}>
<svelte:fragment slot="items">
{#each computeDropdowns() ?? [] as item}
<MenuItem on:click={item.onClick} href={item.href}>
<div
class={classNames(
'!text-secondary text-left px-4 py-2 gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
{#if item.icon}
<svelte:component this={item.icon} class="w-4 h-4" size={lucideIconSize} />
{/if}
{item.label}
</div>
</MenuItem>
{/each}
</svelte:fragment>
</ButtonDropdown>
</div>
<Dropdown items={computeDropdowns(dropdownItems)} class="h-full">
<svelte:fragment slot="buttonReplacement">
<div
class={twMerge(
buttonClass,
'rounded-md m-0 p-0 !w-10 center-center h-full',
variant === 'border' ? 'border-0 border-r border-y ' : 'border-0',
'rounded-r-md !rounded-l-none'
)}
>
<ChevronDown class="w-5 h-5" />
</div>
</svelte:fragment>
</Dropdown>
{/if}
</div>
@@ -1,5 +1,4 @@
<script lang="ts">
import Portal from '$lib/components/Portal.svelte'
import { melt, createSync } from '@melt-ui/svelte'
import type { Placement } from '@floating-ui/core'
import { pointerDownOutside } from '$lib/utils'
@@ -65,20 +64,16 @@
<slot name="trigger" trigger={$trigger} />
</button>
<Portal name="menu-v3">
<div class="z-[6000]" use:melt={$menuElement} data-menu>
<div
class={twMerge(
'border w-56 origin-top-right rounded-md shadow-md focus:outline-none overflow-y-auto',
lightMode ? 'bg-surface-inverse' : 'bg-surface',
invisible ? 'opacity-0' : ''
)}
style="max-height: {maxHeight}px;"
>
<div class="my-1">
<slot item={$item} />
</div>
</div>
</div>
</Portal>
<div
class={twMerge(
'z-[6000] border w-56 origin-top-right rounded-md shadow-md focus:outline-none overflow-y-auto py-1',
lightMode ? 'bg-surface-inverse' : 'bg-surface',
invisible ? 'opacity-0' : ''
)}
style="max-height: {maxHeight}px;"
use:melt={$menuElement}
data-menu
>
<slot item={$item} />
</div>
</div>
@@ -16,7 +16,7 @@
<Tutorial
bind:this={tutorial}
index={0}
index={4}
name="error-handler"
tainted={false}
on:error
+11
View File
@@ -1112,3 +1112,14 @@ export function conditionalMelt(node: HTMLElement, meltElement: any) {
}
return { destroy: () => {} }
}
export type Item = {
displayName: string
action?: (e: CustomEvent<any>) => void
icon?: any
iconColor?: string
href?: string
disabled?: boolean
type?: 'action' | 'delete'
hide?: boolean | undefined
}
@@ -66,7 +66,6 @@
Skeleton,
Tab,
Alert,
MenuItem,
DrawerContent
} from '$lib/components/common'
import FlowMetadata from '$lib/components/FlowMetadata.svelte'
@@ -79,7 +78,7 @@
import { goto } from '$lib/navigation'
import { sendUserToast } from '$lib/toast'
import { forLater } from '$lib/forLater'
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
import Dropdown from '$lib/components/DropdownV2.svelte'
import PersistentScriptDrawer from '$lib/components/PersistentScriptDrawer.svelte'
import Portal from '$lib/components/Portal.svelte'
@@ -442,20 +441,21 @@
{@const runsHref = `/runs/${job?.script_path}${!isScript ? '?jobKind=flow' : ''}`}
<div class="flex gap-2 items-center">
{#if job && 'deleted' in job && !job?.deleted && ($superadmin || ($userStore?.is_admin ?? false))}
<ButtonDropdown target="body" hasPadding={false}>
<Dropdown
items={[
{
displayName: 'Delete result, logs and args (admin only)',
action: () => {
job?.id && deleteCompletedJob(job.id)
},
type: 'delete'
}
]}
>
<svelte:fragment slot="buttonReplacement">
<Button nonCaptureEvent variant="border" size="sm" startIcon={{ icon: Trash }} />
</svelte:fragment>
<svelte:fragment slot="items">
<MenuItem
on:click={() => {
job?.id && deleteCompletedJob(job.id)
}}
>
<span class="text-red-600"> Delete result, logs and args (admin only) </span>
</MenuItem>
</svelte:fragment>
</ButtonDropdown>
</Dropdown>
{#if job?.job_kind === 'script' || job?.job_kind === 'flow'}
<Button
href={runsHref}
@@ -474,9 +474,19 @@
{@const stem = `/${job?.job_kind}s`}
{@const isScript = job?.job_kind === 'script'}
{@const viewHref = `${stem}/get/${isScript ? job?.script_hash : job?.script_path}`}
{#if (job?.job_kind == 'flow' || isFlowPreview(job?.job_kind)) && job?.['running'] && job?.parent_job == undefined}
{#if ((job?.job_kind == 'flow' || isFlowPreview(job?.job_kind)) && job?.['running'] && job?.parent_job == undefined) || true}
<div class="inline">
<ButtonDropdown hasPadding={false}>
<Dropdown
items={[
{
displayName: 'Show Flow Debug Info',
action: () => {
debugInfo()
}
}
]}
class="h-auto"
>
<svelte:fragment slot="buttonReplacement">
<Button nonCaptureEvent size="xs" color="light">
<div class="flex flex-row items-center">
@@ -484,10 +494,7 @@
</div>
</Button>
</svelte:fragment>
<svelte:fragment slot="items">
<MenuItem on:click={debugInfo}>Show Flow Debug Info</MenuItem>
</svelte:fragment>
</ButtonDropdown>
</Dropdown>
</div>
{/if}
{#if isFlowPreview(job?.job_kind) || isScriptPreview(job?.job_kind)}