@budibase/frontend-core 3.18.0 → 3.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@budibase/frontend-core",
3
- "version": "3.18.0",
3
+ "version": "3.18.2",
4
4
  "description": "Budibase frontend core libraries used in builder and client",
5
5
  "author": "Budibase",
6
6
  "license": "MPL-2.0",
@@ -17,5 +17,5 @@
17
17
  "shortid": "2.2.15",
18
18
  "socket.io-client": "^4.7.5"
19
19
  },
20
- "gitHead": "255f35eea6023bbc035d9dee8c56ba3e600c6b89"
20
+ "gitHead": "43d59f5579dd910575d8aebefb40fae25570c05d"
21
21
  }
@@ -21,6 +21,7 @@
21
21
  showHScrollbar,
22
22
  dispatch,
23
23
  config,
24
+ metadata,
24
25
  } = getContext("grid")
25
26
 
26
27
  let container
@@ -42,6 +43,29 @@
42
43
  return gridButtons
43
44
  }
44
45
 
46
+ // Apply button conditions and return filtered/modified buttons for a specific row
47
+ const getButtonsForRow = (buttons, row) => {
48
+ if (!buttons || !row) return buttons
49
+
50
+ const rowMetadata = $metadata?.[row._id]?.button || {}
51
+
52
+ return buttons
53
+ .map((button, index) => {
54
+ const buttonMetadata = rowMetadata[index]
55
+ if (!buttonMetadata) return button
56
+
57
+ // Skip hidden buttons
58
+ if (buttonMetadata.hidden) return null
59
+
60
+ // Apply any setting updates
61
+ return {
62
+ ...button,
63
+ ...buttonMetadata,
64
+ }
65
+ })
66
+ .filter(button => button !== null)
67
+ }
68
+
45
69
  const handleClick = async (button, row) => {
46
70
  await button.onClick?.(rows.actions.cleanRow(row))
47
71
  await rows.actions.refreshRow(row._id)
@@ -75,13 +99,14 @@
75
99
  {@const rowSelected = !!$selectedRows[row._id]}
76
100
  {@const rowHovered = $hoveredRowId === row._id}
77
101
  {@const rowFocused = $focusedRow?._id === row._id}
102
+ {@const rowButtons = getButtonsForRow(buttons, row)}
78
103
  <div
79
104
  class="row"
80
105
  on:mouseenter={$isDragging ? null : () => ($hoveredRowId = row._id)}
81
106
  on:mouseleave={$isDragging ? null : () => ($hoveredRowId = null)}
82
107
  >
83
108
  <GridCell
84
- width="auto"
109
+ width="100%"
85
110
  rowIdx={row.__idx}
86
111
  selected={rowSelected}
87
112
  highlighted={rowHovered || rowFocused}
@@ -92,17 +117,21 @@
92
117
  class:offset={$showVScrollbar && $showHScrollbar}
93
118
  >
94
119
  {#if $props.buttonsCollapsed}
95
- <CollapsedButtonGroup
96
- buttons={makeCollapsedButtons(buttons, row)}
97
- text={$props.buttonsCollapsedText || "Action"}
98
- align="right"
99
- offset={5}
100
- size="S"
101
- animate={false}
102
- on:mouseenter={() => ($hoveredRowId = row._id)}
103
- />
120
+ {#if rowButtons.length > 0}
121
+ <CollapsedButtonGroup
122
+ buttons={makeCollapsedButtons(rowButtons, row)}
123
+ text={$props.buttonsCollapsedText || "Action"}
124
+ align="right"
125
+ offset={5}
126
+ size="S"
127
+ animate={false}
128
+ on:mouseenter={() => ($hoveredRowId = row._id)}
129
+ />
130
+ {:else}
131
+ <div class="button-placeholder-collapsed" />
132
+ {/if}
104
133
  {:else}
105
- {#each buttons as button}
134
+ {#each rowButtons as button}
106
135
  <Button
107
136
  newStyles
108
137
  size="S"
@@ -119,6 +148,9 @@
119
148
  {button.text || "Button"}
120
149
  </Button>
121
150
  {/each}
151
+ {#if rowButtons.length === 0}
152
+ <div class="button-placeholder" />
153
+ {/if}
122
154
  {/if}
123
155
  </div>
124
156
  </GridCell>
@@ -179,6 +211,15 @@
179
211
  align-items: center;
180
212
  gap: 4px;
181
213
  }
214
+ .button-placeholder {
215
+ min-width: 60px;
216
+ height: 32px;
217
+ }
218
+ .button-placeholder-collapsed {
219
+ min-width: 70px;
220
+ height: 32px;
221
+ visibility: hidden;
222
+ }
182
223
  .blank :global(.cell:hover) {
183
224
  cursor: pointer;
184
225
  }
@@ -1,6 +1,7 @@
1
1
  <script>
2
2
  import { getContext } from "svelte"
3
3
  import DataCell from "../cells/DataCell.svelte"
4
+ import GridCell from "../cells/GridCell.svelte"
4
5
  import { getCellID } from "../lib/utils"
5
6
 
6
7
  export let row
@@ -22,6 +23,8 @@
22
23
  isSelectingCells,
23
24
  selectedCellMap,
24
25
  selectedCellCount,
26
+ props,
27
+ buttonColumnWidth,
25
28
  } = getContext("grid")
26
29
 
27
30
  $: rowSelected = !!$selectedRows[row._id]
@@ -29,6 +32,8 @@
29
32
  $hoveredRowId === row._id && (!$selectedCellCount || !$isSelectingCells)
30
33
  $: rowFocused = $focusedRow?._id === row._id
31
34
  $: reorderSource = $reorder.sourceColumn
35
+ $: hasButtons = $props?.buttons?.length > 0
36
+ $: needsButtonSpacer = hasButtons && $buttonColumnWidth > 0
32
37
  </script>
33
38
 
34
39
  <!-- svelte-ignore a11y-no-static-element-interactions -->
@@ -60,6 +65,14 @@
60
65
  isSelectingCells={$isSelectingCells}
61
66
  />
62
67
  {/each}
68
+ {#if needsButtonSpacer}
69
+ <GridCell
70
+ width={$buttonColumnWidth}
71
+ selected={rowSelected}
72
+ highlighted={rowHovered || rowFocused}
73
+ rowIdx={row.__idx}
74
+ />
75
+ {/if}
63
76
  </div>
64
77
 
65
78
  <style>
@@ -1,4 +1,4 @@
1
- import { writable, get, Writable, Readable } from "svelte/store"
1
+ import { writable, get, derived, Writable, Readable } from "svelte/store"
2
2
  import { derivedMemo, QueryUtils } from "../../../utils"
3
3
  import {
4
4
  FieldType,
@@ -27,23 +27,42 @@ export const createStores = (): ConditionStore => {
27
27
  }
28
28
 
29
29
  export const deriveStores = (context: StoreContext): ConditionDerivedStore => {
30
- const { columns } = context
30
+ const { columns, props } = context
31
31
 
32
32
  // Derive and memoize the cell conditions present in our columns so that we
33
33
  // only recompute condition metadata when absolutely necessary
34
- const conditions = derivedMemo(columns, $columns => {
35
- let newConditions: UICondition[] = []
36
- for (let column of $columns) {
37
- for (let condition of column.conditions || []) {
38
- newConditions.push({
39
- ...condition,
40
- column: column.name,
41
- type: column.schema.type,
42
- })
34
+ const conditions = derivedMemo(
35
+ derived([columns, props], ([$columns, $props]) => {
36
+ let newConditions: UICondition[] = []
37
+
38
+ // Add column conditions
39
+ for (let column of $columns) {
40
+ for (let condition of column.conditions || []) {
41
+ newConditions.push({
42
+ ...condition,
43
+ column: column.name,
44
+ type: column.schema.type,
45
+ })
46
+ }
43
47
  }
44
- }
45
- return newConditions
46
- })
48
+
49
+ // Add button conditions
50
+ if ($props.buttons) {
51
+ for (let button of $props.buttons) {
52
+ for (let condition of button.conditions || []) {
53
+ newConditions.push({
54
+ ...condition,
55
+ target: "button",
56
+ buttonIndex: $props.buttons.indexOf(button),
57
+ })
58
+ }
59
+ }
60
+ }
61
+
62
+ return newConditions
63
+ }),
64
+ conditions => conditions
65
+ )
47
66
 
48
67
  return {
49
68
  conditions,
@@ -58,7 +77,7 @@ export const initialise = (context: StoreContext) => {
58
77
  let newMetadata: Record<string, any> = {}
59
78
  if ($conditions?.length) {
60
79
  for (let row of get(rows)) {
61
- newMetadata[row._id] = evaluateConditions(row, $conditions)
80
+ newMetadata[row._id] = evaluateConditions(row, $conditions, context)
62
81
  }
63
82
  }
64
83
  metadata.set(newMetadata)
@@ -86,7 +105,7 @@ export const initialise = (context: StoreContext) => {
86
105
  continue
87
106
  }
88
107
 
89
- $metadata[row._id] = evaluateConditions(row, $conditions)
108
+ $metadata[row._id] = evaluateConditions(row, $conditions, context)
90
109
  }
91
110
  if (Object.keys(metadataUpdates).length) {
92
111
  metadata.update(state => ({
@@ -118,17 +137,62 @@ const TypeCoercionMap: Partial<Record<FieldType, (val: string) => any>> = {
118
137
 
119
138
  // Evaluates an array of cell conditions against a certain row and returns the
120
139
  // resultant metadata
121
- const evaluateConditions = (row: UIRow, conditions: UICondition[]) => {
140
+ const evaluateConditions = (
141
+ row: UIRow,
142
+ conditions: UICondition[],
143
+ context: StoreContext
144
+ ) => {
122
145
  const metadata: {
123
146
  version?: string
124
147
  row: Record<string, string>
125
148
  cell: Record<string, any>
149
+ button: Record<string, any>
126
150
  } = {
127
151
  version: row._rev,
128
152
  row: {},
129
153
  cell: {},
154
+ button: {},
130
155
  }
131
- for (let condition of conditions) {
156
+
157
+ // Get dynamic button conditions
158
+ const { props } = context
159
+ const $props = get(props)
160
+ let allConditions = [...conditions]
161
+
162
+ // Add dynamic button conditions from getRowConditions
163
+ if ($props.buttons) {
164
+ for (let button of $props.buttons) {
165
+ if (button.getRowConditions) {
166
+ const dynamicConditions = button.getRowConditions(row) || []
167
+ for (let condition of dynamicConditions) {
168
+ allConditions.push({
169
+ ...condition,
170
+ target: "button",
171
+ buttonIndex: $props.buttons.indexOf(button),
172
+ })
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ // Pre-process button conditions to set default visibility for show conditions
179
+ const buttonShowConditions = new Set()
180
+ for (let condition of allConditions) {
181
+ if (
182
+ condition.target === "button" &&
183
+ condition.action === "show" &&
184
+ typeof condition.buttonIndex === "number"
185
+ ) {
186
+ buttonShowConditions.add(condition.buttonIndex)
187
+ // Initialize button metadata and set as hidden by default for show conditions
188
+ if (!metadata.button[condition.buttonIndex]) {
189
+ metadata.button[condition.buttonIndex] = {}
190
+ }
191
+ metadata.button[condition.buttonIndex].hidden = true
192
+ }
193
+ }
194
+
195
+ for (let condition of allConditions) {
132
196
  try {
133
197
  let {
134
198
  column,
@@ -138,8 +202,19 @@ const evaluateConditions = (row: UIRow, conditions: UICondition[]) => {
138
202
  metadataKey,
139
203
  metadataValue,
140
204
  target,
205
+ buttonIndex,
206
+ newValue,
207
+ action,
208
+ setting,
209
+ settingValue,
141
210
  } = condition
142
- let value = row[column]
211
+
212
+ let value
213
+ if (target === "button") {
214
+ value = newValue
215
+ } else {
216
+ value = row[column]
217
+ }
143
218
 
144
219
  // Coerce values into correct types for primitives
145
220
  let coercedType = type
@@ -154,8 +229,8 @@ const evaluateConditions = (row: UIRow, conditions: UICondition[]) => {
154
229
  }
155
230
  const coerce = TypeCoercionMap[coercedType]
156
231
  if (coerce) {
157
- value = coerce(value)
158
- referenceValue = coerce(referenceValue)
232
+ value = coerce(value as string)
233
+ referenceValue = coerce(referenceValue as string)
159
234
  }
160
235
 
161
236
  // Build lucene compatible condition expression
@@ -169,7 +244,19 @@ const evaluateConditions = (row: UIRow, conditions: UICondition[]) => {
169
244
  query.onEmptyFilter = EmptyFilterOption.RETURN_NONE
170
245
  const result = QueryUtils.runQuery([{ value }], query)
171
246
  if (result.length > 0) {
172
- if (target === "row") {
247
+ if (target === "button" && typeof buttonIndex === "number") {
248
+ if (!metadata.button[buttonIndex]) {
249
+ metadata.button[buttonIndex] = {}
250
+ }
251
+
252
+ if (action === "hide") {
253
+ metadata.button[buttonIndex].hidden = true
254
+ } else if (action === "show") {
255
+ metadata.button[buttonIndex].hidden = false
256
+ } else if (action === "update" && setting) {
257
+ metadata.button[buttonIndex][setting] = settingValue
258
+ }
259
+ } else if (target === "row") {
173
260
  metadata.row = {
174
261
  ...metadata.row,
175
262
  [metadataKey]: metadataValue,
@@ -5,7 +5,7 @@ import {
5
5
  getDatasourceSchema,
6
6
  } from "../../../fetch"
7
7
  import { enrichSchemaWithRelColumns, memo } from "../../../utils"
8
- import { cloneDeep } from "lodash"
8
+ import cloneDeep from "lodash/cloneDeep"
9
9
  import {
10
10
  SaveRowRequest,
11
11
  UIDatasource,
@@ -24,7 +24,12 @@ import * as ViewV2 from "./datasources/viewV2"
24
24
  import * as NonPlus from "./datasources/nonPlus"
25
25
  import * as Cache from "./cache"
26
26
  import * as Conditions from "./conditions"
27
- import { SortOrder, UIDatasource, UISearchFilter } from "@budibase/types"
27
+ import {
28
+ SortOrder,
29
+ UICondition,
30
+ UIDatasource,
31
+ UISearchFilter,
32
+ } from "@budibase/types"
28
33
  import * as Constants from "../lib/constants"
29
34
  import * as GridClipboard from "../../../stores/gridClipboard"
30
35
  import { ExternalClipboardData } from "../../../stores/gridClipboard"
@@ -79,6 +84,14 @@ export interface BaseStoreProps {
79
84
  minHeight?: number
80
85
  canHideColumns?: boolean
81
86
  externalClipboard?: ExternalClipboardData
87
+ buttons?: {
88
+ text: string
89
+ onClick: unknown
90
+ conditions?: UICondition[]
91
+ getRowConditions?: (row: any) => UICondition[]
92
+ }[]
93
+ buttonsCollapsed?: boolean
94
+ buttonsCollapsedText?: string
82
95
  }
83
96
 
84
97
  export interface BaseStore {
@@ -1,5 +1,5 @@
1
1
  import { writable, derived, get, Writable, Readable } from "svelte/store"
2
- import { cloneDeep } from "lodash/fp"
2
+ import cloneDeep from "lodash/fp/cloneDeep"
3
3
  import { QueryUtils } from "../utils"
4
4
  import { convertJSONSchemaToTableSchema } from "../utils/json"
5
5
  import {
@@ -3,6 +3,9 @@ import { helpers } from "@budibase/shared-core"
3
3
  // Util to check if a setting can be rendered for a certain instance, based on
4
4
  // the "dependsOn" metadata in the manifest
5
5
  export const shouldDisplaySetting = (instance, setting) => {
6
+ if (setting.nestedOnly && !setting.nested) {
7
+ return false
8
+ }
6
9
  let dependsOn = setting.dependsOn
7
10
  if (dependsOn && !Array.isArray(dependsOn)) {
8
11
  dependsOn = [dependsOn]
@@ -1,6 +1,6 @@
1
1
  import { makePropSafe as safe } from "@budibase/string-templates"
2
2
  import { Helpers } from "@budibase/bbui"
3
- import { cloneDeep } from "lodash"
3
+ import cloneDeep from "lodash/cloneDeep"
4
4
  import {
5
5
  SearchFilterGroup,
6
6
  UISearchFilter,