feat(frontend): Add actions to Database Studio (#3556)

* feat(frontend): Add actions to Database Studio

* feat(frontend): Add actions to Database Studio
This commit is contained in:
Faton Ramadani
2024-04-15 14:07:35 +02:00
committed by GitHub
parent 0b487426c8
commit 863550a91d
19 changed files with 174 additions and 68 deletions
@@ -6,7 +6,7 @@
OneOfConfiguration,
RichConfigurations
} from '../../../types'
import { components } from '$lib/components/apps/editor/component'
import { components, type TableAction } from '$lib/components/apps/editor/component'
import ResolveConfig from '../../helpers/ResolveConfig.svelte'
import { findGridItem, initConfig, initOutput } from '$lib/components/apps/editor/appUtils'
import {
@@ -45,6 +45,7 @@
export let customCss: ComponentCustomCSS<'dbexplorercomponent'> | undefined = undefined
export let render: boolean
export let initializing: boolean = true
export let actions: TableAction[] = []
$: table = resolvedConfig.type.configuration?.[resolvedConfig.type?.selected]?.table as
| string
@@ -173,6 +174,7 @@
selectedRow: {},
selectedRows: [] as any[],
result: [] as any[],
inputs: {},
loading: false,
page: 0,
newChange: { row: 0, column: '', value: undefined },
@@ -598,6 +600,7 @@
bind:this={deleteRow}
/>
{/if}
<UpdateCell {id} bind:this={updateCell} />
{#if render}
<DbExplorerCount
@@ -668,6 +671,7 @@
containerHeight={componentContainerHeight - (buttonContainerHeight ?? 0)}
on:update={onUpdate}
on:delete={onDelete}
{actions}
/>
{/key}
{/if}
@@ -268,7 +268,8 @@ export function getSelectInput(
}
},
type: 'runnable',
fieldType: 'object'
fieldType: 'object',
hideRefreshButton: false
}
return getQuery
@@ -4,7 +4,7 @@
import { createEventDispatcher, getContext } from 'svelte'
import type { AppViewerContext, ComponentCustomCSS } from '../../../types'
import type { components } from '$lib/components/apps/editor/component'
import type { TableAction, components } from '$lib/components/apps/editor/component'
import Alert from '$lib/components/common/alert/Alert.svelte'
import { deepEqual } from 'fast-equals'
@@ -19,6 +19,7 @@
import { cellRendererFactory } from './utils'
import { Trash2 } from 'lucide-svelte'
import type { ColumnDef } from '../dbtable/utils'
import AppAggridTableActions from './AppAggridTableActions.svelte'
// import 'ag-grid-community/dist/styles/ag-theme-alpine-dark.css'
export let id: string
@@ -31,9 +32,11 @@
export let state: any = undefined
export let outputs: Record<string, Output<any>>
export let allowDelete: boolean
export let actions: TableAction[] = []
let inputs = {}
const { app, selectedComponent, componentControl, darkMode } =
getContext<AppViewerContext>('AppViewerContext')
const context = getContext<AppViewerContext>('AppViewerContext')
const { app, selectedComponent, componentControl, darkMode } = context
let css = initCss($app.css?.aggridcomponent, customCss)
@@ -125,6 +128,57 @@
$: eGui && mountGrid()
function refreshActions(actions: TableAction[]) {
if (!deepEqual(actions, lastActions)) {
lastActions = [...actions]
updateOptions()
}
}
let lastActions: TableAction[] | undefined = undefined
$: actions && refreshActions(actions)
const tableActionsFactory = cellRendererFactory((c, p) => {
const rowIndex = p.node.rowIndex ?? 0
const row = p.data
new AppAggridTableActions({
target: c.eGui,
props: {
id: id,
actions,
rowIndex,
row,
render: true,
wrapActions: resolvedConfig.wrapActions,
onSet: (id, value) => {
if (!inputs[id]) {
inputs[id] = { [rowIndex]: value }
} else {
inputs[id] = { ...inputs[id], [rowIndex]: value }
}
outputs?.inputs.set(inputs, true)
},
onRemove: (id) => {
if (inputs?.[id] == undefined) {
return
}
delete inputs[id][rowIndex]
inputs[id] = { ...inputs[id] }
if (Object.keys(inputs?.[id] ?? {}).length == 0) {
delete inputs[id]
inputs = { ...inputs }
}
outputs?.inputs.set(inputs, true)
}
},
context: new Map([['AppViewerContext', context]])
})
})
function transformColumnDefs(columnDefs: any[]) {
const { isValid, errors } = validateColumnDefs(columnDefs)
@@ -134,6 +188,7 @@
}
let r = columnDefs?.filter((x) => x && !x.ignored) ?? []
if (allowDelete) {
r.push({
field: 'delete',
@@ -164,6 +219,19 @@
width: 100
})
}
if (actions && actions.length > 0) {
r.push({
headerName: 'Actions',
cellRenderer: tableActionsFactory,
autoHeight: true,
cellStyle: { textAlign: 'center' },
cellClass: 'grid-cell-centered',
lockPosition: 'right',
...(!resolvedConfig?.wrapActions ? { minWidth: 130 * actions?.length } : {})
})
}
return r
}
@@ -204,6 +272,13 @@
cacheBlockSize: 100,
cacheOverflowSize: 10,
maxBlocksInCache: 20,
...(resolvedConfig?.wrapActions
? {
rowHeight: Math.max(44, actions.length * 48)
}
: {
rowHeight: 44
}),
suppressColumnMoveAnimation: true,
rowSelection: resolvedConfig?.multipleSelectable ? 'multiple' : 'single',
rowMultiSelectWithClick: resolvedConfig?.multipleSelectable
@@ -288,6 +363,13 @@
editable: resolvedConfig?.allEditable,
onCellValueChanged
},
...(resolvedConfig?.wrapActions
? {
rowHeight: Math.max(44, actions.length * 48)
}
: {
rowHeight: 44
}),
rowSelection: resolvedConfig?.multipleSelectable ? 'multiple' : 'single',
rowMultiSelectWithClick: resolvedConfig?.multipleSelectable
? resolvedConfig.rowMultiselectWithClick
@@ -1,7 +1,7 @@
<script lang="ts">
import { GridApi, createGrid } from 'ag-grid-community'
import { isObject, sendUserToast } from '$lib/utils'
import { SvelteComponent, getContext, onDestroy } from 'svelte'
import { getContext, onDestroy } from 'svelte'
import type { AppInput } from '../../../inputType'
import type {
AppViewerContext,
@@ -27,6 +27,7 @@
import ResolveStyle from '../../helpers/ResolveStyle.svelte'
import AppAggridTableActions from './AppAggridTableActions.svelte'
import { cellRendererFactory } from './utils'
// import 'ag-grid-community/dist/styles/ag-theme-alpine-dark.css'
@@ -167,26 +168,10 @@
$: loaded && eGui && mountGrid()
const cachedDivs = new Map<
number,
{
div: HTMLDivElement
svelteComponent: SvelteComponent
actions: TableAction[]
}
>()
function refreshActions(actions: TableAction[]) {
if (!deepEqual(actions, lastActions)) {
lastActions = [...actions]
cachedDivs.forEach((cachedDiv) => {
cachedDiv.svelteComponent.$destroy()
cachedDiv.div.remove()
})
cachedDivs.clear()
updateOptions()
}
}
@@ -196,21 +181,12 @@
let inputs = {}
function actionRenderer(params) {
const { rowIndex, data: row } = params
if (rowIndex === -1 || actions == undefined || actions?.length == 0) {
return null
}
const tableActionsFactory = cellRendererFactory((c, p) => {
const rowIndex = p.node.rowIndex ?? 0
const row = p.data
if (cachedDivs.has(rowIndex)) {
return cachedDivs.get(rowIndex)?.div
}
const div = document.createElement('div')
div.classList.add('flex', 'flex-row', 'items-center', 'w-full', 'h-full')
const svelteComponent = new AppAggridTableActions({
target: div,
new AppAggridTableActions({
target: c.eGui,
props: {
id: id,
actions,
@@ -242,15 +218,7 @@
},
context: new Map([['AppViewerContext', context]])
})
cachedDivs.set(rowIndex, {
div,
actions,
svelteComponent
})
return div
}
})
function mountGrid() {
if (eGui) {
@@ -264,7 +232,7 @@
if (actions && actions.length > 0) {
columnDefs.push({
headerName: 'Actions',
cellRenderer: actionRenderer,
cellRenderer: tableActionsFactory,
autoHeight: true,
cellStyle: { textAlign: 'center' },
cellClass: 'grid-cell-centered',
@@ -388,7 +356,7 @@
if (actions && actions.length > 0) {
columnDefs.push({
headerName: 'Actions',
cellRenderer: actionRenderer,
cellRenderer: tableActionsFactory,
autoHeight: true,
cellStyle: { textAlign: 'center' },
cellClass: 'grid-cell-centered',
@@ -32,8 +32,8 @@
<RowWrapper value={row} index={rowIndex} {onSet} {onRemove}>
<div
class={twMerge(
'flex flex-row justify-center items-center gap-4 h-full px-4 py-1',
!wrapActions ? 'flex-wrap' : ''
'flex flex-row justify-center items-center gap-4 h-full px-4 py-1 w-full',
wrapActions ? 'flex-wrap' : ''
)}
>
{#each actions as action, actionIndex}
@@ -31,7 +31,6 @@ export abstract class AbstractCellRenderer implements ICellRendererComp {
refresh(params: ICellRendererParams) {
this.value = params.value
this.eGui.innerHTML = ''
return true
}
@@ -185,7 +185,9 @@
r.push(...c.actionButtons.map((x) => ({ input: x.componentInput, id: x.id })))
}
if (
(c.type === 'aggridcomponent' || c.type === 'aggridcomponentee') &&
(c.type === 'aggridcomponent' ||
c.type === 'aggridcomponentee' ||
c.type === 'dbexplorercomponent') &&
Array.isArray(c.actions)
) {
r.push(...c.actions.map((x) => ({ input: x.componentInput, id: x.id })))
@@ -29,7 +29,7 @@
{/each}
</div>
</div>
{:else if gridItem?.data?.type === 'aggridcomponent' || gridItem?.data?.type === 'aggridcomponentee'}
{:else if gridItem?.data?.type === 'aggridcomponent' || gridItem?.data?.type === 'aggridcomponentee' || gridItem?.data?.type === 'dbexplorercomponent'}
<div>
<AppComponentInput bind:component={gridItem.data} {resourceOnly} />
<div class="ml-4 mt-4">
@@ -33,7 +33,11 @@
return { item: { data: tableAction, id: tableAction.id }, parent: x.data.id }
}
}
} else if (x?.data?.type === 'aggridcomponent' || x?.data?.type === 'aggridcomponentee') {
} else if (
x?.data?.type === 'aggridcomponent' ||
x?.data?.type === 'aggridcomponentee' ||
x?.data?.type === 'dbexplorercomponent'
) {
if (x?.data?.actions) {
const tableAction = x.data.actions.find((x) => x.id === id)
if (tableAction) {
@@ -90,7 +94,9 @@
}
if (
(parent.data.type === 'aggridcomponent' || parent.data.type === 'aggridcomponentee') &&
(parent.data.type === 'aggridcomponent' ||
parent.data.type === 'aggridcomponentee' ||
parent.data.type === 'dbexplorercomponent') &&
Array.isArray(parent.data.actions)
) {
parent.data.actions = parent.data.actions.filter(
@@ -73,7 +73,9 @@ export function dfs(
} else if (item.data.type == 'menucomponent' && item.data.menuItems.find((x) => id == x.id)) {
return [item.id, id]
} else if (
(item.data.type == 'aggridcomponent' || item.data.type == 'aggridcomponentee') &&
(item.data.type == 'aggridcomponent' ||
item.data.type == 'aggridcomponentee' ||
item.data.type == 'dbexplorercomponent') &&
item.data.actions?.find((x) => id == x.id)
) {
return [item.id, id]
@@ -171,8 +173,10 @@ export function allsubIds(app: App, parentId: string): string[] {
subIds.push(...item.data.actionButtons?.map((x) => x.id))
}
if (
item.data.type === 'aggridcomponent' ||
(item.data.type === 'aggridcomponentee' && Array.isArray(item.data.actions))
(item.data.type === 'aggridcomponent' ||
item.data.type === 'aggridcomponentee' ||
item.data.type === 'dbexplorercomponent') &&
Array.isArray(item.data.actions)
) {
subIds.push(...item.data.actions?.map((x) => x.id))
}
@@ -194,7 +198,9 @@ export function getNextGridItemId(app: App): string {
if (x.data.type === 'tablecomponent') {
return [x.id, ...x.data.actionButtons.map((x) => x.id)]
} else if (
(x.data.type === 'aggridcomponent' || x.data.type === 'aggridcomponentee') &&
(x.data.type === 'aggridcomponent' ||
x.data.type === 'aggridcomponentee' ||
x.data.type === 'dbexplorercomponent') &&
Array.isArray(x.data.actions)
) {
return [x.id, ...x.data.actions.map((x) => x.id)]
@@ -436,7 +442,11 @@ export function copyComponent(
id: x.id.replace(`${item.id}_`, `${id}_`)
})) ?? []
}
} else if (item.data.type === 'aggridcomponent' || item.data.type === 'aggridcomponentee') {
} else if (
item.data.type === 'aggridcomponent' ||
item.data.type === 'aggridcomponentee' ||
item.data.type === 'dbexplorercomponent'
) {
return {
...item.data,
id,
@@ -489,7 +499,11 @@ export function getAllSubgridsAndComponentIds(
components.push(...component.actionButtons?.map((x) => x.id))
}
if (component.type === 'aggridcomponent' || component.type === 'aggridcomponentee') {
if (
component.type === 'aggridcomponent' ||
component.type === 'aggridcomponentee' ||
component.type === 'dbexplorercomponent'
) {
components.push(...(component.actions?.map((x) => x.id) ?? []))
}
@@ -521,7 +535,9 @@ export function getAllGridItems(app: App): GridItem[] {
if (x?.data?.type === 'tablecomponent') {
return [x, ...x?.data?.actionButtons?.map((x) => ({ data: x, id: x.id }))]
} else if (
(x?.data?.type === 'aggridcomponent' || x?.data?.type === 'aggridcomponentee') &&
(x?.data?.type === 'aggridcomponent' ||
x?.data?.type === 'aggridcomponentee' ||
x?.data?.type === 'dbexplorercomponent') &&
Array.isArray(x?.data?.actions)
) {
return [x, ...x?.data?.actions?.map((x) => ({ data: x, id: x.id }))]
@@ -341,6 +341,7 @@
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
actions={component.actions ?? []}
bind:initializing
{render}
/>
@@ -63,7 +63,10 @@
if (left.data.type === 'tablecomponent' && left.data.actionButtons.length >= 1) {
$selectedComponent = [left.data.actionButtons[left.data.actionButtons.length - 1].id]
} else if (
(left.data.type === 'aggridcomponent' || left.data.type === 'aggridcomponentee') && Array.isArray(left.data.actions) &&
(left.data.type === 'aggridcomponent' ||
left.data.type === 'aggridcomponentee' ||
left.data.type === 'dbexplorercomponent') &&
Array.isArray(left.data.actions) &&
left.data.actions.length >= 1
) {
$selectedComponent = [left.data.actions[left.data.actions.length - 1].id]
@@ -213,6 +213,7 @@ export type MenuComponent = BaseComponent<'menucomponent'> & {
export type DBExplorerComponent = BaseComponent<'dbexplorercomponent'> & {
columns: RichConfiguration
actions: TableAction[]
}
export type S3FileInputComponent = BaseComponent<'s3fileinputcomponent'>
@@ -3533,6 +3534,13 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
fieldType: 'boolean',
value: false,
tooltip: 'Hide the search bar'
},
wrapActions: {
type: 'static',
fieldType: 'boolean',
value: false,
tooltip:
'When true, actions will wrap to the next line. Otherwise, the column will grow to fit the actions.'
}
},
componentInput: undefined
@@ -90,7 +90,11 @@
}
}
if (item?.data.type == 'aggridcomponent' || item?.data.type == 'aggridcomponentee') {
if (
item?.data.type == 'aggridcomponent' ||
item?.data.type == 'aggridcomponentee' ||
item?.data.type == 'dbexplorercomponent'
) {
for (let c of item.data.actions ?? []) {
let old = c.id
c.id = c.id.replace(id + '_', newId + '_')
@@ -120,7 +124,9 @@
}
if (
(data.type == 'aggridcomponent' || data.type == 'aggridcomponentee') &&
(data.type == 'aggridcomponent' ||
data.type == 'aggridcomponentee' ||
data.type == 'dbexplorercomponent') &&
Array.isArray(data.actions)
) {
for (let c of data.actions) {
@@ -13,7 +13,7 @@
{/each}
</div>
{/if}
{#if (gridItem.data.type === 'aggridcomponent' || gridItem.data.type === 'aggridcomponentee') && gridItem.data.actions?.length > 0}
{#if (gridItem.data.type === 'aggridcomponent' || gridItem.data.type === 'aggridcomponentee' || gridItem.data.type === 'dbexplorercomponent') && gridItem.data.actions?.length > 0}
<div class="ml-2 border-l">
{#each gridItem.data.actions as action, index}
<Output id={action.id} first={index === 0} label="Table action" />
@@ -34,7 +34,7 @@
{/each}
{/if}
{#if (gridItem?.data?.type === 'aggridcomponent' || gridItem?.data?.type === 'aggridcomponentee') && Array.isArray(gridItem.data.actions)}
{#if (gridItem?.data?.type === 'aggridcomponent' || gridItem?.data?.type === 'aggridcomponentee' || gridItem?.data?.type === 'dbexplorercomponent') && Array.isArray(gridItem.data.actions)}
{#each gridItem.data.actions as actionButton, index (index)}
{#if actionButton?.id === $selectedComponentInEditor || actionButton?.id + '_transformer' === $selectedComponentInEditor}
<InlineScriptEditorPanel
@@ -66,7 +66,11 @@ function processGridItemRunnable(gridItem: GridItem, list: AppScriptsList): AppS
})
}
if (component.type === 'aggridcomponent' || component.type === 'aggridcomponentee') {
if (
component.type === 'aggridcomponent' ||
component.type === 'aggridcomponentee' ||
component.type === 'dbexplorercomponent'
) {
component.actions?.forEach((actionButton) => {
if (actionButton.componentInput?.type !== 'runnable') {
return
@@ -367,6 +367,8 @@
/>
{:else if componentSettings.item.data.type === 'aggridcomponent'}
<TableActions id={component.id} bind:components={componentSettings.item.data.actions} />
{:else if componentSettings.item.data.type === 'dbexplorercomponent'}
<TableActions id={component.id} bind:components={componentSettings.item.data.actions} />
{:else if componentSettings.item.data.type === 'tablecomponent' && Array.isArray(componentSettings.item.data.actionButtons)}
<TableActions id={component.id} bind:components={componentSettings.item.data.actionButtons} />
{:else if componentSettings.item.data.type === 'menucomponent' && Array.isArray(componentSettings.item.data.menuItems)}
+5 -1
View File
@@ -291,7 +291,11 @@ export function getAllScriptNames(app: App): string[] {
})
}
if (gridItem.data.type === 'aggridcomponent' || gridItem.data.type === 'aggridcomponentee') {
if (
gridItem.data.type === 'aggridcomponent' ||
gridItem.data.type === 'aggridcomponentee' ||
gridItem.data.type === 'dbexplorercomponent'
) {
gridItem.data.actions?.forEach((actionButton) => {
if (actionButton.componentInput?.type === 'runnable') {
if (actionButton.componentInput.runnable?.type === 'runnableByName') {