Add row actions to tables in data section

This commit is contained in:
Andrew Kingston 2024-09-02 08:45:49 +01:00
parent 9fc837b04a
commit c5e27b860f
No known key found for this signature in database
6 changed files with 93 additions and 29 deletions

View File

@ -15,7 +15,6 @@
let anchor
let dropdown
let timeout
let open
// This is needed because display: contents is considered "invisible".
// It should only ever be an action button, so should be fine.
@ -23,7 +22,7 @@
anchor = node.firstChild
}
export const show = () => {
export const show = e => {
cancelHide()
dropdown.show()
}
@ -64,11 +63,10 @@
on:mouseenter={openOnHover ? show : null}
on:mouseleave={openOnHover ? queueHide : null}
>
<slot name="control" {open} />
<slot name="control" />
</div>
<Popover
bind:this={dropdown}
bind:open
{anchor}
{align}
{portalTarget}

View File

@ -169,7 +169,7 @@ export default function positionDropdown(element, opts) {
align === "left-context-menu"
) {
applyYStrategy(Strategies.StartToStart)
styles.top -= 4 // Manual adjustment for action menu padding
styles.top -= 5 // Manual adjustment for action menu padding
} else {
applyYStrategy(Strategies.StartToEnd)
}

View File

@ -1,5 +1,5 @@
<script>
import { Banner } from "@budibase/bbui"
import { Banner, Menu, MenuItem, Popover } from "@budibase/bbui"
import { datasources, tables, integrations, appStore } from "stores/builder"
import { themeStore, admin } from "stores/portal"
import { TableNames } from "constants"
@ -28,7 +28,12 @@
status: { displayName: "Status", disabled: true },
}
let rowActions = []
let generateButton
let rowActionPopover
let rowActionRow
let rowActionAnchor
let refreshRow
$: autoColumnStatus = verifyAutocolumns($tables?.selected)
$: duplicates = Object.values(autoColumnStatus).reduce((acc, status) => {
@ -53,6 +58,20 @@
$: relationshipsEnabled = relationshipSupport(tableDatasource)
$: currentTheme = $themeStore?.theme
$: darkMode = !currentTheme.includes("light")
$: buttons = [
{
text: "Actions",
type: "cta",
icon: "ChevronDown",
onClick: async (e, row, refresh) => {
rowActionRow = row
rowActionAnchor = e.currentTarget
rowActionPopover.show()
refreshRow = refresh
},
},
]
$: fetchRowActions(id)
const relationshipSupport = datasource => {
const integration = $integrations[datasource?.source]
@ -82,6 +101,25 @@
return acc
}, {})
}
const fetchRowActions = async tableId => {
if (!tableId) {
rowActions = []
return
}
const res = await API.rowActions.fetch(tableId)
rowActions = Object.values(res || {})
}
const runRowAction = async action => {
await API.rowActions.trigger({
rowActionId: action.id,
tableId: id,
rowId: rowActionRow._id,
})
await refreshRow()
rowActionPopover.hide()
}
</script>
{#if $tables?.selected?.name}
@ -105,6 +143,7 @@
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
showAvatars={false}
isCloud={$admin.cloud}
buttons={rowActions.length ? buttons : null}
on:updatedatasource={handleGridTableUpdate}
>
<!-- Controls -->
@ -153,6 +192,19 @@
<i>Create your first table to start building</i>
{/if}
<Popover
bind:this={rowActionPopover}
align="right"
anchor={rowActionAnchor}
offset={5}
>
<Menu>
{#each rowActions as action}
<MenuItem on:click={() => runRowAction(action)}>{action.name}</MenuItem>
{/each}
</Menu>
</Popover>
<style>
i {
font-size: var(--font-size-m);

View File

@ -115,13 +115,15 @@
text: settings.text,
type: settings.type,
icon: settings.icon,
onClick: async row => {
onClick: async (_, row, refresh) => {
// Create a fake, ephemeral context to run the buttons actions with
const id = get(component).id
const gridContext = createContextStore(context)
gridContext.actions.provideData(id, row)
const fn = enrichButtonActions(settings.onClick, get(gridContext))
return await fn?.({ row })
const res = await fn?.({ row })
await refresh()
return res
},
}))
}

View File

@ -55,9 +55,12 @@ export const buildRowActionEndpoints = API => ({
* @param tableId the ID of the table
* @param rowActionId the ID of the row action to trigger
*/
trigger: async ({ tableId, rowActionId }) => {
trigger: async ({ tableId, rowActionId, rowId }) => {
return await API.post({
url: `/api/tables/${tableId}/actions/${rowActionId}/trigger`,
body: {
rowId,
},
})
},
})

View File

@ -18,6 +18,7 @@
isDragging,
buttonColumnWidth,
showVScrollbar,
showHScrollbar,
dispatch,
} = getContext("grid")
@ -32,10 +33,12 @@
$: gridEnd = $width - $buttonColumnWidth - 1
$: left = Math.min(columnEnd, gridEnd)
const handleClick = async (button, row) => {
await button.onClick?.(rows.actions.cleanRow(row))
// Refresh the row in case it changed
await rows.actions.refreshRow(row._id)
const handleClick = async (e, button, row) => {
await button.onClick?.(
e,
rows.actions.cleanRow(row),
async () => await rows.actions.refreshRow(row._id)
)
}
onMount(() => {
@ -72,23 +75,29 @@
highlighted={rowHovered || rowFocused}
metadata={row.__metadata?.row}
>
<div class="buttons" class:offset={$showVScrollbar}>
<div
class="buttons"
class:offset={$showVScrollbar && $showHScrollbar}
>
{#each buttons as button}
<Button
newStyles
size="S"
cta={button.type === "cta"}
primary={button.type === "primary"}
secondary={button.type === "secondary"}
warning={button.type === "warning"}
overBackground={button.type === "overBackground"}
on:click={() => handleClick(button, row)}
>
{#if button.icon}
<i class="{button.icon} S" />
{/if}
{button.text || "Button"}
</Button>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span on:click={e => handleClick(e, button, row)}>
<Button
newStyles
size="S"
icon={button.icon}
cta={button.type === "cta"}
primary={button.type === "primary"}
secondary={button.type === "secondary"}
warning={button.type === "warning"}
overBackground={button.type === "overBackground"}
>
{#if button.icon}
<i class="{button.icon} S" />
{/if}
{button.text || "Button"}
</Button>
</span>
{/each}
</div>
</GridCell>