1
0
Fork 0
mirror of synced 2024-09-21 03:43:21 +12:00

Add new builder store for row action CRUD, ensuring consistent state everywhere

This commit is contained in:
Andrew Kingston 2024-09-03 18:58:22 +01:00
parent 0e6d903c74
commit 53ff7e1167
No known key found for this signature in database
8 changed files with 173 additions and 63 deletions

View file

@ -9,21 +9,20 @@
} from "@budibase/bbui" } from "@budibase/bbui"
import DetailPopover from "components/common/DetailPopover.svelte" import DetailPopover from "components/common/DetailPopover.svelte"
import { getContext } from "svelte" import { getContext } from "svelte"
import { appStore, automationStore } from "stores/builder" import { appStore, rowActions } from "stores/builder"
import { API } from "api" import { API } from "api"
import { goto, url } from "@roxi/routify" import { goto, url } from "@roxi/routify"
import { derived } from "svelte/store" import { derived } from "svelte/store"
import { getSequentialName } from "helpers/duplicate"
const { datasource } = getContext("grid") const { datasource } = getContext("grid")
let rowActions = []
$: ds = $datasource $: ds = $datasource
$: tableId = ds?.tableId $: tableId = ds?.tableId
$: viewId = ds?.id
$: isView = ds?.type === "viewV2" $: isView = ds?.type === "viewV2"
$: fetchRowActions(ds) $: tableRowActions = $rowActions[tableId] || []
$: actionCount = rowActions.filter(action => !isView || action.enabled).length $: viewRowActions = $rowActions[viewId] || []
$: actionCount = isView ? viewRowActions.length : tableRowActions.length
const rowActionUrl = derived([url, appStore], ([$url, $appStore]) => { const rowActionUrl = derived([url, appStore], ([$url, $appStore]) => {
return ({ automationId }) => { return ({ automationId }) => {
@ -31,30 +30,11 @@
} }
}) })
const fetchRowActions = async datasource => {
if (!datasource?.tableId) {
rowActions = []
return
}
const res = await API.rowActions.fetch(datasource.tableId)
rowActions = Object.values(res || {}).map(action => ({
...action,
enabled: !isView || action.allowedViews?.includes(ds.id),
}))
}
const createRowAction = async () => { const createRowAction = async () => {
try { try {
const name = getSequentialName(rowActions, "New row action ", { const newRowAction = await rowActions.createRowAction(tableId, viewId)
getName: x => x.name,
})
const res = await API.rowActions.create({
name,
tableId,
})
await automationStore.actions.fetch()
notifications.success("Row action created successfully") notifications.success("Row action created successfully")
$goto($rowActionUrl(res)) // $goto($rowActionUrl(newRowAction))
} catch (error) { } catch (error) {
console.error(error) console.error(error)
notifications.error("Error creating row action") notifications.error("Error creating row action")
@ -62,19 +42,10 @@
} }
const toggleAction = async (action, enabled) => { const toggleAction = async (action, enabled) => {
console.log(action, enabled)
if (enabled) { if (enabled) {
await API.rowActions.enableView({ await rowActions.enableView(tableId, viewId, action.id)
tableId,
rowActionId: action.id,
viewId: ds.id,
})
} else { } else {
await API.rowActions.disableView({ await rowActions.disableView(tableId, viewId, action.id)
tableId,
rowActionId: action.id,
viewId: ds.id,
})
} }
} }
</script> </script>
@ -96,18 +67,18 @@
Use the toggle to enable/disable row actions for this view. Use the toggle to enable/disable row actions for this view.
<br /> <br />
{/if} {/if}
{#if !rowActions.length} {#if !tableRowActions.length}
<br /> <br />
You haven't created any row actions. You haven't created any row actions.
{:else} {:else}
<List> <List>
{#each rowActions as action} {#each tableRowActions as action}
<ListItem title={action.name} url={$rowActionUrl(action)} showArrow> <ListItem title={action.name} url={$rowActionUrl(action)} showArrow>
<svelte:fragment slot="right"> <svelte:fragment slot="right">
{#if isView} {#if isView}
<span> <span>
<Toggle <Toggle
value={action.enabled} value={action.allowedViews?.includes(viewId)}
on:change={e => toggleAction(action, e.detail)} on:change={e => toggleAction(action, e.detail)}
/> />
</span> </span>

View file

@ -1,5 +1,5 @@
<script> <script>
import { viewsV2 } from "stores/builder" import { viewsV2, rowActions } from "stores/builder"
import { admin } from "stores/portal" import { admin } from "stores/portal"
import { Grid } from "@budibase/frontend-core" import { Grid } from "@budibase/frontend-core"
import { API } from "api" import { API } from "api"
@ -21,6 +21,21 @@
id, id,
tableId: $viewsV2.selected?.tableId, tableId: $viewsV2.selected?.tableId,
} }
$: buttons = makeRowActionButtons($rowActions[id])
$: rowActions.refreshRowActions(id)
const makeRowActionButtons = rowActions => {
return (rowActions || []).map(action => ({
text: action.name,
onClick: async row => {
await API.rowActions.trigger({
rowActionId: action.id,
sourceId: id,
rowId: row._id,
})
},
}))
}
const handleGridViewUpdate = async e => { const handleGridViewUpdate = async e => {
viewsV2.replaceView(id, e.detail) viewsV2.replaceView(id, e.detail)
@ -35,6 +50,8 @@
showAvatars={false} showAvatars={false}
on:updatedatasource={handleGridViewUpdate} on:updatedatasource={handleGridViewUpdate}
isCloud={$admin.cloud} isCloud={$admin.cloud}
{buttons}
buttonsCollapsed
> >
<svelte:fragment slot="controls"> <svelte:fragment slot="controls">
<GridFilterButton /> <GridFilterButton />

View file

@ -1,6 +1,12 @@
<script> <script>
import { Banner } from "@budibase/bbui" import { Banner } from "@budibase/bbui"
import { datasources, tables, integrations, appStore } from "stores/builder" import {
datasources,
tables,
integrations,
appStore,
rowActions,
} from "stores/builder"
import { themeStore, admin } from "stores/portal" import { themeStore, admin } from "stores/portal"
import { TableNames } from "constants" import { TableNames } from "constants"
import { Grid } from "@budibase/frontend-core" import { Grid } from "@budibase/frontend-core"
@ -28,7 +34,6 @@
status: { displayName: "Status", disabled: true }, status: { displayName: "Status", disabled: true },
} }
let rowActions = []
let generateButton let generateButton
$: autoColumnStatus = verifyAutocolumns($tables?.selected) $: autoColumnStatus = verifyAutocolumns($tables?.selected)
@ -54,11 +59,11 @@
$: relationshipsEnabled = relationshipSupport(tableDatasource) $: relationshipsEnabled = relationshipSupport(tableDatasource)
$: currentTheme = $themeStore?.theme $: currentTheme = $themeStore?.theme
$: darkMode = !currentTheme.includes("light") $: darkMode = !currentTheme.includes("light")
$: buttons = makeRowActionButtons(rowActions) $: buttons = makeRowActionButtons($rowActions[id])
$: fetchRowActions(id) $: rowActions.refreshRowActions(id)
const makeRowActionButtons = rowActions => { const makeRowActionButtons = rowActions => {
return rowActions.map(action => ({ return (rowActions || []).map(action => ({
text: action.name, text: action.name,
onClick: async row => { onClick: async row => {
await API.rowActions.trigger({ await API.rowActions.trigger({
@ -98,15 +103,6 @@
return acc return acc
}, {}) }, {})
} }
const fetchRowActions = async tableId => {
if (!tableId) {
rowActions = []
return
}
const res = await API.rowActions.fetch(tableId)
rowActions = Object.values(res || {})
}
</script> </script>
{#if $tables?.selected?.name} {#if $tables?.selected?.name}

View file

@ -2,9 +2,8 @@ import { writable } from "svelte/store"
export default class BudiStore { export default class BudiStore {
constructor(init, opts) { constructor(init, opts) {
const store = writable({ this.initialState = init
...init, const store = writable({ ...init })
})
/** /**
* Internal Svelte store * Internal Svelte store
@ -23,6 +22,7 @@ export default class BudiStore {
* *Store modification should be kept to a minimum * *Store modification should be kept to a minimum
*/ */
this.update = this.store.update this.update = this.store.update
this.set = this.store.set
/** /**
* Optional debug mode to output the store updates to console * Optional debug mode to output the store updates to console
@ -33,4 +33,8 @@ export default class BudiStore {
}) })
} }
} }
reset = () => {
this.store.set({ ...this.initialState })
}
} }

View file

@ -29,6 +29,7 @@ import { integrations } from "./integrations"
import { sortedIntegrations } from "./sortedIntegrations" import { sortedIntegrations } from "./sortedIntegrations"
import { queries } from "./queries" import { queries } from "./queries"
import { flags } from "./flags" import { flags } from "./flags"
import { rowActions } from "./rowActions"
import componentTreeNodesStore from "./componentTreeNodes" import componentTreeNodesStore from "./componentTreeNodes"
export { export {
@ -65,6 +66,7 @@ export {
flags, flags,
hoverStore, hoverStore,
snippets, snippets,
rowActions,
} }
export const reset = () => { export const reset = () => {
@ -74,6 +76,7 @@ export const reset = () => {
componentStore.reset() componentStore.reset()
layoutStore.reset() layoutStore.reset()
navigationStore.reset() navigationStore.reset()
rowActions.reset()
} }
const refreshBuilderData = async () => { const refreshBuilderData = async () => {

View file

@ -0,0 +1,114 @@
import { get, derived } from "svelte/store"
import BudiStore from "stores/BudiStore"
import { tables } from "./tables"
import { viewsV2 } from "./viewsV2"
import { automationStore } from "./automations"
import { API } from "api"
import { getSequentialName } from "helpers/duplicate"
const initialState = {}
export class RowActionStore extends BudiStore {
constructor() {
super(initialState)
}
refreshRowActions = async sourceId => {
if (!sourceId) {
return
}
// Get the underlying table ID for this source ID
let tableId = get(tables).list.find(table => table._id === sourceId)?._id
if (!tableId) {
const view = get(viewsV2).list.find(view => view.id === sourceId)
tableId = view?.tableId
}
if (!tableId) {
return
}
// Fetch row actions for this table
const res = await API.rowActions.fetch(tableId)
const actions = Object.values(res || {})
this.update(state => ({
...state,
[tableId]: actions,
}))
}
createRowAction = async (tableId, viewId) => {
if (!tableId) {
return
}
// Get a unique name for this action
const existingRowActions = get(this.store)[tableId] || []
const name = getSequentialName(existingRowActions, "New row action ", {
getName: x => x.name,
})
// Create the action and update state
const res = await API.rowActions.create({
name,
tableId,
})
this.update(state => ({
...state,
[tableId]: [...(state[tableId] || []), res],
}))
// If adding to a view, enable on this view
if (viewId) {
await this.enableView(tableId, viewId, res.id)
}
// Refresh automations so we have this new row action automation
await automationStore.actions.fetch()
return res
}
enableView = async (tableId, viewId, rowActionId) => {
await API.rowActions.enableView({
tableId,
viewId,
rowActionId,
})
await this.refreshRowActions(tableId)
}
disableView = async (tableId, viewId, rowActionId) => {
await API.rowActions.disableView({
tableId,
viewId,
rowActionId,
})
await this.refreshRowActions(tableId)
}
}
const store = new RowActionStore()
const derivedStore = derived(store, $store => {
let map = {}
// Generate an entry for every view as well
Object.keys($store || {}).forEach(tableId => {
map[tableId] = $store[tableId]
for (let action of $store[tableId]) {
for (let viewId of action.allowedViews || []) {
if (!map[viewId]) {
map[viewId] = []
}
map[viewId].push(action)
}
}
})
return map
})
export const rowActions = {
...store,
subscribe: derivedStore.subscribe,
}

View file

@ -104,7 +104,6 @@ export const createLicensingStore = () => {
const isBusinessPlan = planType === Constants.PlanType.BUSINESS const isBusinessPlan = planType === Constants.PlanType.BUSINESS
const isEnterpriseTrial = const isEnterpriseTrial =
planType === Constants.PlanType.ENTERPRISE_BASIC_TRIAL planType === Constants.PlanType.ENTERPRISE_BASIC_TRIAL
console.log(license)
const groupsEnabled = license.features.includes( const groupsEnabled = license.features.includes(
Constants.Features.USER_GROUPS Constants.Features.USER_GROUPS
) )
@ -143,8 +142,6 @@ export const createLicensingStore = () => {
Constants.Features.VIEW_READONLY_COLUMNS Constants.Features.VIEW_READONLY_COLUMNS
) )
console.log(isViewReadonlyColumnsEnabled)
store.update(state => { store.update(state => {
return { return {
...state, ...state,

View file

@ -25,7 +25,7 @@
let container let container
$: buttons = $props.buttons?.slice(0, 3) || [] $: buttons = getButtons($props)
$: columnsWidth = $scrollableColumns.reduce( $: columnsWidth = $scrollableColumns.reduce(
(total, col) => (total += col.width), (total, col) => (total += col.width),
0 0
@ -34,6 +34,14 @@
$: gridEnd = $width - $buttonColumnWidth - 1 $: gridEnd = $width - $buttonColumnWidth - 1
$: left = Math.min(columnEnd, gridEnd) $: left = Math.min(columnEnd, gridEnd)
const getButtons = ({ buttons, buttonsCollapsed }) => {
let gridButtons = buttons || []
if (!buttonsCollapsed) {
return gridButtons.slice(0, 3)
}
return gridButtons
}
const handleClick = async (button, row) => { const handleClick = async (button, row) => {
await button.onClick?.(rows.actions.cleanRow(row)) await button.onClick?.(rows.actions.cleanRow(row))
await rows.actions.refreshRow(row._id) await rows.actions.refreshRow(row._id)