@budibase/frontend-core 2.32.7 → 2.32.9

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,18 +1,18 @@
1
1
  {
2
2
  "name": "@budibase/frontend-core",
3
- "version": "2.32.7",
3
+ "version": "2.32.9",
4
4
  "description": "Budibase frontend core libraries used in builder and client",
5
5
  "author": "Budibase",
6
6
  "license": "MPL-2.0",
7
7
  "svelte": "src/index.js",
8
8
  "dependencies": {
9
- "@budibase/bbui": "2.32.7",
10
- "@budibase/shared-core": "2.32.7",
11
- "@budibase/types": "2.32.7",
9
+ "@budibase/bbui": "2.32.9",
10
+ "@budibase/shared-core": "2.32.9",
11
+ "@budibase/types": "2.32.9",
12
12
  "dayjs": "^1.10.8",
13
13
  "lodash": "4.17.21",
14
14
  "shortid": "2.2.15",
15
15
  "socket.io-client": "^4.7.5"
16
16
  },
17
- "gitHead": "e6527e73ac8a4f06197d66a7424b6e5efc17931b"
17
+ "gitHead": "c3d1bac34a42f0707e0cba968a4df9f58fa5088e"
18
18
  }
@@ -2,6 +2,7 @@
2
2
  import { onMount, getContext } from "svelte"
3
3
  import { Dropzone } from "@budibase/bbui"
4
4
  import GridPopover from "../overlays/GridPopover.svelte"
5
+ import { FieldType } from "@budibase/types"
5
6
 
6
7
  export let value
7
8
  export let focused = false
@@ -81,7 +82,12 @@
81
82
  >
82
83
  {#each value || [] as attachment}
83
84
  {#if isImage(attachment.extension)}
84
- <img src={attachment.url} alt={attachment.extension} />
85
+ <img
86
+ class:light={!$props?.darkMode &&
87
+ schema.type === FieldType.SIGNATURE_SINGLE}
88
+ src={attachment.url}
89
+ alt={attachment.extension}
90
+ />
85
91
  {:else}
86
92
  <div class="file" title={attachment.name}>
87
93
  {attachment.extension}
@@ -140,4 +146,9 @@
140
146
  width: 320px;
141
147
  padding: var(--cell-padding);
142
148
  }
149
+
150
+ .attachment-cell img.light {
151
+ -webkit-filter: invert(100%);
152
+ filter: invert(100%);
153
+ }
143
154
  </style>
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  import { getContext, onMount, tick } from "svelte"
3
- import { canBeDisplayColumn, canBeSortColumn } from "@budibase/shared-core"
3
+ import { canBeSortColumn, canBeDisplayColumn } from "@budibase/frontend-core"
4
4
  import { Icon, Menu, MenuItem, Modal } from "@budibase/bbui"
5
5
  import GridCell from "./GridCell.svelte"
6
6
  import { getColumnIcon } from "../lib/utils"
@@ -165,7 +165,17 @@
165
165
  }
166
166
 
167
167
  const hideColumn = () => {
168
- datasource.actions.addSchemaMutation(column.name, { visible: false })
168
+ const { related } = column
169
+ const mutation = { visible: false }
170
+ if (!related) {
171
+ datasource.actions.addSchemaMutation(column.name, mutation)
172
+ } else {
173
+ datasource.actions.addSubSchemaMutation(
174
+ related.subField,
175
+ related.field,
176
+ mutation
177
+ )
178
+ }
169
179
  datasource.actions.saveSchemaMutations()
170
180
  open = false
171
181
  }
@@ -347,15 +357,14 @@
347
357
  <MenuItem
348
358
  icon="Label"
349
359
  on:click={makeDisplayColumn}
350
- disabled={column.primaryDisplay ||
351
- !canBeDisplayColumn(column.schema.type)}
360
+ disabled={column.primaryDisplay || !canBeDisplayColumn(column.schema)}
352
361
  >
353
362
  Use as display column
354
363
  </MenuItem>
355
364
  <MenuItem
356
365
  icon="SortOrderUp"
357
366
  on:click={sortAscending}
358
- disabled={!canBeSortColumn(column.schema.type) ||
367
+ disabled={!canBeSortColumn(column.schema) ||
359
368
  (column.name === $sort.column && $sort.order === "ascending")}
360
369
  >
361
370
  Sort {sortingLabels.ascending}
@@ -363,7 +372,7 @@
363
372
  <MenuItem
364
373
  icon="SortOrderDown"
365
374
  on:click={sortDescending}
366
- disabled={!canBeSortColumn(column.schema.type) ||
375
+ disabled={!canBeSortColumn(column.schema) ||
367
376
  (column.name === $sort.column && $sort.order === "descending")}
368
377
  >
369
378
  Sort {sortingLabels.descending}
@@ -4,13 +4,15 @@
4
4
  import ColumnsSettingContent from "./ColumnsSettingContent.svelte"
5
5
  import { FieldPermissions } from "../../../constants"
6
6
 
7
- const { columns, datasource } = getContext("grid")
7
+ const { tableColumns, datasource } = getContext("grid")
8
8
 
9
9
  let open = false
10
10
  let anchor
11
11
 
12
- $: anyRestricted = $columns.filter(col => !col.visible || col.readonly).length
13
- $: text = anyRestricted ? `Columns: (${anyRestricted} restricted)` : "Columns"
12
+ $: anyRestricted = $tableColumns.filter(
13
+ col => !col.visible || col.readonly
14
+ ).length
15
+ $: text = anyRestricted ? `Columns (${anyRestricted} restricted)` : "Columns"
14
16
  $: permissions =
15
17
  $datasource.type === "viewV2"
16
18
  ? [
@@ -28,12 +30,12 @@
28
30
  size="M"
29
31
  on:click={() => (open = !open)}
30
32
  selected={open || anyRestricted}
31
- disabled={!$columns.length}
33
+ disabled={!$tableColumns.length}
32
34
  >
33
35
  {text}
34
36
  </ActionButton>
35
37
  </div>
36
38
 
37
39
  <Popover bind:open {anchor} align="left">
38
- <ColumnsSettingContent columns={$columns} {permissions} />
40
+ <ColumnsSettingContent columns={$tableColumns} {permissions} />
39
41
  </Popover>
@@ -122,8 +122,10 @@
122
122
  label: name,
123
123
  schema: {
124
124
  type: column.type,
125
+ subtype: column.subtype,
125
126
  visible: column.visible,
126
127
  readonly: column.readonly,
128
+ constraints: column.constraints, // This is needed to properly display "users" column
127
129
  },
128
130
  }
129
131
  })
@@ -1,7 +1,7 @@
1
1
  <script>
2
2
  import { getContext } from "svelte"
3
3
  import { ActionButton, Popover, Select } from "@budibase/bbui"
4
- import { canBeSortColumn } from "@budibase/shared-core"
4
+ import { canBeSortColumn } from "@budibase/frontend-core"
5
5
 
6
6
  const { sort, columns } = getContext("grid")
7
7
 
@@ -13,8 +13,9 @@
13
13
  label: col.label || col.name,
14
14
  value: col.name,
15
15
  type: col.schema?.type,
16
+ related: col.related,
16
17
  }))
17
- .filter(col => canBeSortColumn(col.type))
18
+ .filter(col => canBeSortColumn(col))
18
19
  $: orderOptions = getOrderOptions($sort.column, columnOptions)
19
20
 
20
21
  const getOrderOptions = (column, columnOptions) => {
@@ -35,5 +35,9 @@ const TypeComponentMap = {
35
35
  [FieldType.BB_REFERENCE_SINGLE]: BBReferenceSingleCell,
36
36
  }
37
37
  export const getCellRenderer = column => {
38
- return TypeComponentMap[column?.schema?.type] || TextCell
38
+ return (
39
+ TypeComponentMap[column?.schema?.cellRenderType] ||
40
+ TypeComponentMap[column?.schema?.type] ||
41
+ TextCell
42
+ )
39
43
  }
@@ -42,6 +42,11 @@ export const deriveStores = context => {
42
42
  return map
43
43
  })
44
44
 
45
+ // Derived list of columns which are direct part of the table
46
+ const tableColumns = derived(columns, $columns => {
47
+ return $columns.filter(col => !col.related)
48
+ })
49
+
45
50
  // Derived list of columns which have not been explicitly hidden
46
51
  const visibleColumns = derived(columns, $columns => {
47
52
  return $columns.filter(col => col.visible)
@@ -64,6 +69,7 @@ export const deriveStores = context => {
64
69
  })
65
70
 
66
71
  return {
72
+ tableColumns,
67
73
  displayColumn,
68
74
  columnLookupMap,
69
75
  visibleColumns,
@@ -73,16 +79,24 @@ export const deriveStores = context => {
73
79
  }
74
80
 
75
81
  export const createActions = context => {
76
- const { columns, datasource, schema } = context
82
+ const { columns, datasource } = context
77
83
 
78
84
  // Updates the width of all columns
79
85
  const changeAllColumnWidths = async width => {
80
- const $schema = get(schema)
81
- let mutations = {}
82
- Object.keys($schema).forEach(field => {
83
- mutations[field] = { width }
86
+ const $columns = get(columns)
87
+ $columns.forEach(column => {
88
+ const { related } = column
89
+ const mutation = { width }
90
+ if (!related) {
91
+ datasource.actions.addSchemaMutation(column.name, mutation)
92
+ } else {
93
+ datasource.actions.addSubSchemaMutation(
94
+ related.subField,
95
+ related.field,
96
+ mutation
97
+ )
98
+ }
84
99
  })
85
- datasource.actions.addSchemaMutations(mutations)
86
100
  await datasource.actions.saveSchemaMutations()
87
101
  }
88
102
 
@@ -136,7 +150,7 @@ export const initialise = context => {
136
150
  .map(field => {
137
151
  const fieldSchema = $enrichedSchema[field]
138
152
  const oldColumn = $columns?.find(col => col.name === field)
139
- let column = {
153
+ const column = {
140
154
  name: field,
141
155
  label: fieldSchema.displayName || field,
142
156
  schema: fieldSchema,
@@ -145,6 +159,7 @@ export const initialise = context => {
145
159
  readonly: fieldSchema.readonly,
146
160
  order: fieldSchema.order ?? oldColumn?.order,
147
161
  conditions: fieldSchema.conditions,
162
+ related: fieldSchema.related,
148
163
  }
149
164
  // Override a few properties for primary display
150
165
  if (field === primaryDisplay) {
@@ -1,6 +1,6 @@
1
1
  import { derived, get } from "svelte/store"
2
2
  import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch"
3
- import { memo } from "../../../utils"
3
+ import { enrichSchemaWithRelColumns, memo } from "../../../utils"
4
4
 
5
5
  export const createStores = () => {
6
6
  const definition = memo(null)
@@ -53,10 +53,13 @@ export const deriveStores = context => {
53
53
  if (!$schema) {
54
54
  return null
55
55
  }
56
- let enrichedSchema = {}
57
- Object.keys($schema).forEach(field => {
56
+
57
+ const schemaWithRelatedColumns = enrichSchemaWithRelColumns($schema)
58
+
59
+ const enrichedSchema = {}
60
+ Object.keys(schemaWithRelatedColumns).forEach(field => {
58
61
  enrichedSchema[field] = {
59
- ...$schema[field],
62
+ ...schemaWithRelatedColumns[field],
60
63
  ...$schemaOverrides?.[field],
61
64
  ...$schemaMutations[field],
62
65
  }
@@ -202,24 +205,6 @@ export const createActions = context => {
202
205
  })
203
206
  }
204
207
 
205
- // Adds schema mutations for multiple fields at once
206
- const addSchemaMutations = mutations => {
207
- const fields = Object.keys(mutations || {})
208
- if (!fields.length) {
209
- return
210
- }
211
- schemaMutations.update($schemaMutations => {
212
- let newSchemaMutations = { ...$schemaMutations }
213
- fields.forEach(field => {
214
- newSchemaMutations[field] = {
215
- ...newSchemaMutations[field],
216
- ...mutations[field],
217
- }
218
- })
219
- return newSchemaMutations
220
- })
221
- }
222
-
223
208
  // Saves schema changes to the server, if possible
224
209
  const saveSchemaMutations = async () => {
225
210
  // If we can't save schema changes then we just want to keep this in memory
@@ -309,7 +294,6 @@ export const createActions = context => {
309
294
  changePrimaryDisplay,
310
295
  addSchemaMutation,
311
296
  addSubSchemaMutation,
312
- addSchemaMutations,
313
297
  saveSchemaMutations,
314
298
  resetSchemaMutations,
315
299
  },
@@ -120,25 +120,29 @@ export const initialise = context => {
120
120
  // When sorting changes, ensure view definition is kept up to date
121
121
  unsubscribers.push(
122
122
  sort.subscribe(async $sort => {
123
+ // Ensure we're updating the correct view
124
+ const $view = get(definition)
125
+ if ($view?.id !== $datasource.id) {
126
+ return
127
+ }
128
+
129
+ // Skip if nothing actually changed
130
+ if (
131
+ $sort?.column === $view.sort?.field &&
132
+ $sort?.order === $view.sort?.order
133
+ ) {
134
+ return
135
+ }
136
+
123
137
  // If we can mutate schema then update the view definition
124
138
  if (get(config).canSaveSchema) {
125
- // Ensure we're updating the correct view
126
- const $view = get(definition)
127
- if ($view?.id !== $datasource.id) {
128
- return
129
- }
130
- if (
131
- $sort?.column !== $view.sort?.field ||
132
- $sort?.order !== $view.sort?.order
133
- ) {
134
- await datasource.actions.saveDefinition({
135
- ...$view,
136
- sort: {
137
- field: $sort.column,
138
- order: $sort.order || "ascending",
139
- },
140
- })
141
- }
139
+ await datasource.actions.saveDefinition({
140
+ ...$view,
141
+ sort: {
142
+ field: $sort.column,
143
+ order: $sort.order || "ascending",
144
+ },
145
+ })
142
146
  }
143
147
 
144
148
  // Also update the fetch to ensure the new sort is respected.
@@ -214,11 +214,20 @@ export const createActions = context => {
214
214
  })
215
215
 
216
216
  // Extract new orders as schema mutations
217
- let mutations = {}
218
217
  get(columns).forEach((column, idx) => {
219
- mutations[column.name] = { order: idx }
218
+ const { related } = column
219
+ const mutation = { order: idx }
220
+ if (!related) {
221
+ datasource.actions.addSchemaMutation(column.name, mutation)
222
+ } else {
223
+ datasource.actions.addSubSchemaMutation(
224
+ related.subField,
225
+ related.field,
226
+ mutation
227
+ )
228
+ }
220
229
  })
221
- datasource.actions.addSchemaMutations(mutations)
230
+
222
231
  await datasource.actions.saveSchemaMutations()
223
232
  }
224
233
 
@@ -38,6 +38,7 @@ export const createActions = context => {
38
38
  initialWidth: column.width,
39
39
  initialMouseX: x,
40
40
  column: column.name,
41
+ related: column.related,
41
42
  })
42
43
 
43
44
  // Add mouse event listeners to handle resizing
@@ -50,7 +51,7 @@ export const createActions = context => {
50
51
 
51
52
  // Handler for moving the mouse to resize columns
52
53
  const onResizeMouseMove = e => {
53
- const { initialMouseX, initialWidth, width, column } = get(resize)
54
+ const { initialMouseX, initialWidth, width, column, related } = get(resize)
54
55
  const { x } = parseEventLocation(e)
55
56
  const dx = x - initialMouseX
56
57
  const newWidth = Math.round(Math.max(MinColumnWidth, initialWidth + dx))
@@ -61,7 +62,13 @@ export const createActions = context => {
61
62
  }
62
63
 
63
64
  // Update column state
64
- datasource.actions.addSchemaMutation(column, { width })
65
+ if (!related) {
66
+ datasource.actions.addSchemaMutation(column, { width })
67
+ } else {
68
+ datasource.actions.addSubSchemaMutation(related.subField, related.field, {
69
+ width,
70
+ })
71
+ }
65
72
 
66
73
  // Update state
67
74
  resize.update(state => ({
@@ -6,6 +6,7 @@ import { tick } from "svelte"
6
6
  import { Helpers } from "@budibase/bbui"
7
7
  import { sleep } from "../../../utils/utils"
8
8
  import { FieldType } from "@budibase/types"
9
+ import { getRelatedTableValues } from "../../../utils"
9
10
 
10
11
  export const createStores = () => {
11
12
  const rows = writable([])
@@ -42,15 +43,26 @@ export const createStores = () => {
42
43
  }
43
44
 
44
45
  export const deriveStores = context => {
45
- const { rows } = context
46
+ const { rows, enrichedSchema } = context
46
47
 
47
48
  // Enrich rows with an index property and any pending changes
48
- const enrichedRows = derived(rows, $rows => {
49
- return $rows.map((row, idx) => ({
50
- ...row,
51
- __idx: idx,
52
- }))
53
- })
49
+ const enrichedRows = derived(
50
+ [rows, enrichedSchema],
51
+ ([$rows, $enrichedSchema]) => {
52
+ const customColumns = Object.values($enrichedSchema || {}).filter(
53
+ f => f.related
54
+ )
55
+ return $rows.map((row, idx) => ({
56
+ ...row,
57
+ __idx: idx,
58
+ ...customColumns.reduce((map, column) => {
59
+ const fromField = $enrichedSchema[column.related.field]
60
+ map[column.name] = getRelatedTableValues(row, column, fromField)
61
+ return map
62
+ }, {}),
63
+ }))
64
+ }
65
+ )
54
66
 
55
67
  // Generate a lookup map to quick find a row by ID
56
68
  const rowLookupMap = derived(enrichedRows, $enrichedRows => {
@@ -10,3 +10,5 @@ export { createWebsocket } from "./websocket"
10
10
  export * from "./download"
11
11
  export * from "./theme"
12
12
  export * from "./settings"
13
+ export * from "./relatedColumns"
14
+ export * from "./table"
@@ -0,0 +1,107 @@
1
+ import { FieldType, RelationshipType } from "@budibase/types"
2
+ import { Helpers } from "@budibase/bbui"
3
+
4
+ const columnTypeManyTypeOverrides = {
5
+ [FieldType.DATETIME]: FieldType.STRING,
6
+ [FieldType.BOOLEAN]: FieldType.STRING,
7
+ [FieldType.SIGNATURE_SINGLE]: FieldType.ATTACHMENTS,
8
+ }
9
+
10
+ const columnTypeManyParser = {
11
+ [FieldType.DATETIME]: (value, field) => {
12
+ function parseDate(value) {
13
+ const { timeOnly, dateOnly, ignoreTimezones } = field || {}
14
+ const enableTime = !dateOnly
15
+ const parsedValue = Helpers.parseDate(value, {
16
+ timeOnly,
17
+ enableTime,
18
+ ignoreTimezones,
19
+ })
20
+ const parsed = Helpers.getDateDisplayValue(parsedValue, {
21
+ enableTime,
22
+ timeOnly,
23
+ })
24
+ return parsed
25
+ }
26
+
27
+ return value?.map(v => parseDate(v))
28
+ },
29
+ [FieldType.BOOLEAN]: value => value?.map(v => !!v),
30
+ [FieldType.BB_REFERENCE_SINGLE]: value => [
31
+ ...new Map(value.map(i => [i._id, i])).values(),
32
+ ],
33
+ [FieldType.BB_REFERENCE]: value => [
34
+ ...new Map(value.map(i => [i._id, i])).values(),
35
+ ],
36
+ [FieldType.ARRAY]: value => Array.from(new Set(value)),
37
+ }
38
+
39
+ export function enrichSchemaWithRelColumns(schema) {
40
+ if (!schema) {
41
+ return
42
+ }
43
+ const result = Object.keys(schema).reduce((result, fieldName) => {
44
+ const field = schema[fieldName]
45
+ result[fieldName] = field
46
+
47
+ if (field.visible !== false && field.columns) {
48
+ const fromSingle =
49
+ field?.relationshipType === RelationshipType.ONE_TO_MANY
50
+
51
+ for (const relColumn of Object.keys(field.columns)) {
52
+ const relField = field.columns[relColumn]
53
+ if (!relField.visible) {
54
+ continue
55
+ }
56
+ const name = `${field.name}.${relColumn}`
57
+ result[name] = {
58
+ ...relField,
59
+ name,
60
+ related: { field: fieldName, subField: relColumn },
61
+ cellRenderType:
62
+ (!fromSingle && columnTypeManyTypeOverrides[relField.type]) ||
63
+ relField.type,
64
+ }
65
+ }
66
+ }
67
+ return result
68
+ }, {})
69
+
70
+ return result
71
+ }
72
+
73
+ export function getRelatedTableValues(row, field, fromField) {
74
+ const fromSingle =
75
+ fromField?.relationshipType === RelationshipType.ONE_TO_MANY
76
+
77
+ let result = ""
78
+
79
+ if (fromSingle) {
80
+ result = row[field.related.field]?.[0]?.[field.related.subField]
81
+ } else {
82
+ const parser = columnTypeManyParser[field.type] || (value => value)
83
+
84
+ result = parser(
85
+ row[field.related.field]
86
+ ?.flatMap(r => r[field.related.subField])
87
+ ?.filter(i => i !== undefined && i !== null),
88
+ field
89
+ )
90
+
91
+ if (
92
+ [
93
+ FieldType.STRING,
94
+ FieldType.NUMBER,
95
+ FieldType.BIGINT,
96
+ FieldType.BOOLEAN,
97
+ FieldType.DATETIME,
98
+ FieldType.LONGFORM,
99
+ FieldType.BARCODEQR,
100
+ ].includes(field.type)
101
+ ) {
102
+ result = result?.join(", ")
103
+ }
104
+ }
105
+
106
+ return result
107
+ }
@@ -0,0 +1,27 @@
1
+ import * as sharedCore from "@budibase/shared-core"
2
+
3
+ export function canBeDisplayColumn(column) {
4
+ if (!sharedCore.canBeDisplayColumn(column.type)) {
5
+ return false
6
+ }
7
+
8
+ if (column.related) {
9
+ // If it's a related column (only available in the frontend), don't allow using it as display column
10
+ return false
11
+ }
12
+
13
+ return true
14
+ }
15
+
16
+ export function canBeSortColumn(column) {
17
+ if (!sharedCore.canBeSortColumn(column.type)) {
18
+ return false
19
+ }
20
+
21
+ if (column.related) {
22
+ // If it's a related column (only available in the frontend), don't allow using it as display column
23
+ return false
24
+ }
25
+
26
+ return true
27
+ }