@budibase/frontend-core 2.29.21 → 2.29.23
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 +1 -5
- package/src/components/grid/cells/DataCell.svelte +63 -12
- package/src/components/grid/cells/GridCell.svelte +36 -16
- package/src/components/grid/cells/GutterCell.svelte +15 -8
- package/src/components/grid/cells/HeaderCell.svelte +3 -3
- package/src/components/grid/cells/RelationshipCell.svelte +16 -8
- package/src/components/grid/controls/BulkDeleteHandler.svelte +98 -13
- package/src/components/grid/controls/BulkDuplicationHandler.svelte +79 -0
- package/src/components/grid/controls/ClipboardHandler.svelte +67 -0
- package/src/components/grid/controls/ColumnsSettingButton.svelte +5 -10
- package/src/components/grid/controls/SizeButton.svelte +6 -13
- package/src/components/grid/controls/SortButton.svelte +8 -22
- package/src/components/grid/layout/ButtonColumn.svelte +12 -6
- package/src/components/grid/layout/Grid.svelte +11 -7
- package/src/components/grid/layout/GridBody.svelte +2 -2
- package/src/components/grid/layout/GridRow.svelte +12 -6
- package/src/components/grid/layout/GridScrollWrapper.svelte +9 -7
- package/src/components/grid/layout/HeaderRow.svelte +2 -2
- package/src/components/grid/layout/NewColumnButton.svelte +11 -5
- package/src/components/grid/layout/NewRow.svelte +16 -12
- package/src/components/grid/layout/StickyColumn.svelte +24 -14
- package/src/components/grid/lib/utils.js +4 -4
- package/src/components/grid/overlays/KeyboardManager.svelte +144 -95
- package/src/components/grid/overlays/MenuOverlay.svelte +114 -63
- package/src/components/grid/overlays/ReorderOverlay.svelte +14 -18
- package/src/components/grid/overlays/ResizeOverlay.svelte +8 -21
- package/src/components/grid/stores/clipboard.js +215 -18
- package/src/components/grid/stores/columns.js +78 -97
- package/src/components/grid/stores/conditions.js +157 -0
- package/src/components/grid/stores/config.js +2 -2
- package/src/components/grid/stores/datasource.js +4 -14
- package/src/components/grid/stores/datasources/nonPlus.js +2 -4
- package/src/components/grid/stores/datasources/table.js +6 -5
- package/src/components/grid/stores/datasources/viewV2.js +7 -9
- package/src/components/grid/stores/index.js +5 -3
- package/src/components/grid/stores/menu.js +40 -6
- package/src/components/grid/stores/pagination.js +9 -3
- package/src/components/grid/stores/reorder.js +67 -42
- package/src/components/grid/stores/resize.js +1 -1
- package/src/components/grid/stores/rows.js +220 -85
- package/src/components/grid/stores/scroll.js +31 -28
- package/src/components/grid/stores/ui.js +295 -70
- package/src/components/grid/stores/users.js +2 -2
- package/src/components/grid/stores/validation.js +43 -16
- package/src/components/grid/stores/viewport.js +30 -24
- package/src/components/index.js +1 -0
- package/src/constants.js +3 -0
- package/src/themes/midnight.css +18 -17
- package/src/themes/nord.css +2 -1
- package/src/utils/utils.js +2 -0
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "2.29.
|
|
3
|
+
"version": "2.29.23",
|
|
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.29.
|
|
10
|
-
"@budibase/shared-core": "2.29.
|
|
11
|
-
"@budibase/types": "2.29.
|
|
9
|
+
"@budibase/bbui": "2.29.23",
|
|
10
|
+
"@budibase/shared-core": "2.29.23",
|
|
11
|
+
"@budibase/types": "2.29.23",
|
|
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": "7e4c562339380dedd324b8a0cf23ef36baebe676"
|
|
18
18
|
}
|
|
@@ -36,11 +36,7 @@
|
|
|
36
36
|
) &&
|
|
37
37
|
!schemaFields.some(field => field.name === "_id")
|
|
38
38
|
) {
|
|
39
|
-
schemaFields = [
|
|
40
|
-
...schemaFields,
|
|
41
|
-
{ name: "_id", type: "string" },
|
|
42
|
-
{ name: "_rev", type: "string" },
|
|
43
|
-
]
|
|
39
|
+
schemaFields = [...schemaFields, { name: "_id", type: "string" }]
|
|
44
40
|
}
|
|
45
41
|
}
|
|
46
42
|
|
|
@@ -4,12 +4,21 @@
|
|
|
4
4
|
import { getCellRenderer } from "../lib/renderers"
|
|
5
5
|
import { derived, writable } from "svelte/store"
|
|
6
6
|
|
|
7
|
-
const {
|
|
8
|
-
|
|
7
|
+
const {
|
|
8
|
+
rows,
|
|
9
|
+
columns,
|
|
10
|
+
focusedCellId,
|
|
11
|
+
focusedCellAPI,
|
|
12
|
+
menu,
|
|
13
|
+
config,
|
|
14
|
+
validation,
|
|
15
|
+
selectedCells,
|
|
16
|
+
selectedCellCount,
|
|
17
|
+
} = getContext("grid")
|
|
9
18
|
|
|
10
19
|
export let highlighted
|
|
11
|
-
export let selected
|
|
12
20
|
export let rowFocused
|
|
21
|
+
export let rowSelected
|
|
13
22
|
export let rowIdx
|
|
14
23
|
export let topRow = false
|
|
15
24
|
export let focused
|
|
@@ -20,29 +29,32 @@
|
|
|
20
29
|
export let updateValue = rows.actions.updateValue
|
|
21
30
|
export let contentLines = 1
|
|
22
31
|
export let hidden = false
|
|
32
|
+
export let isSelectingCells = false
|
|
33
|
+
export let cellSelected = false
|
|
23
34
|
|
|
24
35
|
const emptyError = writable(null)
|
|
25
36
|
|
|
26
37
|
let api
|
|
27
38
|
|
|
28
|
-
// Get the error for this cell if the
|
|
39
|
+
// Get the error for this cell if the cell is focused or selected
|
|
29
40
|
$: error = getErrorStore(rowFocused, cellId)
|
|
30
41
|
|
|
31
42
|
// Determine if the cell is editable
|
|
32
43
|
$: readonly =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
column.schema.type === "formula" ||
|
|
36
|
-
(!$config.canEditRows && !row._isNewRow) ||
|
|
37
|
-
column.schema.readonly
|
|
44
|
+
columns.actions.isReadonly(column) ||
|
|
45
|
+
(!$config.canEditRows && !row._isNewRow)
|
|
38
46
|
|
|
39
|
-
// Register this cell API if
|
|
47
|
+
// Register this cell API if this cell is focused
|
|
40
48
|
$: {
|
|
41
49
|
if (focused) {
|
|
42
50
|
focusedCellAPI.set(cellAPI)
|
|
43
51
|
}
|
|
44
52
|
}
|
|
45
53
|
|
|
54
|
+
// Callbacks for cell selection
|
|
55
|
+
$: updateSelectionCallback = isSelectingCells ? updateSelection : null
|
|
56
|
+
$: stopSelectionCallback = isSelectingCells ? stopSelection : null
|
|
57
|
+
|
|
46
58
|
const getErrorStore = (selected, cellId) => {
|
|
47
59
|
if (!selected) {
|
|
48
60
|
return emptyError
|
|
@@ -68,21 +80,60 @@
|
|
|
68
80
|
})
|
|
69
81
|
},
|
|
70
82
|
}
|
|
83
|
+
|
|
84
|
+
const startSelection = e => {
|
|
85
|
+
if (e.button !== 0 || e.shiftKey) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
selectedCells.actions.startSelecting(cellId)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const updateSelection = e => {
|
|
92
|
+
if (e.buttons !== 1) {
|
|
93
|
+
selectedCells.actions.stopSelecting()
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
selectedCells.actions.updateTarget(cellId)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const stopSelection = () => {
|
|
100
|
+
selectedCells.actions.stopSelecting()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const handleClick = e => {
|
|
104
|
+
if (e.shiftKey && $focusedCellId) {
|
|
105
|
+
// If we have a focused cell, select the range from that cell to here
|
|
106
|
+
selectedCells.actions.selectRange($focusedCellId, cellId)
|
|
107
|
+
} else if (e.shiftKey && $selectedCellCount) {
|
|
108
|
+
// If we already have a selected range of cell, update it
|
|
109
|
+
selectedCells.actions.updateTarget(cellId)
|
|
110
|
+
} else {
|
|
111
|
+
// Otherwise just select this cell
|
|
112
|
+
focusedCellId.set(cellId)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
71
115
|
</script>
|
|
72
116
|
|
|
73
117
|
<GridCell
|
|
74
118
|
{highlighted}
|
|
75
|
-
{selected}
|
|
76
119
|
{rowIdx}
|
|
77
120
|
{topRow}
|
|
78
121
|
{focused}
|
|
79
122
|
{selectedUser}
|
|
80
123
|
{readonly}
|
|
81
124
|
{hidden}
|
|
125
|
+
selected={rowSelected || cellSelected}
|
|
82
126
|
error={$error}
|
|
83
|
-
on:click={() => focusedCellId.set(cellId)}
|
|
84
127
|
on:contextmenu={e => menu.actions.open(cellId, e)}
|
|
128
|
+
on:mousedown={startSelection}
|
|
129
|
+
on:mouseenter={updateSelectionCallback}
|
|
130
|
+
on:mouseup={stopSelectionCallback}
|
|
131
|
+
on:click={handleClick}
|
|
85
132
|
width={column.width}
|
|
133
|
+
metadata={{
|
|
134
|
+
...row.__metadata?.row,
|
|
135
|
+
...row.__metadata?.cell[column.name],
|
|
136
|
+
}}
|
|
86
137
|
>
|
|
87
138
|
<svelte:component
|
|
88
139
|
this={getCellRenderer(column)}
|
|
@@ -11,13 +11,20 @@
|
|
|
11
11
|
export let center = false
|
|
12
12
|
export let readonly = false
|
|
13
13
|
export let hidden = false
|
|
14
|
+
export let metadata = null
|
|
14
15
|
|
|
15
|
-
$: style = getStyle(width, selectedUser)
|
|
16
|
+
$: style = getStyle(width, selectedUser, metadata)
|
|
16
17
|
|
|
17
|
-
const getStyle = (width, selectedUser) => {
|
|
18
|
+
const getStyle = (width, selectedUser, metadata) => {
|
|
18
19
|
let style = width === "auto" ? "width: auto;" : `flex: 0 0 ${width}px;`
|
|
19
20
|
if (selectedUser) {
|
|
20
|
-
style += `--user-color:${selectedUser.color};`
|
|
21
|
+
style += `--user-color :${selectedUser.color};`
|
|
22
|
+
}
|
|
23
|
+
if (metadata?.backgroundColor) {
|
|
24
|
+
style += `--cell-background: ${metadata.backgroundColor};`
|
|
25
|
+
}
|
|
26
|
+
if (metadata?.textColor) {
|
|
27
|
+
style += `--cell-font-color: ${metadata.textColor};`
|
|
21
28
|
}
|
|
22
29
|
return style
|
|
23
30
|
}
|
|
@@ -43,9 +50,10 @@
|
|
|
43
50
|
on:mouseup
|
|
44
51
|
on:click
|
|
45
52
|
on:contextmenu
|
|
46
|
-
on:touchstart
|
|
53
|
+
on:touchstart|passive
|
|
47
54
|
on:touchend
|
|
48
55
|
on:touchcancel
|
|
56
|
+
on:mouseenter
|
|
49
57
|
{style}
|
|
50
58
|
>
|
|
51
59
|
{#if error}
|
|
@@ -71,7 +79,7 @@
|
|
|
71
79
|
flex-direction: row;
|
|
72
80
|
justify-content: flex-start;
|
|
73
81
|
align-items: flex-start;
|
|
74
|
-
color: var(--
|
|
82
|
+
color: var(--cell-font-color);
|
|
75
83
|
font-size: var(--cell-font-size);
|
|
76
84
|
gap: var(--cell-spacing);
|
|
77
85
|
background: var(--cell-background);
|
|
@@ -93,9 +101,9 @@
|
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
/* Cell border */
|
|
96
|
-
.cell.focused
|
|
97
|
-
.cell.error
|
|
98
|
-
.cell.selected-other:not(.focused)
|
|
104
|
+
.cell.focused::after,
|
|
105
|
+
.cell.error::after,
|
|
106
|
+
.cell.selected-other:not(.focused)::after {
|
|
99
107
|
content: " ";
|
|
100
108
|
position: absolute;
|
|
101
109
|
top: 0;
|
|
@@ -108,14 +116,30 @@
|
|
|
108
116
|
box-sizing: border-box;
|
|
109
117
|
}
|
|
110
118
|
|
|
119
|
+
/* Cell background overlay */
|
|
120
|
+
.cell.selected::before {
|
|
121
|
+
content: " ";
|
|
122
|
+
position: absolute;
|
|
123
|
+
top: 0;
|
|
124
|
+
left: 0;
|
|
125
|
+
pointer-events: none;
|
|
126
|
+
box-sizing: border-box;
|
|
127
|
+
height: calc(100% + 1px);
|
|
128
|
+
width: calc(100% + 1px);
|
|
129
|
+
opacity: 0.16;
|
|
130
|
+
background: var(--spectrum-global-color-blue-400);
|
|
131
|
+
z-index: 2;
|
|
132
|
+
pointer-events: none;
|
|
133
|
+
}
|
|
134
|
+
|
|
111
135
|
/* Cell border for cells with labels */
|
|
112
|
-
.cell.error
|
|
136
|
+
.cell.error::after {
|
|
113
137
|
border-radius: 0 2px 2px 2px;
|
|
114
138
|
}
|
|
115
|
-
.cell.top.error
|
|
139
|
+
.cell.top.error::after {
|
|
116
140
|
border-radius: 2px 2px 2px 0;
|
|
117
141
|
}
|
|
118
|
-
.cell.selected-other:not(.focused)
|
|
142
|
+
.cell.selected-other:not(.focused)::after {
|
|
119
143
|
border-radius: 2px;
|
|
120
144
|
}
|
|
121
145
|
|
|
@@ -150,14 +174,10 @@
|
|
|
150
174
|
.cell.focused.readonly {
|
|
151
175
|
--cell-color: var(--spectrum-global-color-gray-600);
|
|
152
176
|
}
|
|
153
|
-
|
|
154
|
-
.cell.highlighted:not(.focused),
|
|
177
|
+
.cell.highlighted:not(.focused):not(.selected),
|
|
155
178
|
.cell.focused.readonly {
|
|
156
179
|
--cell-background: var(--cell-background-hover);
|
|
157
180
|
}
|
|
158
|
-
.cell.selected:not(.focused) {
|
|
159
|
-
--cell-background: var(--spectrum-global-color-blue-100);
|
|
160
|
-
}
|
|
161
181
|
|
|
162
182
|
/* Label for additional text */
|
|
163
183
|
.label {
|
|
@@ -16,14 +16,22 @@
|
|
|
16
16
|
const { config, dispatch, selectedRows } = getContext("grid")
|
|
17
17
|
const svelteDispatch = createEventDispatcher()
|
|
18
18
|
|
|
19
|
-
$: selectionEnabled = $config.canSelectRows || $config.canDeleteRows
|
|
20
|
-
|
|
21
19
|
const select = e => {
|
|
22
20
|
e.stopPropagation()
|
|
23
21
|
svelteDispatch("select")
|
|
24
22
|
const id = row?._id
|
|
25
23
|
if (id) {
|
|
26
|
-
|
|
24
|
+
// Bulk select with shift
|
|
25
|
+
if (e.shiftKey) {
|
|
26
|
+
// Prevent default if already selected, to prevent checkbox clearing
|
|
27
|
+
if (rowSelected) {
|
|
28
|
+
e.preventDefault()
|
|
29
|
+
} else {
|
|
30
|
+
selectedRows.actions.bulkSelectRows(id)
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
selectedRows.actions.toggleRow(id)
|
|
34
|
+
}
|
|
27
35
|
}
|
|
28
36
|
}
|
|
29
37
|
|
|
@@ -46,6 +54,7 @@
|
|
|
46
54
|
selected={rowSelected}
|
|
47
55
|
{defaultHeight}
|
|
48
56
|
rowIdx={row?.__idx}
|
|
57
|
+
metadata={row?.__metadata?.row}
|
|
49
58
|
>
|
|
50
59
|
<div class="gutter">
|
|
51
60
|
{#if $$slots.default}
|
|
@@ -54,16 +63,14 @@
|
|
|
54
63
|
<div
|
|
55
64
|
on:click={select}
|
|
56
65
|
class="checkbox"
|
|
57
|
-
class:visible={
|
|
58
|
-
(disableNumber || rowSelected || rowHovered || rowFocused)}
|
|
66
|
+
class:visible={disableNumber || rowSelected || rowHovered || rowFocused}
|
|
59
67
|
>
|
|
60
68
|
<Checkbox value={rowSelected} {disabled} />
|
|
61
69
|
</div>
|
|
62
70
|
{#if !disableNumber}
|
|
63
71
|
<div
|
|
64
72
|
class="number"
|
|
65
|
-
class:visible={!
|
|
66
|
-
!(rowSelected || rowHovered || rowFocused)}
|
|
73
|
+
class:visible={!(rowSelected || rowHovered || rowFocused)}
|
|
67
74
|
>
|
|
68
75
|
{row.__idx + 1}
|
|
69
76
|
</div>
|
|
@@ -109,7 +116,7 @@
|
|
|
109
116
|
margin: 3px 0 0 0;
|
|
110
117
|
}
|
|
111
118
|
.number {
|
|
112
|
-
color: var(--spectrum-global-color-gray-500);
|
|
119
|
+
color: val(--cell-font-color, var(--spectrum-global-color-gray-500));
|
|
113
120
|
}
|
|
114
121
|
.checkbox.visible,
|
|
115
122
|
.number.visible {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
isReordering,
|
|
19
19
|
isResizing,
|
|
20
20
|
sort,
|
|
21
|
-
|
|
21
|
+
scrollableColumns,
|
|
22
22
|
dispatch,
|
|
23
23
|
subscribe,
|
|
24
24
|
config,
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
|
|
52
52
|
$: sortedBy = column.name === $sort.column
|
|
53
53
|
$: canMoveLeft = orderable && idx > 0
|
|
54
|
-
$: canMoveRight = orderable && idx < $
|
|
54
|
+
$: canMoveRight = orderable && idx < $scrollableColumns.length - 1
|
|
55
55
|
$: sortingLabels = getSortingLabels(column.schema?.type)
|
|
56
56
|
$: searchable = isColumnSearchable(column)
|
|
57
57
|
$: resetSearchValue(column.name)
|
|
@@ -270,7 +270,7 @@
|
|
|
270
270
|
on:touchcancel={onMouseUp}
|
|
271
271
|
on:contextmenu={onContextMenu}
|
|
272
272
|
width={column.width}
|
|
273
|
-
left={column.
|
|
273
|
+
left={column.__left}
|
|
274
274
|
defaultHeight
|
|
275
275
|
center
|
|
276
276
|
>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const { API, cache } = getContext("grid")
|
|
9
9
|
|
|
10
|
-
export let value
|
|
10
|
+
export let value = []
|
|
11
11
|
export let api
|
|
12
12
|
export let readonly
|
|
13
13
|
export let focused
|
|
@@ -30,9 +30,10 @@
|
|
|
30
30
|
let container
|
|
31
31
|
let anchor
|
|
32
32
|
|
|
33
|
+
$: fieldValue = parseValue(value)
|
|
33
34
|
$: oneRowOnly = schema?.relationshipType === "one-to-many"
|
|
34
35
|
$: editable = focused && !readonly
|
|
35
|
-
$: lookupMap = buildLookupMap(
|
|
36
|
+
$: lookupMap = buildLookupMap(fieldValue, isOpen)
|
|
36
37
|
$: debouncedSearch(searchString)
|
|
37
38
|
$: {
|
|
38
39
|
if (!focused && isOpen) {
|
|
@@ -40,6 +41,13 @@
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
const parseValue = value => {
|
|
45
|
+
if (Array.isArray(value) && value.every(x => x?._id)) {
|
|
46
|
+
return value
|
|
47
|
+
}
|
|
48
|
+
return []
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
// Builds a lookup map to quickly check which rows are selected
|
|
44
52
|
const buildLookupMap = (value, isOpen) => {
|
|
45
53
|
let map = {}
|
|
@@ -177,13 +185,13 @@
|
|
|
177
185
|
|
|
178
186
|
// Toggles whether a row is included in the relationship or not
|
|
179
187
|
const toggleRow = async row => {
|
|
180
|
-
if (
|
|
188
|
+
if (fieldValue?.some(x => x._id === row._id)) {
|
|
181
189
|
// If the row is already included, remove it and update the candidate
|
|
182
190
|
// row to be the same position if possible
|
|
183
191
|
if (oneRowOnly) {
|
|
184
192
|
await onChange([])
|
|
185
193
|
} else {
|
|
186
|
-
const newValue =
|
|
194
|
+
const newValue = fieldValue.filter(x => x._id !== row._id)
|
|
187
195
|
if (!newValue.length) {
|
|
188
196
|
candidateIndex = null
|
|
189
197
|
} else {
|
|
@@ -196,7 +204,7 @@
|
|
|
196
204
|
if (oneRowOnly) {
|
|
197
205
|
await onChange([row])
|
|
198
206
|
} else {
|
|
199
|
-
await onChange(sortRows([...(
|
|
207
|
+
await onChange(sortRows([...(fieldValue || []), row]))
|
|
200
208
|
}
|
|
201
209
|
candidateIndex = null
|
|
202
210
|
}
|
|
@@ -238,7 +246,7 @@
|
|
|
238
246
|
class:wrap={editable || contentLines > 1}
|
|
239
247
|
on:wheel={e => (focused ? e.stopPropagation() : null)}
|
|
240
248
|
>
|
|
241
|
-
{#each
|
|
249
|
+
{#each fieldValue || [] as relationship}
|
|
242
250
|
{#if relationship[primaryDisplay] || relationship.primaryDisplay}
|
|
243
251
|
<div class="badge">
|
|
244
252
|
<span>
|
|
@@ -263,9 +271,9 @@
|
|
|
263
271
|
</div>
|
|
264
272
|
{/if}
|
|
265
273
|
</div>
|
|
266
|
-
{#if !hideCounter &&
|
|
274
|
+
{#if !hideCounter && fieldValue?.length}
|
|
267
275
|
<div class="count">
|
|
268
|
-
{
|
|
276
|
+
{fieldValue?.length || 0}
|
|
269
277
|
</div>
|
|
270
278
|
{/if}
|
|
271
279
|
</div>
|
|
@@ -1,35 +1,120 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { Modal, ModalContent } from "@budibase/bbui"
|
|
2
|
+
import { Modal, ModalContent, ProgressBar } from "@budibase/bbui"
|
|
3
3
|
import { getContext, onMount } from "svelte"
|
|
4
|
+
import { parseCellID } from "../lib/utils"
|
|
5
|
+
import { sleep } from "../../../utils/utils"
|
|
4
6
|
|
|
5
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
selectedRows,
|
|
9
|
+
rows,
|
|
10
|
+
subscribe,
|
|
11
|
+
notifications,
|
|
12
|
+
menu,
|
|
13
|
+
selectedCellCount,
|
|
14
|
+
selectedRowCount,
|
|
15
|
+
selectedCells,
|
|
16
|
+
rowLookupMap,
|
|
17
|
+
config,
|
|
18
|
+
} = getContext("grid")
|
|
19
|
+
const duration = 260
|
|
6
20
|
|
|
7
|
-
let
|
|
21
|
+
let rowsModal
|
|
22
|
+
let cellsModal
|
|
23
|
+
let processing = false
|
|
24
|
+
let progressPercentage = 0
|
|
25
|
+
let promptQuantity = 0
|
|
8
26
|
|
|
9
|
-
$:
|
|
10
|
-
|
|
11
|
-
.map(entry => $rows.find(x => x._id === entry[0]))
|
|
27
|
+
$: rowsToDelete = Object.keys($selectedRows)
|
|
28
|
+
.map(rowId => $rowLookupMap[rowId])
|
|
12
29
|
.filter(x => x != null)
|
|
13
30
|
|
|
14
|
-
|
|
15
|
-
|
|
31
|
+
const handleBulkDeleteRequest = () => {
|
|
32
|
+
progressPercentage = 0
|
|
33
|
+
menu.actions.close()
|
|
34
|
+
if ($selectedRowCount && $config.canDeleteRows) {
|
|
35
|
+
if ($selectedRowCount === 1) {
|
|
36
|
+
bulkDeleteRows()
|
|
37
|
+
} else {
|
|
38
|
+
promptQuantity = $selectedRowCount
|
|
39
|
+
rowsModal?.show()
|
|
40
|
+
}
|
|
41
|
+
} else if ($selectedCellCount && $config.canEditRows) {
|
|
42
|
+
promptQuantity = $selectedCellCount
|
|
43
|
+
cellsModal?.show()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const bulkDeleteRows = async () => {
|
|
48
|
+
processing = true
|
|
16
49
|
const count = rowsToDelete.length
|
|
17
50
|
await rows.actions.deleteRows(rowsToDelete)
|
|
51
|
+
// This is a real bulk delete endpoint so we don't need progress.
|
|
52
|
+
// We just animate it uo to 100 when we're done for consistency with other
|
|
53
|
+
// prompts.
|
|
54
|
+
progressPercentage = 100
|
|
55
|
+
await sleep(duration)
|
|
18
56
|
$notifications.success(`Deleted ${count} row${count === 1 ? "" : "s"}`)
|
|
57
|
+
processing = false
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const bulkDeleteCells = async () => {
|
|
61
|
+
processing = true
|
|
62
|
+
let changeMap = {}
|
|
63
|
+
for (let row of $selectedCells) {
|
|
64
|
+
for (let cellId of row) {
|
|
65
|
+
const { rowId, field } = parseCellID(cellId)
|
|
66
|
+
if (!changeMap[rowId]) {
|
|
67
|
+
changeMap[rowId] = {}
|
|
68
|
+
}
|
|
69
|
+
changeMap[rowId][field] = null
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
await rows.actions.bulkUpdate(changeMap, progress => {
|
|
73
|
+
progressPercentage = progress * 100
|
|
74
|
+
})
|
|
75
|
+
await sleep(duration)
|
|
76
|
+
processing = false
|
|
19
77
|
}
|
|
20
78
|
|
|
21
|
-
onMount(() => subscribe("request-bulk-delete",
|
|
79
|
+
onMount(() => subscribe("request-bulk-delete", handleBulkDeleteRequest))
|
|
22
80
|
</script>
|
|
23
81
|
|
|
24
|
-
<Modal bind:this={
|
|
82
|
+
<Modal bind:this={rowsModal}>
|
|
25
83
|
<ModalContent
|
|
26
84
|
title="Delete rows"
|
|
27
85
|
confirmText="Continue"
|
|
28
86
|
cancelText="Cancel"
|
|
29
|
-
onConfirm={
|
|
87
|
+
onConfirm={bulkDeleteRows}
|
|
88
|
+
size="M"
|
|
89
|
+
>
|
|
90
|
+
Are you sure you want to delete {promptQuantity} rows?
|
|
91
|
+
{#if processing}
|
|
92
|
+
<ProgressBar
|
|
93
|
+
size="L"
|
|
94
|
+
value={progressPercentage}
|
|
95
|
+
{duration}
|
|
96
|
+
width="100%"
|
|
97
|
+
/>
|
|
98
|
+
{/if}
|
|
99
|
+
</ModalContent>
|
|
100
|
+
</Modal>
|
|
101
|
+
|
|
102
|
+
<Modal bind:this={cellsModal}>
|
|
103
|
+
<ModalContent
|
|
104
|
+
title="Delete cells"
|
|
105
|
+
confirmText="Continue"
|
|
106
|
+
cancelText="Cancel"
|
|
107
|
+
onConfirm={bulkDeleteCells}
|
|
30
108
|
size="M"
|
|
31
109
|
>
|
|
32
|
-
Are you sure you want to delete {
|
|
33
|
-
|
|
110
|
+
Are you sure you want to delete {promptQuantity} cells?
|
|
111
|
+
{#if processing}
|
|
112
|
+
<ProgressBar
|
|
113
|
+
size="L"
|
|
114
|
+
value={progressPercentage}
|
|
115
|
+
{duration}
|
|
116
|
+
width="100%"
|
|
117
|
+
/>
|
|
118
|
+
{/if}
|
|
34
119
|
</ModalContent>
|
|
35
120
|
</Modal>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { Modal, ModalContent, ProgressBar } from "@budibase/bbui"
|
|
3
|
+
import { getContext, onMount } from "svelte"
|
|
4
|
+
import { getCellID } from "../lib/utils"
|
|
5
|
+
import { sleep } from "../../../utils/utils"
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
selectedRows,
|
|
9
|
+
rows,
|
|
10
|
+
subscribe,
|
|
11
|
+
selectedRowCount,
|
|
12
|
+
visibleColumns,
|
|
13
|
+
selectedCells,
|
|
14
|
+
rowLookupMap,
|
|
15
|
+
} = getContext("grid")
|
|
16
|
+
const duration = 260
|
|
17
|
+
|
|
18
|
+
let modal
|
|
19
|
+
let progressPercentage = 0
|
|
20
|
+
let processing = false
|
|
21
|
+
let promptQuantity = 0
|
|
22
|
+
|
|
23
|
+
// Deletion callback when confirmed
|
|
24
|
+
const performDuplication = async () => {
|
|
25
|
+
progressPercentage = 0
|
|
26
|
+
processing = true
|
|
27
|
+
|
|
28
|
+
// duplicate rows
|
|
29
|
+
const rowsToDuplicate = Object.keys($selectedRows).map(id => {
|
|
30
|
+
return $rowLookupMap[id]
|
|
31
|
+
})
|
|
32
|
+
const newRows = await rows.actions.bulkDuplicate(
|
|
33
|
+
rowsToDuplicate,
|
|
34
|
+
progress => {
|
|
35
|
+
progressPercentage = progress * 100
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
await sleep(duration)
|
|
39
|
+
|
|
40
|
+
// Select new cells to highlight them
|
|
41
|
+
if (newRows.length) {
|
|
42
|
+
const firstRow = newRows[0]
|
|
43
|
+
const lastRow = newRows[newRows.length - 1]
|
|
44
|
+
const firstCol = $visibleColumns[0]
|
|
45
|
+
const lastCol = $visibleColumns[$visibleColumns.length - 1]
|
|
46
|
+
const startCellId = getCellID(firstRow._id, firstCol.name)
|
|
47
|
+
const endCellId = getCellID(lastRow._id, lastCol.name)
|
|
48
|
+
selectedCells.actions.selectRange(startCellId, endCellId)
|
|
49
|
+
}
|
|
50
|
+
processing = false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const handleBulkDuplicateRequest = () => {
|
|
54
|
+
promptQuantity = $selectedRowCount
|
|
55
|
+
modal?.show()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onMount(() => subscribe("request-bulk-duplicate", handleBulkDuplicateRequest))
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<Modal bind:this={modal}>
|
|
62
|
+
<ModalContent
|
|
63
|
+
title="Duplicate rows"
|
|
64
|
+
confirmText="Continue"
|
|
65
|
+
cancelText="Cancel"
|
|
66
|
+
onConfirm={performDuplication}
|
|
67
|
+
size="M"
|
|
68
|
+
>
|
|
69
|
+
Are you sure you want to duplicate {promptQuantity} rows?
|
|
70
|
+
{#if processing}
|
|
71
|
+
<ProgressBar
|
|
72
|
+
size="L"
|
|
73
|
+
value={progressPercentage}
|
|
74
|
+
{duration}
|
|
75
|
+
width="100%"
|
|
76
|
+
/>
|
|
77
|
+
{/if}
|
|
78
|
+
</ModalContent>
|
|
79
|
+
</Modal>
|