@budibase/frontend-core 2.33.14 → 3.0.0
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/automations.js +2 -7
- package/src/api/index.js +2 -0
- package/src/api/rowActions.js +90 -0
- package/src/api/tables.js +1 -1
- package/src/api/viewsV2.js +1 -0
- package/src/components/CoreFilterBuilder.svelte +532 -0
- package/src/components/FilterField.svelte +319 -0
- package/src/components/FilterUsers.svelte +2 -1
- package/src/components/grid/cells/AICell.svelte +99 -0
- package/src/components/grid/cells/DataCell.svelte +1 -1
- package/src/components/grid/cells/GridCell.svelte +6 -1
- package/src/components/grid/cells/HeaderCell.svelte +20 -11
- package/src/components/grid/cells/NumberCell.svelte +23 -1
- package/src/components/grid/cells/RelationshipCell.svelte +1 -3
- package/src/components/grid/cells/RoleCell.svelte +45 -0
- package/src/components/grid/cells/TextCell.svelte +3 -1
- package/src/components/grid/layout/ButtonColumn.svelte +67 -36
- package/src/components/grid/layout/Grid.svelte +22 -27
- package/src/components/grid/lib/constants.js +2 -0
- package/src/components/grid/lib/renderers.js +9 -0
- package/src/components/grid/lib/utils.js +13 -31
- package/src/components/grid/lib/websocket.js +9 -0
- package/src/components/grid/overlays/GridPopover.svelte +4 -2
- package/src/components/grid/overlays/MenuOverlay.svelte +19 -0
- package/src/components/grid/stores/columns.js +2 -1
- package/src/components/grid/stores/config.js +12 -3
- package/src/components/grid/stores/datasource.js +27 -10
- package/src/components/grid/stores/datasources/nonPlus.js +3 -2
- package/src/components/grid/stores/datasources/table.js +3 -2
- package/src/components/grid/stores/datasources/viewV2.js +58 -27
- package/src/components/grid/stores/filter.js +25 -7
- package/src/components/grid/stores/rows.js +11 -6
- package/src/components/grid/stores/sort.js +7 -3
- package/src/components/index.js +1 -1
- package/src/constants.js +17 -30
- package/src/fetch/DataFetch.js +17 -9
- package/src/fetch/TableFetch.js +2 -1
- package/src/fetch/UserFetch.js +7 -8
- package/src/fetch/ViewV2Fetch.js +18 -13
- package/src/utils/index.js +1 -1
- package/src/utils/relatedColumns.js +6 -10
- package/src/utils/roles.js +0 -13
- package/src/utils/schema.js +24 -0
- package/src/utils/table.js +6 -6
- package/src/utils/utils.js +1 -1
- package/src/components/FilterBuilder.svelte +0 -379
- package/src/components/grid/controls/ColumnsSettingButton.svelte +0 -41
- package/src/components/grid/controls/ColumnsSettingContent.svelte +0 -270
- package/src/components/grid/controls/SizeButton.svelte +0 -136
- package/src/components/grid/controls/SortButton.svelte +0 -96
- package/src/components/grid/controls/ToggleActionButtonGroup.svelte +0 -41
- package/src/utils/theme.js +0 -12
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import {
|
|
3
|
-
Body,
|
|
4
|
-
Button,
|
|
5
|
-
Combobox,
|
|
6
|
-
DatePicker,
|
|
7
|
-
Icon,
|
|
8
|
-
Input,
|
|
9
|
-
Layout,
|
|
10
|
-
Select,
|
|
11
|
-
Label,
|
|
12
|
-
Multiselect,
|
|
13
|
-
} from "@budibase/bbui"
|
|
14
|
-
import { ArrayOperator, FieldType } from "@budibase/types"
|
|
15
|
-
import { generate } from "shortid"
|
|
16
|
-
import { QueryUtils, Constants } from "@budibase/frontend-core"
|
|
17
|
-
import { getContext } from "svelte"
|
|
18
|
-
import FilterUsers from "./FilterUsers.svelte"
|
|
19
|
-
|
|
20
|
-
const { OperatorOptions, DEFAULT_BB_DATASOURCE_ID } = Constants
|
|
21
|
-
|
|
22
|
-
export let schemaFields
|
|
23
|
-
export let filters = []
|
|
24
|
-
export let tables = []
|
|
25
|
-
export let datasource
|
|
26
|
-
export let behaviourFilters = false
|
|
27
|
-
export let allowBindings = false
|
|
28
|
-
export let filtersLabel = "Filters"
|
|
29
|
-
export let showFilterEmptyDropdown = true
|
|
30
|
-
$: {
|
|
31
|
-
if (
|
|
32
|
-
tables.find(
|
|
33
|
-
table =>
|
|
34
|
-
table._id === datasource?.tableId &&
|
|
35
|
-
table.sourceId === DEFAULT_BB_DATASOURCE_ID
|
|
36
|
-
) &&
|
|
37
|
-
!schemaFields.some(field => field.name === "_id")
|
|
38
|
-
) {
|
|
39
|
-
schemaFields = [...schemaFields, { name: "_id", type: "string" }]
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
$: matchAny = filters?.find(filter => filter.operator === "allOr") != null
|
|
44
|
-
$: onEmptyFilter =
|
|
45
|
-
filters?.find(filter => filter.onEmptyFilter)?.onEmptyFilter ?? "all"
|
|
46
|
-
|
|
47
|
-
$: fieldFilters = filters.filter(
|
|
48
|
-
filter => filter.operator !== "allOr" && !filter.onEmptyFilter
|
|
49
|
-
)
|
|
50
|
-
const behaviourOptions = [
|
|
51
|
-
{ value: "and", label: "Match all filters" },
|
|
52
|
-
{ value: "or", label: "Match any filter" },
|
|
53
|
-
]
|
|
54
|
-
const onEmptyOptions = [
|
|
55
|
-
{ value: "all", label: "Return all table rows" },
|
|
56
|
-
{ value: "none", label: "Return no rows" },
|
|
57
|
-
]
|
|
58
|
-
const context = getContext("context")
|
|
59
|
-
|
|
60
|
-
$: fieldOptions = (schemaFields || []).map(field => ({
|
|
61
|
-
label: field.displayName || field.name,
|
|
62
|
-
value: field.name,
|
|
63
|
-
}))
|
|
64
|
-
|
|
65
|
-
const addFilter = () => {
|
|
66
|
-
filters = [
|
|
67
|
-
...(filters || []),
|
|
68
|
-
{
|
|
69
|
-
id: generate(),
|
|
70
|
-
field: null,
|
|
71
|
-
operator: OperatorOptions.Equals.value,
|
|
72
|
-
value: null,
|
|
73
|
-
valueType: "Value",
|
|
74
|
-
},
|
|
75
|
-
]
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const removeFilter = id => {
|
|
79
|
-
filters = filters.filter(field => field.id !== id)
|
|
80
|
-
|
|
81
|
-
// Clear all filters when no fields are specified
|
|
82
|
-
if (filters.length === 1 && filters[0].onEmptyFilter) {
|
|
83
|
-
filters = []
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const duplicateFilter = id => {
|
|
88
|
-
const existingFilter = filters.find(filter => filter.id === id)
|
|
89
|
-
const duplicate = { ...existingFilter, id: generate() }
|
|
90
|
-
filters = [...filters, duplicate]
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const onFieldChange = filter => {
|
|
94
|
-
const previousType = filter.type
|
|
95
|
-
sanitizeTypes(filter)
|
|
96
|
-
sanitizeOperator(filter)
|
|
97
|
-
sanitizeValue(filter, previousType)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const onOperatorChange = filter => {
|
|
101
|
-
sanitizeOperator(filter)
|
|
102
|
-
sanitizeValue(filter, filter.type)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const onValueTypeChange = filter => {
|
|
106
|
-
sanitizeValue(filter)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const getFieldOptions = field => {
|
|
110
|
-
const schema = schemaFields.find(x => x.name === field)
|
|
111
|
-
return schema?.constraints?.inclusion || []
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const getSchema = filter => {
|
|
115
|
-
return schemaFields.find(field => field.name === filter.field)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const getValidOperatorsForType = filter => {
|
|
119
|
-
if (!filter?.field && !filter?.name) {
|
|
120
|
-
return []
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return QueryUtils.getValidOperatorsForType(
|
|
124
|
-
filter,
|
|
125
|
-
filter.field || filter.name,
|
|
126
|
-
datasource
|
|
127
|
-
)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
$: valueTypeOptions = allowBindings ? ["Value", "Binding"] : ["Value"]
|
|
131
|
-
|
|
132
|
-
const sanitizeTypes = filter => {
|
|
133
|
-
// Update type based on field
|
|
134
|
-
const fieldSchema = schemaFields.find(x => x.name === filter.field)
|
|
135
|
-
filter.type = fieldSchema?.type
|
|
136
|
-
filter.subtype = fieldSchema?.subtype
|
|
137
|
-
filter.formulaType = fieldSchema?.formulaType
|
|
138
|
-
filter.constraints = fieldSchema?.constraints
|
|
139
|
-
|
|
140
|
-
// Update external type based on field
|
|
141
|
-
filter.externalType = getSchema(filter)?.externalType
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const sanitizeOperator = filter => {
|
|
145
|
-
// Ensure a valid operator is selected
|
|
146
|
-
const operators = getValidOperatorsForType(filter).map(x => x.value)
|
|
147
|
-
if (!operators.includes(filter.operator)) {
|
|
148
|
-
filter.operator = operators[0] ?? OperatorOptions.Equals.value
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Update the noValue flag if the operator does not take a value
|
|
152
|
-
const noValueOptions = [
|
|
153
|
-
OperatorOptions.Empty.value,
|
|
154
|
-
OperatorOptions.NotEmpty.value,
|
|
155
|
-
]
|
|
156
|
-
filter.noValue = noValueOptions.includes(filter.operator)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const sanitizeValue = (filter, previousType) => {
|
|
160
|
-
// Check if the operator allows a value at all
|
|
161
|
-
if (filter.noValue) {
|
|
162
|
-
filter.value = null
|
|
163
|
-
return
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Ensure array values are properly set and cleared
|
|
167
|
-
if (Array.isArray(filter.value)) {
|
|
168
|
-
if (filter.valueType !== "Value" || filter.type !== FieldType.ARRAY) {
|
|
169
|
-
filter.value = null
|
|
170
|
-
}
|
|
171
|
-
} else if (
|
|
172
|
-
filter.type === FieldType.ARRAY &&
|
|
173
|
-
filter.valueType === "Value"
|
|
174
|
-
) {
|
|
175
|
-
filter.value = []
|
|
176
|
-
} else if (
|
|
177
|
-
previousType !== filter.type &&
|
|
178
|
-
(previousType === FieldType.BB_REFERENCE ||
|
|
179
|
-
filter.type === FieldType.BB_REFERENCE)
|
|
180
|
-
) {
|
|
181
|
-
filter.value = filter.type === FieldType.ARRAY ? [] : null
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function handleAllOr(option) {
|
|
186
|
-
filters = filters.filter(f => f.operator !== "allOr")
|
|
187
|
-
if (option === "or") {
|
|
188
|
-
filters.push({ operator: "allOr" })
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function handleOnEmptyFilter(value) {
|
|
193
|
-
filters = filters?.filter(filter => !filter.onEmptyFilter)
|
|
194
|
-
filters.push({ onEmptyFilter: value })
|
|
195
|
-
}
|
|
196
|
-
</script>
|
|
197
|
-
|
|
198
|
-
<div class="container" class:mobile={$context?.device?.mobile}>
|
|
199
|
-
<Layout noPadding>
|
|
200
|
-
{#if fieldOptions?.length}
|
|
201
|
-
<Body size="S">
|
|
202
|
-
{#if !fieldFilters?.length}
|
|
203
|
-
Add your first filter expression.
|
|
204
|
-
{:else}
|
|
205
|
-
<slot name="filtering-hero-content" />
|
|
206
|
-
{#if behaviourFilters}
|
|
207
|
-
<div class="behaviour-filters">
|
|
208
|
-
<Select
|
|
209
|
-
label="Behaviour"
|
|
210
|
-
value={matchAny ? "or" : "and"}
|
|
211
|
-
options={behaviourOptions}
|
|
212
|
-
getOptionLabel={opt => opt.label}
|
|
213
|
-
getOptionValue={opt => opt.value}
|
|
214
|
-
on:change={e => handleAllOr(e.detail)}
|
|
215
|
-
placeholder={null}
|
|
216
|
-
/>
|
|
217
|
-
{#if datasource?.type === "table" && showFilterEmptyDropdown}
|
|
218
|
-
<Select
|
|
219
|
-
label="When filter empty"
|
|
220
|
-
value={onEmptyFilter}
|
|
221
|
-
options={onEmptyOptions}
|
|
222
|
-
getOptionLabel={opt => opt.label}
|
|
223
|
-
getOptionValue={opt => opt.value}
|
|
224
|
-
on:change={e => handleOnEmptyFilter(e.detail)}
|
|
225
|
-
placeholder={null}
|
|
226
|
-
/>
|
|
227
|
-
{/if}
|
|
228
|
-
</div>
|
|
229
|
-
{/if}
|
|
230
|
-
{/if}
|
|
231
|
-
</Body>
|
|
232
|
-
{#if fieldFilters?.length}
|
|
233
|
-
<div>
|
|
234
|
-
{#if filtersLabel}
|
|
235
|
-
<div class="filter-label">
|
|
236
|
-
<Label>{filtersLabel}</Label>
|
|
237
|
-
</div>
|
|
238
|
-
{/if}
|
|
239
|
-
<div class="fields" class:with-bindings={allowBindings}>
|
|
240
|
-
{#each fieldFilters as filter}
|
|
241
|
-
<Select
|
|
242
|
-
bind:value={filter.field}
|
|
243
|
-
options={fieldOptions}
|
|
244
|
-
on:change={() => onFieldChange(filter)}
|
|
245
|
-
placeholder="Column"
|
|
246
|
-
/>
|
|
247
|
-
<Select
|
|
248
|
-
disabled={!filter.field}
|
|
249
|
-
options={getValidOperatorsForType(filter)}
|
|
250
|
-
bind:value={filter.operator}
|
|
251
|
-
on:change={() => onOperatorChange(filter)}
|
|
252
|
-
placeholder={null}
|
|
253
|
-
/>
|
|
254
|
-
{#if allowBindings}
|
|
255
|
-
<Select
|
|
256
|
-
disabled={filter.noValue || !filter.field}
|
|
257
|
-
options={valueTypeOptions}
|
|
258
|
-
bind:value={filter.valueType}
|
|
259
|
-
on:change={() => onValueTypeChange(filter)}
|
|
260
|
-
placeholder={null}
|
|
261
|
-
/>
|
|
262
|
-
{/if}
|
|
263
|
-
{#if allowBindings && filter.field && filter.valueType === "Binding"}
|
|
264
|
-
<slot name="binding" {filter} />
|
|
265
|
-
{:else if [FieldType.STRING, FieldType.LONGFORM, FieldType.NUMBER, FieldType.BIGINT, FieldType.FORMULA].includes(filter.type)}
|
|
266
|
-
<Input disabled={filter.noValue} bind:value={filter.value} />
|
|
267
|
-
{:else if filter.type === FieldType.ARRAY || (filter.type === FieldType.OPTIONS && filter.operator === ArrayOperator.ONE_OF)}
|
|
268
|
-
<Multiselect
|
|
269
|
-
disabled={filter.noValue}
|
|
270
|
-
options={getFieldOptions(filter.field)}
|
|
271
|
-
bind:value={filter.value}
|
|
272
|
-
/>
|
|
273
|
-
{:else if filter.type === FieldType.OPTIONS}
|
|
274
|
-
<Combobox
|
|
275
|
-
disabled={filter.noValue}
|
|
276
|
-
options={getFieldOptions(filter.field)}
|
|
277
|
-
bind:value={filter.value}
|
|
278
|
-
/>
|
|
279
|
-
{:else if filter.type === FieldType.BOOLEAN}
|
|
280
|
-
<Combobox
|
|
281
|
-
disabled={filter.noValue}
|
|
282
|
-
options={[
|
|
283
|
-
{ label: "True", value: "true" },
|
|
284
|
-
{ label: "False", value: "false" },
|
|
285
|
-
]}
|
|
286
|
-
bind:value={filter.value}
|
|
287
|
-
/>
|
|
288
|
-
{:else if filter.type === FieldType.DATETIME}
|
|
289
|
-
<DatePicker
|
|
290
|
-
disabled={filter.noValue}
|
|
291
|
-
enableTime={!getSchema(filter)?.dateOnly}
|
|
292
|
-
timeOnly={getSchema(filter)?.timeOnly}
|
|
293
|
-
bind:value={filter.value}
|
|
294
|
-
/>
|
|
295
|
-
{:else if [FieldType.BB_REFERENCE, FieldType.BB_REFERENCE_SINGLE].includes(filter.type)}
|
|
296
|
-
<FilterUsers
|
|
297
|
-
bind:value={filter.value}
|
|
298
|
-
multiselect={[
|
|
299
|
-
OperatorOptions.In.value,
|
|
300
|
-
OperatorOptions.ContainsAny.value,
|
|
301
|
-
].includes(filter.operator)}
|
|
302
|
-
disabled={filter.noValue}
|
|
303
|
-
type={filter.valueType}
|
|
304
|
-
/>
|
|
305
|
-
{:else}
|
|
306
|
-
<Input disabled />
|
|
307
|
-
{/if}
|
|
308
|
-
<div class="controls">
|
|
309
|
-
<Icon
|
|
310
|
-
name="Duplicate"
|
|
311
|
-
hoverable
|
|
312
|
-
size="S"
|
|
313
|
-
on:click={() => duplicateFilter(filter.id)}
|
|
314
|
-
/>
|
|
315
|
-
<Icon
|
|
316
|
-
name="Close"
|
|
317
|
-
hoverable
|
|
318
|
-
size="S"
|
|
319
|
-
on:click={() => removeFilter(filter.id)}
|
|
320
|
-
/>
|
|
321
|
-
</div>
|
|
322
|
-
{/each}
|
|
323
|
-
</div>
|
|
324
|
-
</div>
|
|
325
|
-
{/if}
|
|
326
|
-
<div>
|
|
327
|
-
<Button icon="AddCircle" size="M" secondary on:click={addFilter}>
|
|
328
|
-
Add filter
|
|
329
|
-
</Button>
|
|
330
|
-
</div>
|
|
331
|
-
{:else}
|
|
332
|
-
<Body size="S">None of the table column can be used for filtering.</Body>
|
|
333
|
-
{/if}
|
|
334
|
-
</Layout>
|
|
335
|
-
</div>
|
|
336
|
-
|
|
337
|
-
<style>
|
|
338
|
-
.container {
|
|
339
|
-
width: 100%;
|
|
340
|
-
}
|
|
341
|
-
.fields {
|
|
342
|
-
display: grid;
|
|
343
|
-
column-gap: var(--spacing-l);
|
|
344
|
-
row-gap: var(--spacing-s);
|
|
345
|
-
align-items: center;
|
|
346
|
-
grid-template-columns: 1fr 120px 1fr auto auto;
|
|
347
|
-
}
|
|
348
|
-
.fields.with-bindings {
|
|
349
|
-
grid-template-columns: minmax(150px, 1fr) 170px 120px minmax(150px, 1fr) 16px 16px;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
.controls {
|
|
353
|
-
display: contents;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
.container.mobile .fields {
|
|
357
|
-
grid-template-columns: 1fr;
|
|
358
|
-
}
|
|
359
|
-
.container.mobile .controls {
|
|
360
|
-
display: flex;
|
|
361
|
-
flex-direction: row;
|
|
362
|
-
justify-content: flex-start;
|
|
363
|
-
align-items: center;
|
|
364
|
-
padding: var(--spacing-s) 0;
|
|
365
|
-
gap: var(--spacing-s);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
.filter-label {
|
|
369
|
-
margin-bottom: var(--spacing-s);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
.behaviour-filters {
|
|
373
|
-
display: grid;
|
|
374
|
-
column-gap: var(--spacing-l);
|
|
375
|
-
row-gap: var(--spacing-s);
|
|
376
|
-
align-items: center;
|
|
377
|
-
grid-template-columns: minmax(150px, 1fr) 170px 120px minmax(150px, 1fr) 16px 16px;
|
|
378
|
-
}
|
|
379
|
-
</style>
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { getContext } from "svelte"
|
|
3
|
-
import { ActionButton, Popover } from "@budibase/bbui"
|
|
4
|
-
import ColumnsSettingContent from "./ColumnsSettingContent.svelte"
|
|
5
|
-
import { FieldPermissions } from "../../../constants"
|
|
6
|
-
|
|
7
|
-
const { tableColumns, datasource } = getContext("grid")
|
|
8
|
-
|
|
9
|
-
let open = false
|
|
10
|
-
let anchor
|
|
11
|
-
|
|
12
|
-
$: anyRestricted = $tableColumns.filter(
|
|
13
|
-
col => !col.visible || col.readonly
|
|
14
|
-
).length
|
|
15
|
-
$: text = anyRestricted ? `Columns (${anyRestricted} restricted)` : "Columns"
|
|
16
|
-
$: permissions =
|
|
17
|
-
$datasource.type === "viewV2"
|
|
18
|
-
? [
|
|
19
|
-
FieldPermissions.WRITABLE,
|
|
20
|
-
FieldPermissions.READONLY,
|
|
21
|
-
FieldPermissions.HIDDEN,
|
|
22
|
-
]
|
|
23
|
-
: [FieldPermissions.WRITABLE, FieldPermissions.HIDDEN]
|
|
24
|
-
</script>
|
|
25
|
-
|
|
26
|
-
<div bind:this={anchor}>
|
|
27
|
-
<ActionButton
|
|
28
|
-
icon="ColumnSettings"
|
|
29
|
-
quiet
|
|
30
|
-
size="M"
|
|
31
|
-
on:click={() => (open = !open)}
|
|
32
|
-
selected={open || anyRestricted}
|
|
33
|
-
disabled={!$tableColumns.length}
|
|
34
|
-
>
|
|
35
|
-
{text}
|
|
36
|
-
</ActionButton>
|
|
37
|
-
</div>
|
|
38
|
-
|
|
39
|
-
<Popover bind:open {anchor} align="left">
|
|
40
|
-
<ColumnsSettingContent columns={$tableColumns} {permissions} />
|
|
41
|
-
</Popover>
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { getContext } from "svelte"
|
|
3
|
-
import { Icon, notifications, ActionButton, Popover } from "@budibase/bbui"
|
|
4
|
-
import { getColumnIcon } from "../lib/utils"
|
|
5
|
-
import ToggleActionButtonGroup from "./ToggleActionButtonGroup.svelte"
|
|
6
|
-
import { helpers } from "@budibase/shared-core"
|
|
7
|
-
import { FieldType } from "@budibase/types"
|
|
8
|
-
import { FieldPermissions } from "../../../constants"
|
|
9
|
-
|
|
10
|
-
export let permissions = [FieldPermissions.WRITABLE, FieldPermissions.HIDDEN]
|
|
11
|
-
export let disabledPermissions = []
|
|
12
|
-
export let columns
|
|
13
|
-
export let fromRelationshipField
|
|
14
|
-
|
|
15
|
-
const { datasource, dispatch, config } = getContext("grid")
|
|
16
|
-
|
|
17
|
-
$: canSetRelationshipSchemas = $config.canSetRelationshipSchemas
|
|
18
|
-
|
|
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
|
-
})(),
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const options = []
|
|
80
|
-
|
|
81
|
-
let permission
|
|
82
|
-
if ((permission = permissionsObj[FieldPermissions.WRITABLE])) {
|
|
83
|
-
const tooltip = requiredTooltips[FieldPermissions.WRITABLE] || "Writable"
|
|
84
|
-
options.push({
|
|
85
|
-
icon: "Edit",
|
|
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)" : "")
|
|
96
|
-
options.push({
|
|
97
|
-
icon: "Visibility",
|
|
98
|
-
value: FieldPermissions.READONLY,
|
|
99
|
-
tooltip,
|
|
100
|
-
disabled: permission.disabled || isRequired,
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
|
|
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
|
-
}
|
|
113
|
-
|
|
114
|
-
return { ...c, options }
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
$: relationshipPanelColumns = Object.entries(
|
|
118
|
-
relationshipField?.columns || {}
|
|
119
|
-
).map(([name, column]) => {
|
|
120
|
-
return {
|
|
121
|
-
name: name,
|
|
122
|
-
label: name,
|
|
123
|
-
schema: {
|
|
124
|
-
type: column.type,
|
|
125
|
-
subtype: column.subtype,
|
|
126
|
-
visible: column.visible,
|
|
127
|
-
readonly: column.readonly,
|
|
128
|
-
icon: column.icon,
|
|
129
|
-
},
|
|
130
|
-
}
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
async function toggleColumn(column, permission) {
|
|
134
|
-
const visible = permission !== FieldPermissions.HIDDEN
|
|
135
|
-
const readonly = permission === FieldPermissions.READONLY
|
|
136
|
-
|
|
137
|
-
if (!fromRelationshipField) {
|
|
138
|
-
await datasource.actions.addSchemaMutation(column.name, {
|
|
139
|
-
visible,
|
|
140
|
-
readonly,
|
|
141
|
-
})
|
|
142
|
-
} else {
|
|
143
|
-
await datasource.actions.addSubSchemaMutation(
|
|
144
|
-
column.name,
|
|
145
|
-
fromRelationshipField.name,
|
|
146
|
-
{
|
|
147
|
-
visible,
|
|
148
|
-
readonly,
|
|
149
|
-
}
|
|
150
|
-
)
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
await datasource.actions.saveSchemaMutations()
|
|
154
|
-
} catch (e) {
|
|
155
|
-
notifications.error(e.message)
|
|
156
|
-
} finally {
|
|
157
|
-
await datasource.actions.resetSchemaMutations()
|
|
158
|
-
await datasource.actions.refreshDefinition()
|
|
159
|
-
}
|
|
160
|
-
dispatch(visible ? "show-column" : "hide-column")
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function columnToPermissionOptions(column) {
|
|
164
|
-
if (column.schema.visible === false) {
|
|
165
|
-
return FieldPermissions.HIDDEN
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (column.schema.readonly) {
|
|
169
|
-
return FieldPermissions.READONLY
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return FieldPermissions.WRITABLE
|
|
173
|
-
}
|
|
174
|
-
</script>
|
|
175
|
-
|
|
176
|
-
<div class="content">
|
|
177
|
-
<div class="columns">
|
|
178
|
-
{#each displayColumns as column}
|
|
179
|
-
<div class="column">
|
|
180
|
-
<Icon size="S" name={getColumnIcon(column)} />
|
|
181
|
-
<div class="column-label" title={column.label}>
|
|
182
|
-
{column.label}
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
<div class="column-options">
|
|
186
|
-
<ToggleActionButtonGroup
|
|
187
|
-
on:click={e => toggleColumn(column, e.detail)}
|
|
188
|
-
value={columnToPermissionOptions(column)}
|
|
189
|
-
options={column.options}
|
|
190
|
-
/>
|
|
191
|
-
{#if canSetRelationshipSchemas && column.schema.type === FieldType.LINK && columnToPermissionOptions(column) !== FieldPermissions.HIDDEN}
|
|
192
|
-
<div class="relationship-columns">
|
|
193
|
-
<ActionButton
|
|
194
|
-
on:click={e => {
|
|
195
|
-
relationshipFieldName = column.name
|
|
196
|
-
relationshipPanelAnchor = e.currentTarget
|
|
197
|
-
}}
|
|
198
|
-
size="S"
|
|
199
|
-
icon="ChevronRight"
|
|
200
|
-
quiet
|
|
201
|
-
/>
|
|
202
|
-
</div>
|
|
203
|
-
{/if}
|
|
204
|
-
</div>
|
|
205
|
-
{/each}
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
|
|
209
|
-
{#if canSetRelationshipSchemas}
|
|
210
|
-
<Popover
|
|
211
|
-
on:close={() => (relationshipFieldName = null)}
|
|
212
|
-
open={relationshipFieldName}
|
|
213
|
-
anchor={relationshipPanelAnchor}
|
|
214
|
-
align="left"
|
|
215
|
-
>
|
|
216
|
-
{#if relationshipPanelColumns.length}
|
|
217
|
-
<div class="relationship-header">
|
|
218
|
-
{relationshipFieldName} columns
|
|
219
|
-
</div>
|
|
220
|
-
{/if}
|
|
221
|
-
<svelte:self
|
|
222
|
-
columns={relationshipPanelColumns}
|
|
223
|
-
permissions={[FieldPermissions.READONLY, FieldPermissions.HIDDEN]}
|
|
224
|
-
fromRelationshipField={relationshipField}
|
|
225
|
-
/>
|
|
226
|
-
</Popover>
|
|
227
|
-
{/if}
|
|
228
|
-
|
|
229
|
-
<style>
|
|
230
|
-
.relationship-columns :global(.spectrum-ActionButton) {
|
|
231
|
-
width: 28px;
|
|
232
|
-
height: 28px;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.content {
|
|
236
|
-
padding: 12px 12px;
|
|
237
|
-
display: flex;
|
|
238
|
-
flex-direction: column;
|
|
239
|
-
gap: 12px;
|
|
240
|
-
}
|
|
241
|
-
.columns {
|
|
242
|
-
display: grid;
|
|
243
|
-
align-items: center;
|
|
244
|
-
grid-template-columns: 1fr auto;
|
|
245
|
-
grid-row-gap: 8px;
|
|
246
|
-
grid-column-gap: 24px;
|
|
247
|
-
}
|
|
248
|
-
.columns :global(.spectrum-Switch) {
|
|
249
|
-
margin-right: 0;
|
|
250
|
-
}
|
|
251
|
-
.column {
|
|
252
|
-
display: flex;
|
|
253
|
-
gap: 8px;
|
|
254
|
-
}
|
|
255
|
-
.column-label {
|
|
256
|
-
min-width: 80px;
|
|
257
|
-
max-width: 200px;
|
|
258
|
-
text-overflow: ellipsis;
|
|
259
|
-
white-space: nowrap;
|
|
260
|
-
overflow: hidden;
|
|
261
|
-
}
|
|
262
|
-
.column-options {
|
|
263
|
-
display: flex;
|
|
264
|
-
gap: var(--spacing-xs);
|
|
265
|
-
}
|
|
266
|
-
.relationship-header {
|
|
267
|
-
color: var(--spectrum-global-color-gray-600);
|
|
268
|
-
padding: 12px 12px 0 12px;
|
|
269
|
-
}
|
|
270
|
-
</style>
|