@budibase/frontend-core 2.31.3 → 2.31.8

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.31.3",
3
+ "version": "2.31.8",
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.31.3",
10
- "@budibase/shared-core": "2.31.3",
11
- "@budibase/types": "2.31.3",
9
+ "@budibase/bbui": "2.31.8",
10
+ "@budibase/shared-core": "2.31.8",
11
+ "@budibase/types": "2.31.8",
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": "f25fc42a409fd0ddb3492d6b622e63e2e8d25de4"
17
+ "gitHead": "773a06abcdbf7b7bed843f909afc5ca2b11e7ce2"
18
18
  }
package/src/api/user.js CHANGED
@@ -295,4 +295,10 @@ export const buildUserEndpoints = API => ({
295
295
  url: `/api/global/users/${userId}/app/${appId}/builder`,
296
296
  })
297
297
  },
298
+
299
+ getTenantInfo: async ({ tenantId }) => {
300
+ return await API.get({
301
+ url: `/api/global/tenant/${tenantId}`,
302
+ })
303
+ },
298
304
  })
@@ -29,6 +29,7 @@
29
29
  let searching = false
30
30
  let container
31
31
  let anchor
32
+ let relationshipFields
32
33
 
33
34
  $: fieldValue = parseValue(value)
34
35
  $: oneRowOnly = schema?.relationshipType === "one-to-many"
@@ -41,6 +42,26 @@
41
42
  }
42
43
  }
43
44
 
45
+ $: relationFields = fieldValue?.reduce((acc, f) => {
46
+ const fields = {}
47
+ for (const [column] of Object.entries(schema?.columns || {}).filter(
48
+ ([key, column]) =>
49
+ column.visible !== false && f[key] !== null && f[key] !== undefined
50
+ )) {
51
+ fields[column] = f[column]
52
+ }
53
+ if (Object.keys(fields).length) {
54
+ acc[f._id] = fields
55
+ }
56
+ return acc
57
+ }, {})
58
+
59
+ $: showRelationshipFields =
60
+ relationshipFields &&
61
+ Object.keys(relationshipFields).length &&
62
+ focused &&
63
+ !isOpen
64
+
44
65
  const parseValue = value => {
45
66
  if (Array.isArray(value) && value.every(x => x?._id)) {
46
67
  return value
@@ -221,6 +242,14 @@
221
242
  return value
222
243
  }
223
244
 
245
+ const displayRelationshipFields = relationship => {
246
+ relationshipFields = relationFields[relationship._id]
247
+ }
248
+
249
+ const hideRelationshipFields = () => {
250
+ relationshipFields = undefined
251
+ }
252
+
224
253
  onMount(() => {
225
254
  api = {
226
255
  focus: open,
@@ -244,11 +273,18 @@
244
273
  <div
245
274
  class="values"
246
275
  class:wrap={editable || contentLines > 1}
276
+ class:disabled={!focused}
247
277
  on:wheel={e => (focused ? e.stopPropagation() : null)}
248
278
  >
249
279
  {#each fieldValue || [] as relationship}
250
280
  {#if relationship[primaryDisplay] || relationship.primaryDisplay}
251
- <div class="badge">
281
+ <div
282
+ class="badge"
283
+ class:extra-info={!!relationFields[relationship._id]}
284
+ on:mouseover={() => displayRelationshipFields(relationship)}
285
+ on:focus={() => {}}
286
+ on:mouseleave={() => hideRelationshipFields()}
287
+ >
252
288
  <span>
253
289
  {readable(
254
290
  relationship[primaryDisplay] || relationship.primaryDisplay
@@ -322,6 +358,21 @@
322
358
  </GridPopover>
323
359
  {/if}
324
360
 
361
+ {#if showRelationshipFields}
362
+ <GridPopover {anchor} minWidth={300} maxWidth={400}>
363
+ <div class="relationship-fields">
364
+ {#each Object.entries(relationshipFields) as [fieldName, fieldValue]}
365
+ <div class="relationship-field-name">
366
+ {fieldName}
367
+ </div>
368
+ <div class="relationship-field-value">
369
+ {fieldValue}
370
+ </div>
371
+ {/each}
372
+ </div>
373
+ </GridPopover>
374
+ {/if}
375
+
325
376
  <style>
326
377
  .wrapper {
327
378
  flex: 1 1 auto;
@@ -376,6 +427,9 @@
376
427
  padding: var(--cell-padding);
377
428
  flex-wrap: nowrap;
378
429
  }
430
+ .values.disabled {
431
+ pointer-events: none;
432
+ }
379
433
  .values.wrap {
380
434
  flex-wrap: wrap;
381
435
  }
@@ -407,6 +461,13 @@
407
461
  height: 20px;
408
462
  max-width: 100%;
409
463
  }
464
+ .values.wrap .badge:hover {
465
+ filter: brightness(1.25);
466
+ }
467
+ .values.wrap .badge.extra-info {
468
+ cursor: pointer;
469
+ }
470
+
410
471
  .badge span {
411
472
  overflow: hidden;
412
473
  white-space: nowrap;
@@ -478,4 +539,25 @@
478
539
  .search :global(.spectrum-Form-item) {
479
540
  flex: 1 1 auto;
480
541
  }
542
+
543
+ .relationship-fields {
544
+ margin: var(--spacing-m) var(--spacing-l);
545
+ display: grid;
546
+ grid-template-columns: minmax(auto, 50%) auto;
547
+ grid-row-gap: var(--spacing-m);
548
+ grid-column-gap: var(--spacing-m);
549
+ }
550
+
551
+ .relationship-field-name {
552
+ text-transform: uppercase;
553
+ color: var(--spectrum-global-color-gray-600);
554
+ font-size: var(--font-size-xs);
555
+ }
556
+ .relationship-field-value {
557
+ overflow: hidden;
558
+ display: -webkit-box;
559
+ -webkit-box-orient: vertical;
560
+ -webkit-line-clamp: 3;
561
+ line-clamp: 3;
562
+ }
481
563
  </style>
@@ -2,16 +2,29 @@
2
2
  import { getContext } from "svelte"
3
3
  import { ActionButton, Popover } from "@budibase/bbui"
4
4
  import ColumnsSettingContent from "./ColumnsSettingContent.svelte"
5
+ import { FieldPermissions } from "../../../constants"
5
6
 
6
7
  export let allowViewReadonlyColumns = false
7
8
 
8
- const { columns } = getContext("grid")
9
+ const { columns, datasource } = getContext("grid")
9
10
 
10
11
  let open = false
11
12
  let anchor
12
13
 
13
14
  $: anyRestricted = $columns.filter(col => !col.visible || col.readonly).length
14
15
  $: text = anyRestricted ? `Columns (${anyRestricted} restricted)` : "Columns"
16
+
17
+ $: permissions =
18
+ $datasource.type === "viewV2"
19
+ ? [
20
+ FieldPermissions.WRITABLE,
21
+ FieldPermissions.READONLY,
22
+ FieldPermissions.HIDDEN,
23
+ ]
24
+ : [FieldPermissions.WRITABLE, FieldPermissions.HIDDEN]
25
+ $: disabledPermissions = allowViewReadonlyColumns
26
+ ? []
27
+ : [FieldPermissions.READONLY]
15
28
  </script>
16
29
 
17
30
  <div bind:this={anchor}>
@@ -28,5 +41,9 @@
28
41
  </div>
29
42
 
30
43
  <Popover bind:open {anchor} align="left">
31
- <ColumnsSettingContent columns={$columns} {allowViewReadonlyColumns} />
44
+ <ColumnsSettingContent
45
+ columns={$columns}
46
+ {permissions}
47
+ {disabledPermissions}
48
+ />
32
49
  </Popover>
@@ -1,87 +1,183 @@
1
1
  <script>
2
2
  import { getContext } from "svelte"
3
- import { Icon, notifications } from "@budibase/bbui"
3
+ import { Icon, notifications, ActionButton, Popover } from "@budibase/bbui"
4
4
  import { getColumnIcon } from "../lib/utils"
5
5
  import ToggleActionButtonGroup from "./ToggleActionButtonGroup.svelte"
6
6
  import { helpers } from "@budibase/shared-core"
7
+ import { FieldType } from "@budibase/types"
8
+ import { FieldPermissions } from "../../../constants"
7
9
 
8
- export let allowViewReadonlyColumns = false
10
+ export let permissions = [FieldPermissions.WRITABLE, FieldPermissions.HIDDEN]
11
+ export let disabledPermissions = []
12
+ export let columns
13
+ export let fromRelationshipField
9
14
 
10
- const { columns, datasource, dispatch } = getContext("grid")
15
+ const { datasource, dispatch, cache, config } = getContext("grid")
11
16
 
12
- const toggleColumn = async (column, permission) => {
13
- const visible = permission !== PERMISSION_OPTIONS.HIDDEN
14
- const readonly = permission === PERMISSION_OPTIONS.READONLY
17
+ $: canSetRelationshipSchemas = $config.canSetRelationshipSchemas
15
18
 
16
- await datasource.actions.addSchemaMutation(column.name, {
17
- visible,
18
- readonly,
19
- })
20
- try {
21
- await datasource.actions.saveSchemaMutations()
22
- } catch (e) {
23
- notifications.error(e.message)
24
- } finally {
25
- await datasource.actions.resetSchemaMutations()
26
- await datasource.actions.refreshDefinition()
19
+ let relationshipPanelAnchor
20
+ let relationshipFieldName
21
+
22
+ $: relationshipField = columns.find(
23
+ c => c.name === relationshipFieldName
24
+ )?.schema
25
+ $: permissionsObj = permissions.reduce(
26
+ (acc, c) => ({
27
+ ...acc,
28
+ [c]: {
29
+ disabled: disabledPermissions.includes(c),
30
+ },
31
+ }),
32
+ {}
33
+ )
34
+
35
+ $: displayColumns = columns.map(c => {
36
+ const isRequired =
37
+ c.primaryDisplay || helpers.schema.isRequired(c.schema.constraints)
38
+
39
+ const defaultPermission = permissions[0]
40
+ const requiredTooltips = {
41
+ [FieldPermissions.WRITABLE]: (() => {
42
+ if (defaultPermission === FieldPermissions.WRITABLE) {
43
+ if (c.primaryDisplay) {
44
+ return "Display column must be writable"
45
+ }
46
+ if (isRequired) {
47
+ return "Required columns must be writable"
48
+ }
49
+ }
50
+ })(),
51
+ [FieldPermissions.READONLY]: (() => {
52
+ if (defaultPermission === FieldPermissions.WRITABLE) {
53
+ if (c.primaryDisplay) {
54
+ return "Display column cannot be read-only"
55
+ }
56
+ if (isRequired) {
57
+ return "Required columns cannot be read-only"
58
+ }
59
+ }
60
+ if (defaultPermission === FieldPermissions.READONLY) {
61
+ if (c.primaryDisplay) {
62
+ return "Display column must be read-only"
63
+ }
64
+ if (isRequired) {
65
+ return "Required columns must be read-only"
66
+ }
67
+ }
68
+ })(),
69
+ [FieldPermissions.HIDDEN]: (() => {
70
+ if (c.primaryDisplay) {
71
+ return "Display column cannot be hidden"
72
+ }
73
+ if (isRequired) {
74
+ return "Required columns cannot be hidden"
75
+ }
76
+ })(),
27
77
  }
28
- dispatch(visible ? "show-column" : "hide-column")
29
- }
30
78
 
31
- const PERMISSION_OPTIONS = {
32
- WRITABLE: "writable",
33
- READONLY: "readonly",
34
- HIDDEN: "hidden",
35
- }
79
+ const options = []
36
80
 
37
- $: displayColumns = $columns.map(c => {
38
- const isRequired = helpers.schema.isRequired(c.schema.constraints)
39
- const requiredTooltip = isRequired && "Required columns must be writable"
40
- const editEnabled =
41
- !isRequired ||
42
- columnToPermissionOptions(c) !== PERMISSION_OPTIONS.WRITABLE
43
- const options = [
44
- {
81
+ let permission
82
+ if ((permission = permissionsObj[FieldPermissions.WRITABLE])) {
83
+ const tooltip = requiredTooltips[FieldPermissions.WRITABLE] || "Writable"
84
+ options.push({
45
85
  icon: "Edit",
46
- value: PERMISSION_OPTIONS.WRITABLE,
47
- tooltip: (!editEnabled && requiredTooltip) || "Writable",
48
- disabled: !editEnabled,
49
- },
50
- ]
51
- if ($datasource.type === "viewV2") {
86
+ value: FieldPermissions.WRITABLE,
87
+ tooltip,
88
+ disabled: isRequired || permission.disabled,
89
+ })
90
+ }
91
+
92
+ if ((permission = permissionsObj[FieldPermissions.READONLY])) {
93
+ const tooltip =
94
+ (requiredTooltips[FieldPermissions.READONLY] || "Read-only") +
95
+ (permission.disabled ? " (premium feature)" : "")
52
96
  options.push({
53
97
  icon: "Visibility",
54
- value: PERMISSION_OPTIONS.READONLY,
55
- tooltip: allowViewReadonlyColumns
56
- ? requiredTooltip || "Read only"
57
- : "Read only (premium feature)",
58
- disabled: !allowViewReadonlyColumns || isRequired,
98
+ value: FieldPermissions.READONLY,
99
+ tooltip,
100
+ disabled: permission.disabled || isRequired,
59
101
  })
60
102
  }
61
103
 
62
- options.push({
63
- icon: "VisibilityOff",
64
- value: PERMISSION_OPTIONS.HIDDEN,
65
- disabled: c.primaryDisplay || isRequired,
66
- tooltip:
67
- (c.primaryDisplay && "Display column cannot be hidden") ||
68
- requiredTooltip ||
69
- "Hidden",
70
- })
104
+ if ((permission = permissionsObj[FieldPermissions.HIDDEN])) {
105
+ const tooltip = requiredTooltips[FieldPermissions.HIDDEN] || "Hidden"
106
+ options.push({
107
+ icon: "VisibilityOff",
108
+ value: FieldPermissions.HIDDEN,
109
+ disabled: permission.disabled || isRequired,
110
+ tooltip,
111
+ })
112
+ }
71
113
 
72
114
  return { ...c, options }
73
115
  })
74
116
 
117
+ let relationshipPanelColumns = []
118
+ async function fetchRelationshipPanelColumns(relationshipField) {
119
+ relationshipPanelColumns = []
120
+ if (!relationshipField) {
121
+ return
122
+ }
123
+
124
+ const table = await cache.actions.getTable(relationshipField.tableId)
125
+ relationshipPanelColumns = Object.entries(
126
+ relationshipField?.columns || {}
127
+ ).map(([name, column]) => {
128
+ return {
129
+ name: name,
130
+ label: name,
131
+ schema: {
132
+ type: table.schema[name].type,
133
+ visible: column.visible,
134
+ readonly: column.readonly,
135
+ },
136
+ }
137
+ })
138
+ }
139
+ $: fetchRelationshipPanelColumns(relationshipField)
140
+
141
+ async function toggleColumn(column, permission) {
142
+ const visible = permission !== FieldPermissions.HIDDEN
143
+ const readonly = permission === FieldPermissions.READONLY
144
+
145
+ if (!fromRelationshipField) {
146
+ await datasource.actions.addSchemaMutation(column.name, {
147
+ visible,
148
+ readonly,
149
+ })
150
+ } else {
151
+ await datasource.actions.addSubSchemaMutation(
152
+ column.name,
153
+ fromRelationshipField.name,
154
+ {
155
+ visible,
156
+ readonly,
157
+ }
158
+ )
159
+ }
160
+ try {
161
+ await datasource.actions.saveSchemaMutations()
162
+ } catch (e) {
163
+ notifications.error(e.message)
164
+ } finally {
165
+ await datasource.actions.resetSchemaMutations()
166
+ await datasource.actions.refreshDefinition()
167
+ }
168
+ dispatch(visible ? "show-column" : "hide-column")
169
+ }
170
+
75
171
  function columnToPermissionOptions(column) {
76
172
  if (column.schema.visible === false) {
77
- return PERMISSION_OPTIONS.HIDDEN
173
+ return FieldPermissions.HIDDEN
78
174
  }
79
175
 
80
176
  if (column.schema.readonly) {
81
- return PERMISSION_OPTIONS.READONLY
177
+ return FieldPermissions.READONLY
82
178
  }
83
179
 
84
- return PERMISSION_OPTIONS.WRITABLE
180
+ return FieldPermissions.WRITABLE
85
181
  }
86
182
  </script>
87
183
 
@@ -94,16 +190,56 @@
94
190
  {column.label}
95
191
  </div>
96
192
  </div>
97
- <ToggleActionButtonGroup
98
- on:click={e => toggleColumn(column, e.detail)}
99
- value={columnToPermissionOptions(column)}
100
- options={column.options}
101
- />
193
+ <div class="column-options">
194
+ <ToggleActionButtonGroup
195
+ on:click={e => toggleColumn(column, e.detail)}
196
+ value={columnToPermissionOptions(column)}
197
+ options={column.options}
198
+ />
199
+ {#if canSetRelationshipSchemas && column.schema.type === FieldType.LINK && columnToPermissionOptions(column) !== FieldPermissions.HIDDEN}
200
+ <div class="relationship-columns">
201
+ <ActionButton
202
+ on:click={e => {
203
+ relationshipFieldName = column.name
204
+ relationshipPanelAnchor = e.currentTarget
205
+ }}
206
+ size="S"
207
+ icon="ChevronRight"
208
+ quiet
209
+ />
210
+ </div>
211
+ {/if}
212
+ </div>
102
213
  {/each}
103
214
  </div>
104
215
  </div>
105
216
 
217
+ {#if canSetRelationshipSchemas}
218
+ <Popover
219
+ on:close={() => (relationshipFieldName = null)}
220
+ open={relationshipFieldName}
221
+ anchor={relationshipPanelAnchor}
222
+ align="right-outside"
223
+ >
224
+ {#if relationshipPanelColumns.length}
225
+ <div class="relationship-header">
226
+ {relationshipFieldName} columns
227
+ </div>
228
+ {/if}
229
+ <svelte:self
230
+ columns={relationshipPanelColumns}
231
+ permissions={[FieldPermissions.READONLY, FieldPermissions.HIDDEN]}
232
+ fromRelationshipField={relationshipField}
233
+ />
234
+ </Popover>
235
+ {/if}
236
+
106
237
  <style>
238
+ .relationship-columns :global(.spectrum-ActionButton) {
239
+ width: 28px;
240
+ height: 28px;
241
+ }
242
+
107
243
  .content {
108
244
  padding: 12px 12px;
109
245
  display: flex;
@@ -131,4 +267,12 @@
131
267
  white-space: nowrap;
132
268
  overflow: hidden;
133
269
  }
270
+ .column-options {
271
+ display: flex;
272
+ gap: var(--spacing-xs);
273
+ }
274
+ .relationship-header {
275
+ color: var(--spectrum-global-color-gray-600);
276
+ padding: 12px 12px 0 12px;
277
+ }
134
278
  </style>
@@ -43,6 +43,7 @@
43
43
  export let canDeleteRows = true
44
44
  export let canEditColumns = true
45
45
  export let canSaveSchema = true
46
+ export let canSetRelationshipSchemas = false
46
47
  export let stripeRows = false
47
48
  export let quiet = false
48
49
  export let collaboration = true
@@ -99,6 +100,7 @@
99
100
  canDeleteRows,
100
101
  canEditColumns,
101
102
  canSaveSchema,
103
+ canSetRelationshipSchemas,
102
104
  stripeRows,
103
105
  quiet,
104
106
  collaboration,
@@ -4,35 +4,40 @@ export const createActions = context => {
4
4
  // Cache for the primary display columns of different tables.
5
5
  // If we ever need to cache table definitions for other purposes then we can
6
6
  // expand this to be a more generic cache.
7
- let primaryDisplayCache = {}
7
+ let tableCache = {}
8
8
 
9
- const resetPrimaryDisplayCache = () => {
10
- primaryDisplayCache = {}
9
+ const resetCache = () => {
10
+ tableCache = {}
11
11
  }
12
12
 
13
- const getPrimaryDisplayForTableId = async tableId => {
13
+ const fetchTable = async tableId => {
14
14
  // If we've never encountered this tableId before then store a promise that
15
15
  // resolves to the primary display so that subsequent invocations before the
16
16
  // promise completes can reuse this promise
17
- if (!primaryDisplayCache[tableId]) {
18
- primaryDisplayCache[tableId] = new Promise(resolve => {
19
- API.fetchTableDefinition(tableId).then(def => {
20
- const display = def?.primaryDisplay || def?.schema?.[0]?.name
21
- primaryDisplayCache[tableId] = display
22
- resolve(display)
23
- })
24
- })
17
+ if (!tableCache[tableId]) {
18
+ tableCache[tableId] = API.fetchTableDefinition(tableId)
25
19
  }
26
-
27
20
  // We await the result so that we account for both promises and primitives
28
- return await primaryDisplayCache[tableId]
21
+ return await tableCache[tableId]
22
+ }
23
+
24
+ const getPrimaryDisplayForTableId = async tableId => {
25
+ const table = await fetchTable(tableId)
26
+ const display = table?.primaryDisplay || table?.schema?.[0]?.name
27
+ return display
28
+ }
29
+
30
+ const getTable = async tableId => {
31
+ const table = await fetchTable(tableId)
32
+ return table
29
33
  }
30
34
 
31
35
  return {
32
36
  cache: {
33
37
  actions: {
34
38
  getPrimaryDisplayForTableId,
35
- resetPrimaryDisplayCache,
39
+ getTable,
40
+ resetCache,
36
41
  },
37
42
  },
38
43
  }
@@ -43,5 +48,5 @@ export const initialise = context => {
43
48
 
44
49
  // Wipe the caches whenever the datasource changes to ensure we aren't
45
50
  // storing any stale information
46
- datasource.subscribe(cache.actions.resetPrimaryDisplayCache)
51
+ datasource.subscribe(cache.actions.resetCache)
47
52
  }
@@ -5,16 +5,24 @@ import { memo } from "../../../utils"
5
5
  export const createStores = () => {
6
6
  const definition = memo(null)
7
7
  const schemaMutations = memo({})
8
+ const subSchemaMutations = memo({})
8
9
 
9
10
  return {
10
11
  definition,
11
12
  schemaMutations,
13
+ subSchemaMutations,
12
14
  }
13
15
  }
14
16
 
15
17
  export const deriveStores = context => {
16
- const { API, definition, schemaOverrides, datasource, schemaMutations } =
17
- context
18
+ const {
19
+ API,
20
+ definition,
21
+ schemaOverrides,
22
+ datasource,
23
+ schemaMutations,
24
+ subSchemaMutations,
25
+ } = context
18
26
 
19
27
  const schema = derived(definition, $definition => {
20
28
  let schema = getDatasourceSchema({
@@ -40,8 +48,8 @@ export const deriveStores = context => {
40
48
  // Derives the total enriched schema, made up of the saved schema and any
41
49
  // prop and user overrides
42
50
  const enrichedSchema = derived(
43
- [schema, schemaOverrides, schemaMutations],
44
- ([$schema, $schemaOverrides, $schemaMutations]) => {
51
+ [schema, schemaOverrides, schemaMutations, subSchemaMutations],
52
+ ([$schema, $schemaOverrides, $schemaMutations, $subSchemaMutations]) => {
45
53
  if (!$schema) {
46
54
  return null
47
55
  }
@@ -52,6 +60,18 @@ export const deriveStores = context => {
52
60
  ...$schemaOverrides?.[field],
53
61
  ...$schemaMutations[field],
54
62
  }
63
+
64
+ if ($subSchemaMutations[field]) {
65
+ enrichedSchema[field].columns ??= {}
66
+ for (const [fieldName, mutation] of Object.entries(
67
+ $subSchemaMutations[field]
68
+ )) {
69
+ enrichedSchema[field].columns[fieldName] = {
70
+ ...enrichedSchema[field].columns[fieldName],
71
+ ...mutation,
72
+ }
73
+ }
74
+ }
55
75
  })
56
76
  return enrichedSchema
57
77
  }
@@ -83,6 +103,7 @@ export const createActions = context => {
83
103
  viewV2,
84
104
  nonPlus,
85
105
  schemaMutations,
106
+ subSchemaMutations,
86
107
  schema,
87
108
  notifications,
88
109
  } = context
@@ -162,6 +183,25 @@ export const createActions = context => {
162
183
  })
163
184
  }
164
185
 
186
+ // Adds a nested schema mutation for a single field
187
+ const addSubSchemaMutation = (field, fromField, mutation) => {
188
+ if (!field || !fromField || !mutation) {
189
+ return
190
+ }
191
+ subSchemaMutations.update($subSchemaMutations => {
192
+ return {
193
+ ...$subSchemaMutations,
194
+ [fromField]: {
195
+ ...$subSchemaMutations[fromField],
196
+ [field]: {
197
+ ...($subSchemaMutations[fromField] || {})[field],
198
+ ...mutation,
199
+ },
200
+ },
201
+ }
202
+ })
203
+ }
204
+
165
205
  // Adds schema mutations for multiple fields at once
166
206
  const addSchemaMutations = mutations => {
167
207
  const fields = Object.keys(mutations || {})
@@ -188,6 +228,7 @@ export const createActions = context => {
188
228
  }
189
229
  const $definition = get(definition)
190
230
  const $schemaMutations = get(schemaMutations)
231
+ const $subSchemaMutations = get(subSchemaMutations)
191
232
  const $schema = get(schema)
192
233
  let newSchema = {}
193
234
 
@@ -197,6 +238,17 @@ export const createActions = context => {
197
238
  ...$schema[column],
198
239
  ...$schemaMutations[column],
199
240
  }
241
+ if ($subSchemaMutations[column]) {
242
+ newSchema[column].columns ??= {}
243
+ for (const [fieldName, mutation] of Object.entries(
244
+ $subSchemaMutations[column]
245
+ )) {
246
+ newSchema[column].columns[fieldName] = {
247
+ ...newSchema[column].columns[fieldName],
248
+ ...mutation,
249
+ }
250
+ }
251
+ }
200
252
  })
201
253
 
202
254
  // Save the changes, then reset our local mutations
@@ -209,6 +261,7 @@ export const createActions = context => {
209
261
 
210
262
  const resetSchemaMutations = () => {
211
263
  schemaMutations.set({})
264
+ subSchemaMutations.set({})
212
265
  }
213
266
 
214
267
  // Adds a row to the datasource
@@ -255,6 +308,7 @@ export const createActions = context => {
255
308
  canUseColumn,
256
309
  changePrimaryDisplay,
257
310
  addSchemaMutation,
311
+ addSubSchemaMutation,
258
312
  addSchemaMutations,
259
313
  saveSchemaMutations,
260
314
  resetSchemaMutations,
package/src/constants.js CHANGED
@@ -41,6 +41,7 @@ export const BudibaseRoles = {
41
41
  Developer: "developer",
42
42
  Creator: "creator",
43
43
  Admin: "admin",
44
+ Owner: "owner",
44
45
  }
45
46
 
46
47
  export const BudibaseRoleOptionsOld = [
@@ -54,18 +55,30 @@ export const BudibaseRoleOptions = [
54
55
  label: "Account admin",
55
56
  value: BudibaseRoles.Admin,
56
57
  subtitle: "Has full access to all apps and settings in your account",
58
+ sortOrder: 1,
57
59
  },
58
60
  {
59
61
  label: "Creator",
60
62
  value: BudibaseRoles.Creator,
61
63
  subtitle: "Can create and edit apps they have access to",
64
+ sortOrder: 2,
62
65
  },
63
66
  {
64
67
  label: "App user",
65
68
  value: BudibaseRoles.AppUser,
66
69
  subtitle: "Can only use published apps they have access to",
70
+ sortOrder: 3,
67
71
  },
68
72
  ]
73
+ export const ExtendedBudibaseRoleOptions = [
74
+ {
75
+ label: "Account holder",
76
+ value: BudibaseRoles.Owner,
77
+ sortOrder: 0,
78
+ },
79
+ ]
80
+ .concat(BudibaseRoleOptions)
81
+ .concat(BudibaseRoleOptionsOld)
69
82
 
70
83
  export const PlanType = {
71
84
  FREE: "free",
@@ -161,3 +174,9 @@ export const TypeIconMap = {
161
174
  export const OptionColours = [...new Array(12).keys()].map(idx => {
162
175
  return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, 0.3)`
163
176
  })
177
+
178
+ export const FieldPermissions = {
179
+ WRITABLE: "writable",
180
+ READONLY: "readonly",
181
+ HIDDEN: "hidden",
182
+ }