Add majority of frontend implementation of row actions
This commit is contained in:
parent
1991610b47
commit
c7c6597424
|
@ -19,6 +19,7 @@
|
|||
{disabled}
|
||||
on:change={onChange}
|
||||
on:click
|
||||
on:click|stopPropagation
|
||||
{id}
|
||||
type="checkbox"
|
||||
class="spectrum-Switch-input"
|
||||
|
|
|
@ -1,55 +1,50 @@
|
|||
<script>
|
||||
import Body from "../Typography/Body.svelte"
|
||||
import IconAvatar from "../Icon/IconAvatar.svelte"
|
||||
import Label from "../Label/Label.svelte"
|
||||
import Avatar from "../Avatar/Avatar.svelte"
|
||||
import Icon from "../Icon/Icon.svelte"
|
||||
|
||||
export let icon = null
|
||||
export let iconBackground = null
|
||||
export let iconColor = null
|
||||
export let avatar = false
|
||||
export let title = null
|
||||
export let subtitle = null
|
||||
export let hoverable = false
|
||||
|
||||
$: initials = avatar ? title?.[0] : null
|
||||
export let url = null
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="list-item" class:hoverable on:click>
|
||||
<a href={url} class="list-item" class:hoverable={url != null} on:click>
|
||||
<div class="left">
|
||||
{#if icon}
|
||||
<IconAvatar {icon} color={iconColor} background={iconBackground} />
|
||||
<Icon name={icon} color={iconColor} />
|
||||
{/if}
|
||||
{#if avatar}
|
||||
<Avatar {initials} />
|
||||
{/if}
|
||||
{#if title}
|
||||
<Body>{title}</Body>
|
||||
{/if}
|
||||
{#if subtitle}
|
||||
<Label>{subtitle}</Label>
|
||||
{/if}
|
||||
</div>
|
||||
{#if $$slots.default}
|
||||
<div class="right">
|
||||
<slot />
|
||||
<div class="list-item__text">
|
||||
{#if title}
|
||||
<div class="list-item__title">
|
||||
{title}
|
||||
</div>
|
||||
{/if}
|
||||
{#if subtitle}
|
||||
<div class="list-item__subtitle">
|
||||
{subtitle}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<slot name="right" />
|
||||
<Icon name="ChevronRight" />
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
.list-item {
|
||||
padding: 0 16px;
|
||||
height: 56px;
|
||||
background: var(--spectrum-global-color-gray-50);
|
||||
padding: var(--spacing-m);
|
||||
background: var(--spectrum-global-color-gray-75);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
transition: background 130ms ease-out;
|
||||
gap: var(--spacing-m);
|
||||
color: var(--spectrum-global-color-gray-800);
|
||||
}
|
||||
.list-item:not(:first-child) {
|
||||
border-top: none;
|
||||
|
@ -64,14 +59,15 @@
|
|||
}
|
||||
.hoverable:hover {
|
||||
cursor: pointer;
|
||||
background: var(--spectrum-global-color-gray-75);
|
||||
background: var(--spectrum-global-color-gray-200);
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xl);
|
||||
gap: var(--spacing-l);
|
||||
}
|
||||
.left {
|
||||
width: 0;
|
||||
|
@ -79,17 +75,20 @@
|
|||
}
|
||||
.right {
|
||||
flex: 0 0 auto;
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
.list-item :global(.spectrum-Icon),
|
||||
.list-item :global(.spectrum-Avatar) {
|
||||
flex: 0 0 auto;
|
||||
|
||||
.list-item__text {
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
}
|
||||
.list-item :global(.spectrum-Body) {
|
||||
color: var(--spectrum-global-color-gray-900);
|
||||
}
|
||||
.list-item :global(.spectrum-Body) {
|
||||
.list-item__title,
|
||||
.list-item__subtitle {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.list-item__subtitle {
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { ActionButton, Modal } from "@budibase/bbui"
|
||||
import { ActionButton } from "@budibase/bbui"
|
||||
import { permissions } from "stores/builder"
|
||||
import ManageAccessModal from "../modals/ManageAccessModal.svelte"
|
||||
import DetailPopover from "components/common/DetailPopover.svelte"
|
||||
|
@ -11,7 +11,6 @@
|
|||
$: fetchPermissions(resourceId)
|
||||
|
||||
const fetchPermissions = async id => {
|
||||
console.log("getting perms for", id)
|
||||
resourcePermissions = await permissions.forResourceDetailed(id)
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,12 +1,68 @@
|
|||
<script>
|
||||
import { ActionButton } from "@budibase/bbui"
|
||||
import { ActionButton, List, ListItem, Button } from "@budibase/bbui"
|
||||
import DetailPopover from "components/common/DetailPopover.svelte"
|
||||
import { TriggerStepID } from "constants/backend/automations"
|
||||
import { automationStore, appStore } from "stores/builder"
|
||||
import { createEventDispatcher, getContext } from "svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const { datasource } = getContext("grid")
|
||||
const triggerTypes = [
|
||||
TriggerStepID.ROW_SAVED,
|
||||
TriggerStepID.ROW_UPDATED,
|
||||
TriggerStepID.ROW_DELETED,
|
||||
]
|
||||
|
||||
let popover
|
||||
|
||||
$: ds = $datasource
|
||||
$: resourceId = ds?.type === "table" ? ds.tableId : ds?.id
|
||||
$: connectedAutomations = findConnectedAutomations(
|
||||
$automationStore.automations,
|
||||
resourceId
|
||||
)
|
||||
|
||||
const findConnectedAutomations = (automations, resourceId) => {
|
||||
return automations.filter(automation => {
|
||||
if (!triggerTypes.includes(automation.definition?.trigger?.stepId)) {
|
||||
return false
|
||||
}
|
||||
return automation.definition?.trigger?.inputs?.tableId === resourceId
|
||||
})
|
||||
}
|
||||
|
||||
const generateAutomation = () => {
|
||||
popover?.hide()
|
||||
dispatch("generate-automation")
|
||||
}
|
||||
</script>
|
||||
|
||||
<DetailPopover title="Automations">
|
||||
<DetailPopover title="Automations" minWidth={400} bind:this={popover}>
|
||||
<svelte:fragment slot="anchor" let:open>
|
||||
<ActionButton icon="JourneyVoyager" selected={open} quiet>
|
||||
Automations
|
||||
</ActionButton>
|
||||
<ActionButton icon="JourneyVoyager" selected={open} quiet
|
||||
>Automations</ActionButton
|
||||
>
|
||||
</svelte:fragment>
|
||||
{#if !connectedAutomations.length}
|
||||
There aren't any automations connected to this data.
|
||||
{:else}
|
||||
The following automations are connected to this data.
|
||||
<List>
|
||||
{#each connectedAutomations as automation}
|
||||
<ListItem
|
||||
icon={automation.disabled ? "PauseCircle" : "PlayCircle"}
|
||||
iconColor={automation.disabled
|
||||
? "var(--spectrum-global-color-gray-600)"
|
||||
: "var(--spectrum-global-color-green-600)"}
|
||||
title={automation.name}
|
||||
url={`/builder/app/${$appStore.appId}/automation/${automation._id}`}
|
||||
/>
|
||||
{/each}
|
||||
</List>
|
||||
{/if}
|
||||
<div>
|
||||
<Button secondary icon="JourneyVoyager" on:click={generateAutomation}>
|
||||
Generate new automation
|
||||
</Button>
|
||||
</div>
|
||||
</DetailPopover>
|
||||
|
|
|
@ -8,9 +8,14 @@
|
|||
|
||||
const { datasource } = getContext("grid")
|
||||
|
||||
let popover
|
||||
|
||||
$: triggers = $automationStore.blockDefinitions.CREATABLE_TRIGGER
|
||||
$: table = $tables.list.find(table => table._id === $datasource.tableId)
|
||||
|
||||
export const show = () => popover?.show()
|
||||
export const hide = () => popover?.hide()
|
||||
|
||||
async function createAutomation(type) {
|
||||
const triggerType = triggers[type]
|
||||
if (!triggerType) {
|
||||
|
@ -53,7 +58,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<DetailPopover title="Generate">
|
||||
<DetailPopover title="Generate" bind:this={popover}>
|
||||
<svelte:fragment slot="anchor" let:open>
|
||||
<ActionButton icon="MagicWand" selected={open}>Generate</ActionButton>
|
||||
</svelte:fragment>
|
||||
|
|
|
@ -1,12 +1,110 @@
|
|||
<script>
|
||||
import { ActionButton } from "@budibase/bbui"
|
||||
import {
|
||||
ActionButton,
|
||||
List,
|
||||
ListItem,
|
||||
Button,
|
||||
Toggle,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import DetailPopover from "components/common/DetailPopover.svelte"
|
||||
import { getContext } from "svelte"
|
||||
import { appStore, automationStore } from "stores/builder"
|
||||
import { API } from "api"
|
||||
import { goto, url } from "@roxi/routify"
|
||||
import { derived } from "svelte/store"
|
||||
import { getSequentialName } from "helpers/duplicate"
|
||||
|
||||
const { datasource } = getContext("grid")
|
||||
|
||||
let rowActions = []
|
||||
|
||||
$: ds = $datasource
|
||||
$: tableId = ds?.tableId
|
||||
$: isView = ds?.type === "viewV2"
|
||||
$: fetchRowActions(tableId)
|
||||
$: console.log(rowActions)
|
||||
$: activeCount = 0
|
||||
$: suffix = isView ? activeCount : rowActions.length
|
||||
|
||||
const rowActionUrl = derived([url, appStore], ([$url, $appStore]) => {
|
||||
return ({ automationId }) => {
|
||||
return $url(`/builder/app/${$appStore.appId}/automation/${automationId}`)
|
||||
}
|
||||
})
|
||||
|
||||
const fetchRowActions = async tableId => {
|
||||
if (!tableId) {
|
||||
rowActions = []
|
||||
return
|
||||
}
|
||||
const res = await API.rowActions.fetch(tableId)
|
||||
rowActions = Object.values(res || {})
|
||||
}
|
||||
|
||||
const createRowAction = async () => {
|
||||
try {
|
||||
const name = getSequentialName(rowActions, "New row action ", {
|
||||
getName: x => x.name,
|
||||
})
|
||||
const res = await API.rowActions.create({
|
||||
name,
|
||||
tableId,
|
||||
})
|
||||
console.log(res)
|
||||
await automationStore.actions.fetch()
|
||||
notifications.success("Row action created successfully")
|
||||
$goto($rowActionUrl(res))
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DetailPopover title="Row Actions">
|
||||
<DetailPopover title="Row Actions" minWidth={400} maxWidth={400}>
|
||||
<svelte:fragment slot="anchor" let:open>
|
||||
<ActionButton icon="Engagement" selected={open} quiet>
|
||||
Row Actions
|
||||
Row Actions ({suffix})
|
||||
</ActionButton>
|
||||
</svelte:fragment>
|
||||
A row action is a user-triggered automation for a chosen row.
|
||||
{#if isView && rowActions.length}
|
||||
<br />
|
||||
Use the toggle to enable/disable row actions for this view.
|
||||
<br />
|
||||
{/if}
|
||||
{#if !rowActions.length}
|
||||
<br />
|
||||
You haven't created any row actions.
|
||||
{:else}
|
||||
<List>
|
||||
{#each rowActions as action}
|
||||
<ListItem title={action.name} url={$rowActionUrl(action)}>
|
||||
<svelte:fragment slot="right">
|
||||
{#if isView}
|
||||
<span>
|
||||
<Toggle />
|
||||
</span>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ListItem>
|
||||
{/each}
|
||||
</List>
|
||||
{/if}
|
||||
<div>
|
||||
<Button secondary icon="Engagement" on:click={createRowAction}>
|
||||
Create row action
|
||||
</Button>
|
||||
</div>
|
||||
</DetailPopover>
|
||||
|
||||
<style>
|
||||
span :global(.spectrum-Switch) {
|
||||
min-height: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
span :global(.spectrum-Switch-switch) {
|
||||
margin-bottom: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { ActionButton } from "@budibase/bbui"
|
||||
import { ActionButton, List, ListItem } from "@budibase/bbui"
|
||||
import DetailPopover from "components/common/DetailPopover.svelte"
|
||||
import { screenStore } from "stores/builder"
|
||||
import { screenStore, appStore } from "stores/builder"
|
||||
import { getContext } from "svelte"
|
||||
|
||||
const { datasource } = getContext("grid")
|
||||
|
@ -20,10 +20,21 @@
|
|||
$: console.log(connectedScreens)
|
||||
</script>
|
||||
|
||||
<DetailPopover title="Screens">
|
||||
<DetailPopover title="Screens" minWidth={400}>
|
||||
<svelte:fragment slot="anchor" let:open>
|
||||
<ActionButton icon="WebPage" selected={open} quiet>Screens</ActionButton>
|
||||
</svelte:fragment>
|
||||
The following screens are connected to this data:
|
||||
{connectedScreens.map(screen => screen.routing.route)}
|
||||
{#if !connectedScreens.length}
|
||||
There aren't any screens connected to this data.
|
||||
{:else}
|
||||
The following screens are connected to this data.
|
||||
<List>
|
||||
{#each connectedScreens as screen}
|
||||
<ListItem
|
||||
title={screen.routing.route}
|
||||
url={`/builder/app/${$appStore.appId}/design/${screen._id}`}
|
||||
/>
|
||||
{/each}
|
||||
</List>
|
||||
{/if}
|
||||
</DetailPopover>
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
export let title
|
||||
export let align = "left"
|
||||
export let minWidth
|
||||
export let maxWidth
|
||||
|
||||
let popover
|
||||
let anchor
|
||||
|
@ -18,7 +20,14 @@
|
|||
<slot name="anchor" {open} />
|
||||
</div>
|
||||
|
||||
<Popover bind:this={popover} bind:open {anchor} {align} minWidth={300}>
|
||||
<Popover
|
||||
bind:this={popover}
|
||||
bind:open
|
||||
{anchor}
|
||||
{align}
|
||||
minWidth={minWidth || 300}
|
||||
{maxWidth}
|
||||
>
|
||||
<div class="detail-popover">
|
||||
<div class="detail-popover__header">
|
||||
<div class="detail-popover__title">
|
||||
|
@ -34,7 +43,6 @@
|
|||
|
||||
<style>
|
||||
.detail-popover {
|
||||
--padding: var(--spacing-l);
|
||||
background-color: var(--spectrum-alias-background-color-primary);
|
||||
}
|
||||
.detail-popover__header {
|
||||
|
@ -43,17 +51,17 @@
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
||||
padding: var(--padding);
|
||||
padding: var(--spacing-l) var(--spacing-xl);
|
||||
}
|
||||
.detail-popover__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.detail-popover__body {
|
||||
padding: var(--padding);
|
||||
padding: var(--spacing-xl) var(--spacing-xl);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--padding);
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -43,7 +43,6 @@
|
|||
<GridColumnsSettingButton />
|
||||
<GridManageAccessButton />
|
||||
<GridRowActionsButton />
|
||||
<GridAutomationsButton />
|
||||
<GridScreensButton />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="controls-right">
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
import GridUsersTableButton from "components/backend/DataTable/buttons/grid/GridUsersTableButton.svelte"
|
||||
import GridGenerateButton from "components/backend/DataTable/buttons/grid/GridGenerateButton.svelte"
|
||||
import GridScreensButton from "components/backend/DataTable/buttons/grid/GridScreensButton.svelte"
|
||||
import GridAutomationsButton from "components/backend/DataTable/buttons/grid/GridAutomationsButton.svelte"
|
||||
import GridRowActionsButton from "components/backend/DataTable/buttons/grid/GridRowActionsButton.svelte"
|
||||
import { DB_TYPE_EXTERNAL } from "constants/backend"
|
||||
|
||||
|
@ -27,6 +28,8 @@
|
|||
status: { displayName: "Status", disabled: true },
|
||||
}
|
||||
|
||||
let generateButton
|
||||
|
||||
$: autoColumnStatus = verifyAutocolumns($tables?.selected)
|
||||
$: duplicates = Object.values(autoColumnStatus).reduce((acc, status) => {
|
||||
if (status.length > 1) {
|
||||
|
@ -113,7 +116,12 @@
|
|||
{#if relationshipsEnabled}
|
||||
<GridRelationshipButton />
|
||||
{/if}
|
||||
<GridRowActionsButton />
|
||||
{#if !isUsersTable}
|
||||
<GridRowActionsButton />
|
||||
{/if}
|
||||
<GridAutomationsButton
|
||||
on:generate-automation={() => generateButton?.show()}
|
||||
/>
|
||||
<GridScreensButton />
|
||||
{#if !isUsersTable}
|
||||
<GridImportButton />
|
||||
|
@ -122,7 +130,7 @@
|
|||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="controls-right">
|
||||
<GridGenerateButton />
|
||||
<GridGenerateButton bind:this={generateButton} />
|
||||
</svelte:fragment>
|
||||
|
||||
<!-- Content for editing columns -->
|
||||
|
|
|
@ -34,6 +34,7 @@ import { buildEventEndpoints } from "./events"
|
|||
import { buildAuditLogsEndpoints } from "./auditLogs"
|
||||
import { buildLogsEndpoints } from "./logs"
|
||||
import { buildMigrationEndpoints } from "./migrations"
|
||||
import { buildRowActionEndpoints } from "./rowActions"
|
||||
|
||||
/**
|
||||
* Random identifier to uniquely identify a session in a tab. This is
|
||||
|
@ -301,5 +302,6 @@ export const createAPIClient = config => {
|
|||
...buildLogsEndpoints(API),
|
||||
...buildMigrationEndpoints(API),
|
||||
viewV2: buildViewV2Endpoints(API),
|
||||
rowActions: buildRowActionEndpoints(API),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
export const buildRowActionEndpoints = API => ({
|
||||
/**
|
||||
* Gets the available row actions for a table.
|
||||
* @param tableId the ID of the table
|
||||
*/
|
||||
fetch: async tableId => {
|
||||
const res = await API.get({
|
||||
url: `/api/tables/${tableId}/actions`,
|
||||
})
|
||||
return res?.actions || {}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a row action.
|
||||
* @param name the name of the row action
|
||||
* @param tableId the ID of the table
|
||||
*/
|
||||
create: async ({ name, tableId }) => {
|
||||
return await API.post({
|
||||
url: `/api/tables/${tableId}/actions`,
|
||||
body: {
|
||||
name,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates a row action.
|
||||
* @param name the new name of the row action
|
||||
* @param tableId the ID of the table
|
||||
* @param rowActionId the ID of the row action to update
|
||||
*/
|
||||
update: async ({ tableId, rowActionId, name }) => {
|
||||
return await API.post({
|
||||
url: `/api/tables/${tableId}/actions/${rowActionId}`,
|
||||
body: {
|
||||
name,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a row action.
|
||||
* @param tableId the ID of the table
|
||||
* @param rowActionId the ID of the row action to delete
|
||||
*/
|
||||
delete: async ({ tableId, rowActionId }) => {
|
||||
return await API.delete({
|
||||
url: `/api/tables/${tableId}/actions/${rowActionId}`,
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggers a row action.
|
||||
* @param tableId the ID of the table
|
||||
* @param rowActionId the ID of the row action to trigger
|
||||
*/
|
||||
trigger: async ({ tableId, rowActionid }) => {
|
||||
return await API.post({
|
||||
url: `/api/tables/${tableId}/actions/${rowActionId}/trigger`,
|
||||
})
|
||||
},
|
||||
})
|
Loading…
Reference in New Issue