Skip to content

Commit

Permalink
feat(frontend): improve table action UX (#4056)
Browse files Browse the repository at this point in the history
* feat(frontend): improve table action UX

* feat(frontend): improve table action UX
  • Loading branch information
fatonramadani authored Jul 10, 2024
1 parent c853db4 commit a6eeb68
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 41 deletions.
22 changes: 22 additions & 0 deletions frontend/src/lib/components/apps/editor/appUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1004,3 +1004,25 @@ export function maxHeight(

return Math.max(maxRowPerGrid, maxRows)
}

export function isTableAction(id: string, app: App): boolean {
const [tableId, actionId] = id.split('_')

if (!tableId || !actionId) {
return false
}

const table = findGridItem(app, tableId)
if (
!table ||
(table.data.type !== 'tablecomponent' &&
table.data.type !== 'aggridcomponent' &&
table.data.type !== 'aggridcomponentee' &&
table.data.type !== 'dbexplorercomponent' &&
table.data.type !== 'aggridinfinitecomponent' &&
table.data.type !== 'aggridinfinitecomponentee')
) {
return false
}
return true
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
import { ccomponents, components } from '../component'
import CssProperty from '../componentsPanel/CssProperty.svelte'
import GridTab from './GridTab.svelte'
import { deleteGridItem } from '../appUtils'
import { deleteGridItem, isTableAction } from '../appUtils'
import GridPane from './GridPane.svelte'
import { slide } from 'svelte/transition'
import { push } from '$lib/history'
import StylePanel from './StylePanel.svelte'
import { ChevronLeft, ArrowBigUp } from 'lucide-svelte'
import { ChevronLeft, ArrowBigUp, ArrowLeft } from 'lucide-svelte'
import GridCondition from './GridCondition.svelte'
import { isTriggerable } from './script/utils'
import { inferDeps } from '../appUtilsInfer'
Expand All @@ -42,6 +42,9 @@
import ContextVariables from './ContextVariables.svelte'
import EventHandlers from './EventHandlers.svelte'
import GridNavbar from './GridNavbar.svelte'
import Badge from '$lib/components/common/badge/Badge.svelte'
import { twMerge } from 'tailwind-merge'
import Popover from '$lib/components/Popover.svelte'
export let componentSettings: { item: GridItem; parent: string | undefined } | undefined =
undefined
Expand Down Expand Up @@ -175,6 +178,47 @@

<svelte:window on:keydown={keydown} />

{#if componentSettings?.item?.id && isTableAction(componentSettings?.item?.id, $app)}
<div
class="flex items-center px-3 py-2 bg-surface border-b text-xs font-semibold gap-2 justify-between"
>
<div class="flex flex-row items-center gap-2">
<Popover>
<svelte:fragment slot="text">
<div class="flex flex-row gap-1"> Back to table component </div>
</svelte:fragment>
<Button
iconOnly
startIcon={{
icon: ArrowLeft
}}
size="xs"
btnClasses={twMerge(
'p-1 text-gray-300 hover:!text-gray-600 dark:text-gray-500 dark:hover:!text-gray-200 bg-transparent'
)}
on:click={() => {
const tableId = componentSettings?.item?.id?.split?.('_')?.[0]

if (tableId) {
$selectedComponent = [tableId]
}
}}
color="light"
/>
</Popover>

<div class="flex flex-row gap-2 items-center">
Table action of table
<Badge color="indigo">{componentSettings?.item?.id.split('_')[0]}</Badge>
</div>
</div>

<DocLink
docLink="https://www.windmill.dev/docs/apps/app_configuration_settings/aggrid_table#table-actions"
/>
</div>
{/if}

{#if componentSettings?.item?.data}
{@const component = componentSettings.item.data}
<div class="flex justify-between items-center px-3 py-1 bg-surface-secondary">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
import { Badge } from '$lib/components/common'
import Button from '$lib/components/common/button/Button.svelte'
import { getNextId } from '$lib/components/flows/idUtils'
import { classNames } from '$lib/utils'
import { classNames, generateRandomString } from '$lib/utils'
import { getContext, onMount } from 'svelte'
import type { AppViewerContext, BaseAppComponent } from '../../types'
import { appComponentFromType } from '../appUtils'
import type { ButtonComponent, CheckboxComponent, SelectComponent } from '../component'
import PanelSection from './common/PanelSection.svelte'
import { Inspect, List, ToggleRightIcon, Trash } from 'lucide-svelte'
import { GripVertical, Inspect, List, ToggleRightIcon } from 'lucide-svelte'
import { dragHandle, dragHandleZone } from '@windmill-labs/svelte-dnd-action'
import CloseButton from '$lib/components/common/CloseButton.svelte'
import { flip } from 'svelte/animate'
export let components:
| (BaseAppComponent & (ButtonComponent | CheckboxComponent | SelectComponent))[]
Expand All @@ -21,9 +24,16 @@
}
})
let items =
components?.map((tab, index) => {
return { value: tab, id: generateRandomString(), originalIndex: index }
}) ?? []
$: components = items.map((item) => item.value)
export let id: string
const { selectedComponent, app, errorByComponent } =
const { selectedComponent, app, errorByComponent, hoverStore } =
getContext<AppViewerContext>('AppViewerContext')
function addComponent(typ: 'buttoncomponent' | 'checkboxcomponent' | 'selectcomponent') {
Expand All @@ -37,11 +47,21 @@
...appComponentFromType(typ)(`${id}_${actionId}`),
recomputeIds: []
}
items = [
...items,
{
value: newComponent,
id: generateRandomString(),
originalIndex: items.length
}
]
components = [...components, newComponent]
$app = $app
}
function deleteComponent(cid: string) {
function deleteComponent(cid: string, index: number) {
if (!components) {
return
}
Expand All @@ -51,6 +71,20 @@
$selectedComponent = [id]
$app = $app
// Remove the corresponding item from the items array
items = items.filter((item) => item.originalIndex !== index)
}
function handleConsider(e: CustomEvent): void {
const { items: newItems } = e.detail
items = newItems
}
function handleFinalize(e: CustomEvent): void {
const { items: newItems } = e.detail
items = newItems
}
</script>

Expand All @@ -59,43 +93,61 @@
{#if components.length == 0}
<span class="text-xs text-tertiary">No action buttons</span>
{/if}
{#each components as component}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class={classNames(
'w-full text-xs font-bold gap-1 truncate py-1.5 px-2 cursor-pointer transition-all justify-between flex items-center border border-gray-3 rounded-md',
'bg-surface hover:bg-surface-hover focus:border-primary text-secondary',
$selectedComponent?.includes(component.id) ? 'outline outline-blue-500 bg-red-400' : ''
)}
on:click={() => {
$selectedComponent = [component.id]
<div class="w-full flex gap-2 flex-col mt-2">
<section
use:dragHandleZone={{
items,
flipDurationMs: 200,
dropTargetStyle: {}
}}
on:keypress
on:consider={handleConsider}
on:finalize={handleFinalize}
>
<Badge color="dark-indigo">
{component.id}
</Badge>

<div>
{#if component.type == 'buttoncomponent'}
Button
{:else if component.type == 'selectcomponent'}
Select
{:else if component.type == 'checkboxcomponent'}
Toggle
{/if}
</div>
<div>
<Button
variant="border"
color="red"
on:click={() => deleteComponent(component.id)}
startIcon={{ icon: Trash }}
iconOnly
/>
</div>
</div>
{/each}
{#each items as item, index (item.id)}
{@const component = items[index].value}

<div animate:flip={{ duration: 200 }} class="flex flex-row gap-2 items-center mb-2">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<div
class={classNames(
'w-full text-xs text-semibold truncate py-1.5 px-2 cursor-pointer justify-between flex items-center border rounded-md',
'bg-surface hover:bg-surface-hover focus:border-primary text-secondary'
)}
on:click={() => {
$selectedComponent = [component.id]
}}
on:mouseover={() => {
$hoverStore = component.id
}}
on:keypress
>
<div class="flex flex-row gap-2 items-center">
<Badge color="dark-indigo">
{component.id}
</Badge>

<div>
{#if component.type == 'buttoncomponent'}
Button
{:else if component.type == 'selectcomponent'}
Select
{:else if component.type == 'checkboxcomponent'}
Toggle
{/if}
</div>
</div>
<div class="flex flex-row items-center gap-1">
<CloseButton small on:close={() => deleteComponent(component.id, index)} />
</div>
</div>
<div use:dragHandle class="handle w-4 h-4" aria-label="drag-handle">
<GripVertical size={16} />
</div>
</div>
{/each}
</section>
</div>
<div class="w-full flex gap-2">
<Button
btnClasses="gap-1 flex items-center text-sm text-tertiary"
Expand Down

0 comments on commit a6eeb68

Please sign in to comment.