@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 +5 -5
- package/src/api/user.js +6 -0
- package/src/components/grid/cells/RelationshipCell.svelte +83 -1
- package/src/components/grid/controls/ColumnsSettingButton.svelte +19 -2
- package/src/components/grid/controls/ColumnsSettingContent.svelte +204 -60
- package/src/components/grid/layout/Grid.svelte +2 -0
- package/src/components/grid/stores/cache.js +21 -16
- package/src/components/grid/stores/datasource.js +58 -4
- package/src/constants.js +19 -0
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "2.31.
|
|
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.
|
|
10
|
-
"@budibase/shared-core": "2.31.
|
|
11
|
-
"@budibase/types": "2.31.
|
|
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": "
|
|
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
|
|
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
|
|
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
|
|
10
|
+
export let permissions = [FieldPermissions.WRITABLE, FieldPermissions.HIDDEN]
|
|
11
|
+
export let disabledPermissions = []
|
|
12
|
+
export let columns
|
|
13
|
+
export let fromRelationshipField
|
|
9
14
|
|
|
10
|
-
const {
|
|
15
|
+
const { datasource, dispatch, cache, config } = getContext("grid")
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
const visible = permission !== PERMISSION_OPTIONS.HIDDEN
|
|
14
|
-
const readonly = permission === PERMISSION_OPTIONS.READONLY
|
|
17
|
+
$: canSetRelationshipSchemas = $config.canSetRelationshipSchemas
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
32
|
-
WRITABLE: "writable",
|
|
33
|
-
READONLY: "readonly",
|
|
34
|
-
HIDDEN: "hidden",
|
|
35
|
-
}
|
|
79
|
+
const options = []
|
|
36
80
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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:
|
|
47
|
-
tooltip
|
|
48
|
-
disabled:
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
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:
|
|
55
|
-
tooltip
|
|
56
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
173
|
+
return FieldPermissions.HIDDEN
|
|
78
174
|
}
|
|
79
175
|
|
|
80
176
|
if (column.schema.readonly) {
|
|
81
|
-
return
|
|
177
|
+
return FieldPermissions.READONLY
|
|
82
178
|
}
|
|
83
179
|
|
|
84
|
-
return
|
|
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
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
7
|
+
let tableCache = {}
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
|
|
9
|
+
const resetCache = () => {
|
|
10
|
+
tableCache = {}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const
|
|
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 (!
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
17
|
-
|
|
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
|
+
}
|