Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(frontend): improve table action UX #4056

Merged
merged 2 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading