@budibase/frontend-core 2.27.3 → 2.27.5
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/components/FilterBuilder.svelte +8 -6
- package/src/components/grid/cells/HeaderCell.svelte +16 -8
- package/src/components/grid/cells/OptionsCell.svelte +8 -3
- package/src/components/grid/cells/RelationshipCell.svelte +2 -2
- package/src/components/grid/controls/{HideColumnsButton.svelte → ColumnsSettingButton.svelte} +40 -29
- package/src/components/grid/controls/ToggleActionButtonGroup.svelte +43 -0
- package/src/components/grid/layout/ButtonColumn.svelte +16 -24
- package/src/components/grid/layout/Grid.svelte +4 -3
- package/src/components/grid/layout/GridScrollWrapper.svelte +2 -1
- package/src/components/grid/layout/NewColumnButton.svelte +3 -1
- package/src/components/grid/layout/NewRow.svelte +1 -1
- package/src/components/grid/layout/StickyColumn.svelte +1 -1
- package/src/components/grid/lib/utils.js +0 -7
- package/src/components/grid/overlays/KeyboardManager.svelte +2 -1
- package/src/components/grid/overlays/ScrollOverlay.svelte +6 -6
- package/src/components/grid/stores/rows.js +23 -21
- package/src/components/grid/stores/scroll.js +4 -1
- package/src/components/grid/stores/ui.js +2 -0
- package/src/constants.js +13 -0
- package/src/utils/index.js +1 -0
- package/src/utils/searchFields.js +37 -0
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "2.27.
|
|
3
|
+
"version": "2.27.5",
|
|
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.27.
|
|
10
|
-
"@budibase/shared-core": "2.27.
|
|
11
|
-
"@budibase/types": "2.27.
|
|
9
|
+
"@budibase/bbui": "2.27.5",
|
|
10
|
+
"@budibase/shared-core": "2.27.5",
|
|
11
|
+
"@budibase/types": "2.27.5",
|
|
12
12
|
"dayjs": "^1.10.8",
|
|
13
13
|
"lodash": "4.17.21",
|
|
14
14
|
"shortid": "2.2.15",
|
|
15
15
|
"socket.io-client": "^4.6.1"
|
|
16
16
|
},
|
|
17
|
-
"gitHead": "
|
|
17
|
+
"gitHead": "e4db515ecf2c0a19cd188cf4d646946e903717b1"
|
|
18
18
|
}
|
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
import { LuceneUtils, Constants } from "@budibase/frontend-core"
|
|
17
17
|
import { getContext } from "svelte"
|
|
18
18
|
import FilterUsers from "./FilterUsers.svelte"
|
|
19
|
+
import { getFields } from "../utils/searchFields"
|
|
19
20
|
|
|
20
21
|
const { OperatorOptions } = Constants
|
|
21
22
|
|
|
22
23
|
export let schemaFields
|
|
23
24
|
export let filters = []
|
|
25
|
+
export let tables = []
|
|
24
26
|
export let datasource
|
|
25
27
|
export let behaviourFilters = false
|
|
26
28
|
export let allowBindings = false
|
|
@@ -45,12 +47,12 @@
|
|
|
45
47
|
|
|
46
48
|
const context = getContext("context")
|
|
47
49
|
|
|
48
|
-
$: fieldOptions = (schemaFields
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
$: fieldOptions = getFields(tables, schemaFields || [], {
|
|
51
|
+
allowLinks: true,
|
|
52
|
+
}).map(field => ({
|
|
53
|
+
label: field.displayName || field.name,
|
|
54
|
+
value: field.name,
|
|
55
|
+
}))
|
|
54
56
|
|
|
55
57
|
const addFilter = () => {
|
|
56
58
|
filters = [
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
focusedCellId,
|
|
30
30
|
filter,
|
|
31
31
|
inlineFilters,
|
|
32
|
+
keyboardBlocked,
|
|
32
33
|
} = getContext("grid")
|
|
33
34
|
|
|
34
35
|
const searchableTypes = [
|
|
@@ -57,6 +58,8 @@
|
|
|
57
58
|
$: searching = searchValue != null
|
|
58
59
|
$: debouncedUpdateFilter(searchValue)
|
|
59
60
|
$: orderable = !column.primaryDisplay
|
|
61
|
+
$: editable = $config.canEditColumns && !column.schema.disabled
|
|
62
|
+
$: keyboardBlocked.set(open)
|
|
60
63
|
|
|
61
64
|
const close = () => {
|
|
62
65
|
open = false
|
|
@@ -231,6 +234,14 @@
|
|
|
231
234
|
}
|
|
232
235
|
const debouncedUpdateFilter = debounce(updateFilter, 250)
|
|
233
236
|
|
|
237
|
+
const handleDoubleClick = () => {
|
|
238
|
+
if (!editable || searching) {
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
open = true
|
|
242
|
+
editColumn()
|
|
243
|
+
}
|
|
244
|
+
|
|
234
245
|
onMount(() => subscribe("close-edit-column", close))
|
|
235
246
|
</script>
|
|
236
247
|
|
|
@@ -241,14 +252,15 @@
|
|
|
241
252
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
242
253
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
243
254
|
<div
|
|
255
|
+
bind:this={anchor}
|
|
244
256
|
class="header-cell"
|
|
257
|
+
style="flex: 0 0 {column.width}px;"
|
|
245
258
|
class:open
|
|
246
259
|
class:searchable
|
|
247
260
|
class:searching
|
|
248
|
-
style="flex: 0 0 {column.width}px;"
|
|
249
|
-
bind:this={anchor}
|
|
250
261
|
class:disabled={$isReordering || $isResizing}
|
|
251
262
|
class:sticky={idx === "sticky"}
|
|
263
|
+
on:dblclick={handleDoubleClick}
|
|
252
264
|
>
|
|
253
265
|
<GridCell
|
|
254
266
|
on:mousedown={onMouseDown}
|
|
@@ -311,7 +323,7 @@
|
|
|
311
323
|
{#if open}
|
|
312
324
|
<GridPopover
|
|
313
325
|
{anchor}
|
|
314
|
-
align="
|
|
326
|
+
align="left"
|
|
315
327
|
on:close={close}
|
|
316
328
|
maxHeight={null}
|
|
317
329
|
resizable
|
|
@@ -322,11 +334,7 @@
|
|
|
322
334
|
</div>
|
|
323
335
|
{:else}
|
|
324
336
|
<Menu>
|
|
325
|
-
<MenuItem
|
|
326
|
-
icon="Edit"
|
|
327
|
-
on:click={editColumn}
|
|
328
|
-
disabled={!$config.canEditColumns || column.schema.disabled}
|
|
329
|
-
>
|
|
337
|
+
<MenuItem icon="Edit" on:click={editColumn} disabled={!editable}>
|
|
330
338
|
Edit column
|
|
331
339
|
</MenuItem>
|
|
332
340
|
<MenuItem
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { Icon } from "@budibase/bbui"
|
|
3
|
-
import { getColor } from "../lib/utils"
|
|
4
3
|
import { onMount } from "svelte"
|
|
5
4
|
import GridPopover from "../overlays/GridPopover.svelte"
|
|
5
|
+
import { OptionColours } from "../../../constants"
|
|
6
6
|
|
|
7
7
|
export let value
|
|
8
8
|
export let schema
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
export let api
|
|
14
14
|
export let contentLines = 1
|
|
15
15
|
|
|
16
|
+
const InvalidColor = "hsla(0, 0%, 70%, 0.3)"
|
|
17
|
+
|
|
16
18
|
let isOpen = false
|
|
17
19
|
let focusedOptionIdx = null
|
|
18
20
|
let anchor
|
|
@@ -38,8 +40,11 @@
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
const getOptionColor = value => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
let idx = value ? options.indexOf(value) : null
|
|
44
|
+
if (idx == null || idx === -1) {
|
|
45
|
+
return InvalidColor
|
|
46
|
+
}
|
|
47
|
+
return OptionColours[idx % OptionColours.length]
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
const toggleOption = option => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { getColor } from "../lib/utils"
|
|
3
2
|
import { onMount, getContext } from "svelte"
|
|
4
3
|
import { Icon, Input, ProgressCircle } from "@budibase/bbui"
|
|
5
4
|
import { debounce } from "../../../utils/utils"
|
|
6
5
|
import GridPopover from "../overlays/GridPopover.svelte"
|
|
6
|
+
import { OptionColours } from "../../../constants"
|
|
7
7
|
|
|
8
8
|
const { API, cache } = getContext("grid")
|
|
9
9
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
export let primaryDisplay
|
|
19
19
|
export let hideCounter = false
|
|
20
20
|
|
|
21
|
-
const color =
|
|
21
|
+
const color = OptionColours[0]
|
|
22
22
|
|
|
23
23
|
let isOpen = false
|
|
24
24
|
let searchResults
|
package/src/components/grid/controls/{HideColumnsButton.svelte → ColumnsSettingButton.svelte}
RENAMED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { getContext } from "svelte"
|
|
3
|
-
import { ActionButton, Popover,
|
|
3
|
+
import { ActionButton, Popover, Icon } from "@budibase/bbui"
|
|
4
4
|
import { getColumnIcon } from "../lib/utils"
|
|
5
|
+
import ToggleActionButtonGroup from "./ToggleActionButtonGroup.svelte"
|
|
5
6
|
|
|
6
7
|
const { columns, datasource, stickyColumn, dispatch } = getContext("grid")
|
|
7
8
|
|
|
@@ -11,31 +12,45 @@
|
|
|
11
12
|
$: anyHidden = $columns.some(col => !col.visible)
|
|
12
13
|
$: text = getText($columns)
|
|
13
14
|
|
|
14
|
-
const toggleColumn = async (column,
|
|
15
|
-
|
|
16
|
-
await datasource.actions.saveSchemaMutations()
|
|
17
|
-
dispatch(visible ? "show-column" : "hide-column")
|
|
18
|
-
}
|
|
15
|
+
const toggleColumn = async (column, permission) => {
|
|
16
|
+
const visible = permission !== PERMISSION_OPTIONS.HIDDEN
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
let mutations = {}
|
|
22
|
-
$columns.forEach(column => {
|
|
23
|
-
mutations[column.name] = { visible }
|
|
24
|
-
})
|
|
25
|
-
datasource.actions.addSchemaMutations(mutations)
|
|
18
|
+
datasource.actions.addSchemaMutation(column.name, { visible })
|
|
26
19
|
await datasource.actions.saveSchemaMutations()
|
|
27
20
|
dispatch(visible ? "show-column" : "hide-column")
|
|
28
21
|
}
|
|
29
22
|
|
|
30
23
|
const getText = columns => {
|
|
31
24
|
const hidden = columns.filter(col => !col.visible).length
|
|
32
|
-
return hidden ? `
|
|
25
|
+
return hidden ? `Columns (${hidden} restricted)` : "Columns"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const PERMISSION_OPTIONS = {
|
|
29
|
+
WRITABLE: "writable",
|
|
30
|
+
HIDDEN: "hidden",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const options = [
|
|
34
|
+
{ icon: "Edit", value: PERMISSION_OPTIONS.WRITABLE, tooltip: "Writable" },
|
|
35
|
+
{
|
|
36
|
+
icon: "VisibilityOff",
|
|
37
|
+
value: PERMISSION_OPTIONS.HIDDEN,
|
|
38
|
+
tooltip: "Hidden",
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
function columnToPermissionOptions(column) {
|
|
43
|
+
if (!column.visible) {
|
|
44
|
+
return PERMISSION_OPTIONS.HIDDEN
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return PERMISSION_OPTIONS.WRITABLE
|
|
33
48
|
}
|
|
34
49
|
</script>
|
|
35
50
|
|
|
36
51
|
<div bind:this={anchor}>
|
|
37
52
|
<ActionButton
|
|
38
|
-
icon="
|
|
53
|
+
icon="ColumnSettings"
|
|
39
54
|
quiet
|
|
40
55
|
size="M"
|
|
41
56
|
on:click={() => (open = !open)}
|
|
@@ -54,25 +69,25 @@
|
|
|
54
69
|
<Icon size="S" name={getColumnIcon($stickyColumn)} />
|
|
55
70
|
{$stickyColumn.label}
|
|
56
71
|
</div>
|
|
57
|
-
|
|
72
|
+
|
|
73
|
+
<ToggleActionButtonGroup
|
|
74
|
+
disabled
|
|
75
|
+
value={PERMISSION_OPTIONS.WRITABLE}
|
|
76
|
+
{options}
|
|
77
|
+
/>
|
|
58
78
|
{/if}
|
|
59
79
|
{#each $columns as column}
|
|
60
80
|
<div class="column">
|
|
61
81
|
<Icon size="S" name={getColumnIcon(column)} />
|
|
62
82
|
{column.label}
|
|
63
83
|
</div>
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
value={column
|
|
67
|
-
|
|
68
|
-
disabled={column.primaryDisplay}
|
|
84
|
+
<ToggleActionButtonGroup
|
|
85
|
+
on:click={e => toggleColumn(column, e.detail)}
|
|
86
|
+
value={columnToPermissionOptions(column)}
|
|
87
|
+
{options}
|
|
69
88
|
/>
|
|
70
89
|
{/each}
|
|
71
90
|
</div>
|
|
72
|
-
<div class="buttons">
|
|
73
|
-
<ActionButton on:click={() => toggleAll(true)}>Show all</ActionButton>
|
|
74
|
-
<ActionButton on:click={() => toggleAll(false)}>Hide all</ActionButton>
|
|
75
|
-
</div>
|
|
76
91
|
</div>
|
|
77
92
|
</Popover>
|
|
78
93
|
|
|
@@ -83,15 +98,11 @@
|
|
|
83
98
|
flex-direction: column;
|
|
84
99
|
gap: 12px;
|
|
85
100
|
}
|
|
86
|
-
.buttons {
|
|
87
|
-
display: flex;
|
|
88
|
-
flex-direction: row;
|
|
89
|
-
gap: 8px;
|
|
90
|
-
}
|
|
91
101
|
.columns {
|
|
92
102
|
display: grid;
|
|
93
103
|
align-items: center;
|
|
94
104
|
grid-template-columns: 1fr auto;
|
|
105
|
+
gap: 8px;
|
|
95
106
|
}
|
|
96
107
|
.columns :global(.spectrum-Switch) {
|
|
97
108
|
margin-right: 0;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { createEventDispatcher } from "svelte"
|
|
3
|
+
|
|
4
|
+
let dispatch = createEventDispatcher()
|
|
5
|
+
|
|
6
|
+
import { ActionButton, AbsTooltip, TooltipType } from "@budibase/bbui"
|
|
7
|
+
|
|
8
|
+
export let value
|
|
9
|
+
export let options
|
|
10
|
+
export let disabled
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div class="permissionPicker">
|
|
14
|
+
{#each options as option}
|
|
15
|
+
<AbsTooltip text={option.tooltip} type={TooltipType.Info}>
|
|
16
|
+
<ActionButton
|
|
17
|
+
on:click={() => dispatch("click", option.value)}
|
|
18
|
+
{disabled}
|
|
19
|
+
size="S"
|
|
20
|
+
icon={option.icon}
|
|
21
|
+
quiet
|
|
22
|
+
selected={option.value === value}
|
|
23
|
+
noPadding
|
|
24
|
+
/>
|
|
25
|
+
</AbsTooltip>
|
|
26
|
+
{/each}
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<style>
|
|
30
|
+
.permissionPicker {
|
|
31
|
+
display: flex;
|
|
32
|
+
gap: var(--spacing-xs);
|
|
33
|
+
padding-left: calc(var(--spacing-xl) * 2);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.permissionPicker :global(.spectrum-Icon) {
|
|
37
|
+
width: 14px;
|
|
38
|
+
}
|
|
39
|
+
.permissionPicker :global(.spectrum-ActionButton) {
|
|
40
|
+
width: 28px;
|
|
41
|
+
height: 28px;
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
scroll,
|
|
17
17
|
isDragging,
|
|
18
18
|
buttonColumnWidth,
|
|
19
|
+
showVScrollbar,
|
|
19
20
|
} = getContext("grid")
|
|
20
21
|
|
|
21
|
-
let
|
|
22
|
+
let container
|
|
22
23
|
|
|
23
24
|
$: buttons = $props.buttons?.slice(0, 3) || []
|
|
24
25
|
$: columnsWidth = $visibleColumns.reduce(
|
|
@@ -39,23 +40,10 @@
|
|
|
39
40
|
const width = entries?.[0]?.contentRect?.width ?? 0
|
|
40
41
|
buttonColumnWidth.set(width)
|
|
41
42
|
})
|
|
42
|
-
observer.observe(
|
|
43
|
+
observer.observe(container)
|
|
43
44
|
})
|
|
44
45
|
</script>
|
|
45
46
|
|
|
46
|
-
<!-- Hidden copy of buttons to measure -->
|
|
47
|
-
<div class="measure" bind:this={measureContainer}>
|
|
48
|
-
<GridCell width="auto">
|
|
49
|
-
<div class="buttons">
|
|
50
|
-
{#each buttons as button}
|
|
51
|
-
<Button size="S">
|
|
52
|
-
{button.text || "Button"}
|
|
53
|
-
</Button>
|
|
54
|
-
{/each}
|
|
55
|
-
</div>
|
|
56
|
-
</GridCell>
|
|
57
|
-
</div>
|
|
58
|
-
|
|
59
47
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
60
48
|
<div
|
|
61
49
|
class="button-column"
|
|
@@ -63,7 +51,7 @@
|
|
|
63
51
|
class:hidden={$buttonColumnWidth === 0}
|
|
64
52
|
>
|
|
65
53
|
<div class="content" on:mouseleave={() => ($hoveredRowId = null)}>
|
|
66
|
-
<GridScrollWrapper scrollVertically attachHandlers>
|
|
54
|
+
<GridScrollWrapper scrollVertically attachHandlers bind:ref={container}>
|
|
67
55
|
{#each $renderedRows as row}
|
|
68
56
|
{@const rowSelected = !!$selectedRows[row._id]}
|
|
69
57
|
{@const rowHovered = $hoveredRowId === row._id}
|
|
@@ -79,7 +67,7 @@
|
|
|
79
67
|
selected={rowSelected}
|
|
80
68
|
highlighted={rowHovered || rowFocused}
|
|
81
69
|
>
|
|
82
|
-
<div class="buttons">
|
|
70
|
+
<div class="buttons" class:offset={$showVScrollbar}>
|
|
83
71
|
{#each buttons as button}
|
|
84
72
|
<Button
|
|
85
73
|
newStyles
|
|
@@ -91,6 +79,9 @@
|
|
|
91
79
|
overBackground={button.type === "overBackground"}
|
|
92
80
|
on:click={() => handleClick(button, row)}
|
|
93
81
|
>
|
|
82
|
+
{#if button.icon}
|
|
83
|
+
<i class="{button.icon} S" />
|
|
84
|
+
{/if}
|
|
94
85
|
{button.text || "Button"}
|
|
95
86
|
</Button>
|
|
96
87
|
{/each}
|
|
@@ -130,16 +121,17 @@
|
|
|
130
121
|
gap: var(--cell-padding);
|
|
131
122
|
height: inherit;
|
|
132
123
|
}
|
|
124
|
+
.buttons.offset {
|
|
125
|
+
padding-right: calc(var(--cell-padding) + 2 * var(--scroll-bar-size) - 2px);
|
|
126
|
+
}
|
|
127
|
+
.buttons :global(.spectrum-Button-Label) {
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: 4px;
|
|
131
|
+
}
|
|
133
132
|
|
|
134
133
|
/* Add left cell border */
|
|
135
134
|
.button-column :global(.cell) {
|
|
136
135
|
border-left: var(--cell-border);
|
|
137
136
|
}
|
|
138
|
-
|
|
139
|
-
/* Hidden copy of buttons to measure width against */
|
|
140
|
-
.measure {
|
|
141
|
-
position: absolute;
|
|
142
|
-
opacity: 0;
|
|
143
|
-
pointer-events: none;
|
|
144
|
-
}
|
|
145
137
|
</style>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import UserAvatars from "./UserAvatars.svelte"
|
|
19
19
|
import KeyboardManager from "../overlays/KeyboardManager.svelte"
|
|
20
20
|
import SortButton from "../controls/SortButton.svelte"
|
|
21
|
-
import
|
|
21
|
+
import ColumnsSettingButton from "../controls/ColumnsSettingButton.svelte"
|
|
22
22
|
import SizeButton from "../controls/SizeButton.svelte"
|
|
23
23
|
import NewRow from "./NewRow.svelte"
|
|
24
24
|
import { createGridWebsocket } from "../lib/websocket"
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
Padding,
|
|
30
30
|
SmallRowHeight,
|
|
31
31
|
ControlsHeight,
|
|
32
|
+
ScrollBarSize,
|
|
32
33
|
} from "../lib/constants"
|
|
33
34
|
|
|
34
35
|
export let API = null
|
|
@@ -145,14 +146,14 @@
|
|
|
145
146
|
class:quiet
|
|
146
147
|
on:mouseenter={() => gridFocused.set(true)}
|
|
147
148
|
on:mouseleave={() => gridFocused.set(false)}
|
|
148
|
-
style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-overflow:{MaxCellRenderOverflow}px; --content-lines:{$contentLines}; --min-height:{$minHeight}px; --controls-height:{ControlsHeight}px;"
|
|
149
|
+
style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-overflow:{MaxCellRenderOverflow}px; --content-lines:{$contentLines}; --min-height:{$minHeight}px; --controls-height:{ControlsHeight}px; --scroll-bar-size:{ScrollBarSize}px;"
|
|
149
150
|
>
|
|
150
151
|
{#if showControls}
|
|
151
152
|
<div class="controls">
|
|
152
153
|
<div class="controls-left">
|
|
153
154
|
<slot name="filter" />
|
|
154
155
|
<SortButton />
|
|
155
|
-
<
|
|
156
|
+
<ColumnsSettingButton />
|
|
156
157
|
<SizeButton />
|
|
157
158
|
<slot name="controls" />
|
|
158
159
|
</div>
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
export let scrollVertically = false
|
|
19
19
|
export let scrollHorizontally = false
|
|
20
20
|
export let attachHandlers = false
|
|
21
|
+
export let ref
|
|
21
22
|
|
|
22
23
|
// Used for tracking touch events
|
|
23
24
|
let initialTouchX
|
|
@@ -109,7 +110,7 @@
|
|
|
109
110
|
on:touchmove={attachHandlers ? handleTouchMove : null}
|
|
110
111
|
on:click|self={() => ($focusedCellId = null)}
|
|
111
112
|
>
|
|
112
|
-
<div {style} class="inner">
|
|
113
|
+
<div {style} class="inner" bind:this={ref}>
|
|
113
114
|
<slot />
|
|
114
115
|
</div>
|
|
115
116
|
</div>
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import { Icon } from "@budibase/bbui"
|
|
4
4
|
import GridPopover from "../overlays/GridPopover.svelte"
|
|
5
5
|
|
|
6
|
-
const { visibleColumns, scroll, width, subscribe, ui } =
|
|
6
|
+
const { visibleColumns, scroll, width, subscribe, ui, keyboardBlocked } =
|
|
7
|
+
getContext("grid")
|
|
7
8
|
|
|
8
9
|
let anchor
|
|
9
10
|
let isOpen = false
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
)
|
|
15
16
|
$: end = columnsWidth - 1 - $scroll.left
|
|
16
17
|
$: left = Math.min($width - 40, end)
|
|
18
|
+
$: keyboardBlocked.set(isOpen)
|
|
17
19
|
|
|
18
20
|
const open = () => {
|
|
19
21
|
ui.actions.blur()
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
<GridScrollWrapper scrollHorizontally attachHandlers>
|
|
210
210
|
<div class="row">
|
|
211
211
|
{#each $visibleColumns as column}
|
|
212
|
-
{@const cellId =
|
|
212
|
+
{@const cellId = getCellID(NewRowID, column.name)}
|
|
213
213
|
<DataCell
|
|
214
214
|
{cellId}
|
|
215
215
|
{column}
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
68
68
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
69
|
-
<div class="content"
|
|
69
|
+
<div class="content">
|
|
70
70
|
<GridScrollWrapper scrollVertically attachHandlers>
|
|
71
71
|
{#each $renderedRows as row, idx}
|
|
72
72
|
{@const rowSelected = !!$selectedRows[row._id]}
|
|
@@ -18,13 +18,6 @@ export const getCellID = (rowId, fieldName) => {
|
|
|
18
18
|
return `${rowId}${JOINING_CHARACTER}${fieldName}`
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export const getColor = (idx, opacity = 0.3) => {
|
|
22
|
-
if (idx == null || idx === -1) {
|
|
23
|
-
idx = 0
|
|
24
|
-
}
|
|
25
|
-
return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, ${opacity})`
|
|
26
|
-
}
|
|
27
|
-
|
|
28
21
|
export const getColumnIcon = column => {
|
|
29
22
|
if (column.schema.autocolumn) {
|
|
30
23
|
return "MagicWand"
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
config,
|
|
18
18
|
menu,
|
|
19
19
|
gridFocused,
|
|
20
|
+
keyboardBlocked,
|
|
20
21
|
} = getContext("grid")
|
|
21
22
|
|
|
22
23
|
const ignoredOriginSelectors = [
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
// Global key listener which intercepts all key events
|
|
30
31
|
const handleKeyDown = e => {
|
|
31
32
|
// Ignore completely if the grid is not focused
|
|
32
|
-
if (!$gridFocused) {
|
|
33
|
+
if (!$gridFocused || $keyboardBlocked) {
|
|
33
34
|
return
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
120
120
|
<div
|
|
121
121
|
class="v-scrollbar"
|
|
122
|
-
style="
|
|
122
|
+
style="top:{barTop}px; height:{barHeight}px;"
|
|
123
123
|
on:mousedown={startVDragging}
|
|
124
124
|
on:touchstart={startVDragging}
|
|
125
125
|
class:dragging={isDraggingV}
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
130
130
|
<div
|
|
131
131
|
class="h-scrollbar"
|
|
132
|
-
style="
|
|
132
|
+
style="left:{barLeft}px; width:{barWidth}px;"
|
|
133
133
|
on:mousedown={startHDragging}
|
|
134
134
|
on:touchstart={startHDragging}
|
|
135
135
|
class:dragging={isDraggingH}
|
|
@@ -149,11 +149,11 @@
|
|
|
149
149
|
opacity: 1;
|
|
150
150
|
}
|
|
151
151
|
.v-scrollbar {
|
|
152
|
-
width: var(--size);
|
|
153
|
-
right: var(--size);
|
|
152
|
+
width: var(--scroll-bar-size);
|
|
153
|
+
right: var(--scroll-bar-size);
|
|
154
154
|
}
|
|
155
155
|
.h-scrollbar {
|
|
156
|
-
height: var(--size);
|
|
157
|
-
bottom: var(--size);
|
|
156
|
+
height: var(--scroll-bar-size);
|
|
157
|
+
bottom: var(--scroll-bar-size);
|
|
158
158
|
}
|
|
159
159
|
</style>
|
|
@@ -404,8 +404,11 @@ export const createActions = context => {
|
|
|
404
404
|
|
|
405
405
|
// Save change
|
|
406
406
|
try {
|
|
407
|
-
//
|
|
408
|
-
inProgressChanges.update(state => ({
|
|
407
|
+
// Increment change count for this row
|
|
408
|
+
inProgressChanges.update(state => ({
|
|
409
|
+
...state,
|
|
410
|
+
[rowId]: (state[rowId] || 0) + 1,
|
|
411
|
+
}))
|
|
409
412
|
|
|
410
413
|
// Update row
|
|
411
414
|
const changes = get(rowChangeCache)[rowId]
|
|
@@ -423,17 +426,25 @@ export const createActions = context => {
|
|
|
423
426
|
await refreshRow(saved.id)
|
|
424
427
|
}
|
|
425
428
|
|
|
426
|
-
// Wipe row change cache
|
|
429
|
+
// Wipe row change cache for any values which have been saved
|
|
430
|
+
const liveChanges = get(rowChangeCache)[rowId]
|
|
427
431
|
rowChangeCache.update(state => {
|
|
428
|
-
|
|
432
|
+
Object.keys(changes || {}).forEach(key => {
|
|
433
|
+
if (changes[key] === liveChanges?.[key]) {
|
|
434
|
+
delete state[rowId][key]
|
|
435
|
+
}
|
|
436
|
+
})
|
|
429
437
|
return state
|
|
430
438
|
})
|
|
431
439
|
} catch (error) {
|
|
432
440
|
handleValidationError(rowId, error)
|
|
433
441
|
}
|
|
434
442
|
|
|
435
|
-
//
|
|
436
|
-
inProgressChanges.update(state => ({
|
|
443
|
+
// Decrement change count for this row
|
|
444
|
+
inProgressChanges.update(state => ({
|
|
445
|
+
...state,
|
|
446
|
+
[rowId]: (state[rowId] || 1) - 1,
|
|
447
|
+
}))
|
|
437
448
|
}
|
|
438
449
|
|
|
439
450
|
// Updates a value of a row
|
|
@@ -553,7 +564,6 @@ export const initialise = context => {
|
|
|
553
564
|
previousFocusedCellId,
|
|
554
565
|
rows,
|
|
555
566
|
validation,
|
|
556
|
-
focusedCellId,
|
|
557
567
|
} = context
|
|
558
568
|
|
|
559
569
|
// Wipe the row change cache when changing row
|
|
@@ -571,20 +581,12 @@ export const initialise = context => {
|
|
|
571
581
|
if (!id) {
|
|
572
582
|
return
|
|
573
583
|
}
|
|
574
|
-
|
|
575
|
-
const
|
|
576
|
-
const
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
return
|
|
581
|
-
}
|
|
582
|
-
// Otherwise we just changed cell in the same row
|
|
583
|
-
const hasChanges = oldColumn in (get(rowChangeCache)[oldRowId] || {})
|
|
584
|
-
const hasErrors = validation.actions.rowHasErrors(oldRowId)
|
|
585
|
-
const isSavingChanges = get(inProgressChanges)[oldRowId]
|
|
586
|
-
if (oldRowId && !hasErrors && hasChanges && !isSavingChanges) {
|
|
587
|
-
await rows.actions.applyRowChanges(oldRowId)
|
|
584
|
+
const { id: rowId, field } = parseCellID(id)
|
|
585
|
+
const hasChanges = field in (get(rowChangeCache)[rowId] || {})
|
|
586
|
+
const hasErrors = validation.actions.rowHasErrors(rowId)
|
|
587
|
+
const isSavingChanges = get(inProgressChanges)[rowId]
|
|
588
|
+
if (rowId && !hasErrors && hasChanges && !isSavingChanges) {
|
|
589
|
+
await rows.actions.applyRowChanges(rowId)
|
|
588
590
|
}
|
|
589
591
|
})
|
|
590
592
|
}
|
|
@@ -109,6 +109,7 @@ export const initialise = context => {
|
|
|
109
109
|
maxScrollTop,
|
|
110
110
|
scrollLeft,
|
|
111
111
|
maxScrollLeft,
|
|
112
|
+
buttonColumnWidth,
|
|
112
113
|
} = context
|
|
113
114
|
|
|
114
115
|
// Ensure scroll state never goes invalid, which can happen when changing
|
|
@@ -194,8 +195,10 @@ export const initialise = context => {
|
|
|
194
195
|
|
|
195
196
|
// Ensure column is not cutoff on right edge
|
|
196
197
|
else {
|
|
198
|
+
const $buttonColumnWidth = get(buttonColumnWidth)
|
|
197
199
|
const rightEdge = column.left + column.width
|
|
198
|
-
const rightBound =
|
|
200
|
+
const rightBound =
|
|
201
|
+
$bounds.width + $scroll.left - FocusedCellMinOffset - $buttonColumnWidth
|
|
199
202
|
delta = rightEdge - rightBound
|
|
200
203
|
if (delta > 0) {
|
|
201
204
|
scroll.update(state => ({
|
|
@@ -19,6 +19,7 @@ export const createStores = context => {
|
|
|
19
19
|
const previousFocusedRowId = writable(null)
|
|
20
20
|
const previousFocusedCellId = writable(null)
|
|
21
21
|
const gridFocused = writable(false)
|
|
22
|
+
const keyboardBlocked = writable(false)
|
|
22
23
|
const isDragging = writable(false)
|
|
23
24
|
const buttonColumnWidth = writable(0)
|
|
24
25
|
|
|
@@ -54,6 +55,7 @@ export const createStores = context => {
|
|
|
54
55
|
hoveredRowId,
|
|
55
56
|
rowHeight,
|
|
56
57
|
gridFocused,
|
|
58
|
+
keyboardBlocked,
|
|
57
59
|
isDragging,
|
|
58
60
|
buttonColumnWidth,
|
|
59
61
|
selectedRows: {
|
package/src/constants.js
CHANGED
|
@@ -6,6 +6,15 @@ export { Feature as Features } from "@budibase/types"
|
|
|
6
6
|
import { BpmCorrelationKey } from "@budibase/shared-core"
|
|
7
7
|
import { FieldType, BBReferenceFieldSubType } from "@budibase/types"
|
|
8
8
|
|
|
9
|
+
export const BannedSearchTypes = [
|
|
10
|
+
FieldType.LINK,
|
|
11
|
+
FieldType.ATTACHMENTS,
|
|
12
|
+
FieldType.FORMULA,
|
|
13
|
+
FieldType.JSON,
|
|
14
|
+
"jsonarray",
|
|
15
|
+
"queryarray",
|
|
16
|
+
]
|
|
17
|
+
|
|
9
18
|
// Cookie names
|
|
10
19
|
export const Cookies = {
|
|
11
20
|
Auth: "budibase:auth",
|
|
@@ -141,3 +150,7 @@ export const TypeIconMap = {
|
|
|
141
150
|
[BBReferenceFieldSubType.USER]: "User",
|
|
142
151
|
},
|
|
143
152
|
}
|
|
153
|
+
|
|
154
|
+
export const OptionColours = [...new Array(12).keys()].map(idx => {
|
|
155
|
+
return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, 0.3)`
|
|
156
|
+
})
|
package/src/utils/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * as CookieUtils from "./cookies"
|
|
|
4
4
|
export * as RoleUtils from "./roles"
|
|
5
5
|
export * as Utils from "./utils"
|
|
6
6
|
export * as RowUtils from "./rows"
|
|
7
|
+
export * as search from "./searchFields"
|
|
7
8
|
export { memo, derivedMemo } from "./memo"
|
|
8
9
|
export { createWebsocket } from "./websocket"
|
|
9
10
|
export * from "./download"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BannedSearchTypes } from "../constants"
|
|
2
|
+
|
|
3
|
+
export function getTableFields(tables, linkField) {
|
|
4
|
+
const table = tables.find(table => table._id === linkField.tableId)
|
|
5
|
+
// TODO: mdrury - add support for this with SQS at some point
|
|
6
|
+
if (!table || !table.sql) {
|
|
7
|
+
return []
|
|
8
|
+
}
|
|
9
|
+
const linkFields = getFields(tables, Object.values(table.schema), {
|
|
10
|
+
allowLinks: false,
|
|
11
|
+
})
|
|
12
|
+
return linkFields.map(field => ({
|
|
13
|
+
...field,
|
|
14
|
+
name: `${table.name}.${field.name}`,
|
|
15
|
+
}))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getFields(
|
|
19
|
+
tables,
|
|
20
|
+
fields,
|
|
21
|
+
{ allowLinks } = { allowLinks: true }
|
|
22
|
+
) {
|
|
23
|
+
let filteredFields = fields.filter(
|
|
24
|
+
field => !BannedSearchTypes.includes(field.type)
|
|
25
|
+
)
|
|
26
|
+
if (allowLinks) {
|
|
27
|
+
const linkFields = fields.filter(field => field.type === "link")
|
|
28
|
+
for (let linkField of linkFields) {
|
|
29
|
+
// only allow one depth of SQL relationship filtering
|
|
30
|
+
filteredFields = filteredFields.concat(getTableFields(tables, linkField))
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const staticFormulaFields = fields.filter(
|
|
34
|
+
field => field.type === "formula" && field.formulaType === "static"
|
|
35
|
+
)
|
|
36
|
+
return filteredFields.concat(staticFormulaFields)
|
|
37
|
+
}
|