@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
|
@@ -1,37 +1,33 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { getContext } from "svelte"
|
|
3
3
|
import GridScrollWrapper from "../layout/GridScrollWrapper.svelte"
|
|
4
|
-
import { DefaultRowHeight
|
|
4
|
+
import { DefaultRowHeight } from "../lib/constants"
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
7
|
isReordering,
|
|
8
8
|
reorder,
|
|
9
|
-
|
|
10
|
-
stickyColumn,
|
|
9
|
+
columnLookupMap,
|
|
11
10
|
rowHeight,
|
|
12
11
|
renderedRows,
|
|
13
12
|
scrollLeft,
|
|
13
|
+
stickyWidth,
|
|
14
14
|
} = getContext("grid")
|
|
15
15
|
|
|
16
|
-
$: targetColumn = $reorder.targetColumn
|
|
17
|
-
$:
|
|
18
|
-
$: left = getLeft(targetColumn,
|
|
16
|
+
$: targetColumn = $columnLookupMap[$reorder.targetColumn]
|
|
17
|
+
$: insertAfter = $reorder.insertAfter
|
|
18
|
+
$: left = getLeft(targetColumn, insertAfter, $scrollLeft)
|
|
19
19
|
$: height = $rowHeight * $renderedRows.length + DefaultRowHeight
|
|
20
20
|
$: style = `left:${left}px; height:${height}px;`
|
|
21
|
-
$: visible = $isReordering && left >=
|
|
21
|
+
$: visible = $isReordering && left >= $stickyWidth
|
|
22
22
|
|
|
23
|
-
const getLeft = (targetColumn,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return left
|
|
31
|
-
}
|
|
32
|
-
left += column.left + column.width
|
|
23
|
+
const getLeft = (targetColumn, insertAfter, scrollLeft) => {
|
|
24
|
+
if (!targetColumn) {
|
|
25
|
+
return 0
|
|
26
|
+
}
|
|
27
|
+
let left = targetColumn.__left - scrollLeft
|
|
28
|
+
if (insertAfter) {
|
|
29
|
+
left += targetColumn.width
|
|
33
30
|
}
|
|
34
|
-
|
|
35
31
|
return left
|
|
36
32
|
}
|
|
37
33
|
</script>
|
|
@@ -1,41 +1,28 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { getContext } from "svelte"
|
|
3
|
-
import { GutterWidth } from "../lib/constants"
|
|
4
3
|
|
|
5
|
-
const { resize, visibleColumns,
|
|
4
|
+
const { resize, visibleColumns, isReordering, scrollLeft } =
|
|
6
5
|
getContext("grid")
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
const getStyle = (column, scrollLeft) => {
|
|
8
|
+
let left = column.__left + column.width
|
|
9
|
+
if (!column.primaryDisplay) {
|
|
10
|
+
left -= scrollLeft
|
|
11
|
+
}
|
|
13
12
|
return `left:${left}px;`
|
|
14
13
|
}
|
|
15
14
|
</script>
|
|
16
15
|
|
|
17
16
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
18
17
|
{#if !$isReordering}
|
|
19
|
-
{#if $stickyColumn}
|
|
20
|
-
<div
|
|
21
|
-
class="resize-slider"
|
|
22
|
-
class:visible={activeColumn === $stickyColumn.name}
|
|
23
|
-
on:mousedown={e => resize.actions.startResizing($stickyColumn, e)}
|
|
24
|
-
on:touchstart={e => resize.actions.startResizing($stickyColumn, e)}
|
|
25
|
-
on:dblclick={() => resize.actions.resetSize($stickyColumn)}
|
|
26
|
-
style="left:{GutterWidth + $stickyColumn.width}px;"
|
|
27
|
-
>
|
|
28
|
-
<div class="resize-indicator" />
|
|
29
|
-
</div>
|
|
30
|
-
{/if}
|
|
31
18
|
{#each $visibleColumns as column}
|
|
32
19
|
<div
|
|
33
20
|
class="resize-slider"
|
|
34
|
-
class:visible={
|
|
21
|
+
class:visible={$resize.column === column.name}
|
|
35
22
|
on:mousedown={e => resize.actions.startResizing(column, e)}
|
|
36
23
|
on:touchstart={e => resize.actions.startResizing(column, e)}
|
|
37
24
|
on:dblclick={() => resize.actions.resetSize(column)}
|
|
38
|
-
style={getStyle(column,
|
|
25
|
+
style={getStyle(column, $scrollLeft)}
|
|
39
26
|
>
|
|
40
27
|
<div class="resize-indicator" />
|
|
41
28
|
</div>
|
|
@@ -1,40 +1,237 @@
|
|
|
1
|
-
import { writable, get } from "svelte/store"
|
|
1
|
+
import { derived, writable, get } from "svelte/store"
|
|
2
2
|
import { Helpers } from "@budibase/bbui"
|
|
3
|
+
import { parseCellID, getCellID } from "../lib/utils"
|
|
4
|
+
import { NewRowID } from "../lib/constants"
|
|
3
5
|
|
|
4
6
|
export const createStores = () => {
|
|
5
|
-
const
|
|
7
|
+
const clipboard = writable({
|
|
8
|
+
value: null,
|
|
9
|
+
multiCellCopy: false,
|
|
10
|
+
})
|
|
11
|
+
return {
|
|
12
|
+
clipboard,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const deriveStores = context => {
|
|
17
|
+
const { clipboard, focusedCellAPI, selectedCellCount, config, focusedRowId } =
|
|
18
|
+
context
|
|
19
|
+
|
|
20
|
+
// Derive whether or not we're able to copy
|
|
21
|
+
const copyAllowed = derived(focusedCellAPI, $focusedCellAPI => {
|
|
22
|
+
return $focusedCellAPI != null
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// Derive whether or not we're able to paste
|
|
26
|
+
const pasteAllowed = derived(
|
|
27
|
+
[clipboard, focusedCellAPI, selectedCellCount, config, focusedRowId],
|
|
28
|
+
([
|
|
29
|
+
$clipboard,
|
|
30
|
+
$focusedCellAPI,
|
|
31
|
+
$selectedCellCount,
|
|
32
|
+
$config,
|
|
33
|
+
$focusedRowId,
|
|
34
|
+
]) => {
|
|
35
|
+
if (
|
|
36
|
+
$clipboard.value == null ||
|
|
37
|
+
!$config.canEditRows ||
|
|
38
|
+
!$focusedCellAPI ||
|
|
39
|
+
$focusedRowId === NewRowID
|
|
40
|
+
) {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Prevent single-single pasting if the cell is readonly
|
|
45
|
+
const multiCellPaste = $selectedCellCount > 1
|
|
46
|
+
if (
|
|
47
|
+
!$clipboard.multiCellCopy &&
|
|
48
|
+
!multiCellPaste &&
|
|
49
|
+
$focusedCellAPI.isReadonly()
|
|
50
|
+
) {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
)
|
|
6
56
|
|
|
7
57
|
return {
|
|
8
|
-
|
|
58
|
+
copyAllowed,
|
|
59
|
+
pasteAllowed,
|
|
9
60
|
}
|
|
10
61
|
}
|
|
11
62
|
|
|
12
63
|
export const createActions = context => {
|
|
13
|
-
const {
|
|
64
|
+
const {
|
|
65
|
+
clipboard,
|
|
66
|
+
focusedCellAPI,
|
|
67
|
+
copyAllowed,
|
|
68
|
+
pasteAllowed,
|
|
69
|
+
selectedCells,
|
|
70
|
+
selectedCellCount,
|
|
71
|
+
rowLookupMap,
|
|
72
|
+
rowChangeCache,
|
|
73
|
+
rows,
|
|
74
|
+
focusedCellId,
|
|
75
|
+
columnLookupMap,
|
|
76
|
+
visibleColumns,
|
|
77
|
+
} = context
|
|
14
78
|
|
|
79
|
+
// Copies the currently selected value (or values)
|
|
15
80
|
const copy = () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
81
|
+
if (!get(copyAllowed)) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
const $selectedCells = get(selectedCells)
|
|
85
|
+
const $focusedCellAPI = get(focusedCellAPI)
|
|
86
|
+
const $selectedCellCount = get(selectedCellCount)
|
|
87
|
+
const multiCellCopy = $selectedCellCount > 1
|
|
88
|
+
|
|
89
|
+
// Multiple values to copy
|
|
90
|
+
if (multiCellCopy) {
|
|
91
|
+
const $rowLookupMap = get(rowLookupMap)
|
|
92
|
+
const $rowChangeCache = get(rowChangeCache)
|
|
93
|
+
|
|
94
|
+
// Extract value of each selected cell, accounting for the change cache
|
|
95
|
+
let value = []
|
|
96
|
+
for (let row of $selectedCells) {
|
|
97
|
+
const rowValues = []
|
|
98
|
+
for (let cellId of row) {
|
|
99
|
+
const { rowId, field } = parseCellID(cellId)
|
|
100
|
+
const row = {
|
|
101
|
+
...$rowLookupMap[rowId],
|
|
102
|
+
...$rowChangeCache[rowId],
|
|
103
|
+
}
|
|
104
|
+
rowValues.push(row[field])
|
|
105
|
+
}
|
|
106
|
+
value.push(rowValues)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Update state
|
|
110
|
+
clipboard.set({
|
|
111
|
+
value,
|
|
112
|
+
multiCellCopy: true,
|
|
113
|
+
})
|
|
114
|
+
} else {
|
|
115
|
+
// Single value to copy
|
|
116
|
+
const value = $focusedCellAPI.getValue()
|
|
117
|
+
clipboard.set({
|
|
118
|
+
value,
|
|
119
|
+
multiCellCopy,
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// Also copy a stringified version to the clipboard
|
|
123
|
+
let stringified = ""
|
|
124
|
+
if (value != null && value !== "") {
|
|
125
|
+
// Only conditionally stringify to avoid redundant quotes around text
|
|
126
|
+
stringified = typeof value === "object" ? JSON.stringify(value) : value
|
|
127
|
+
}
|
|
128
|
+
Helpers.copyToClipboard(stringified)
|
|
24
129
|
}
|
|
25
|
-
Helpers.copyToClipboard(stringified)
|
|
26
130
|
}
|
|
27
131
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
132
|
+
// Pastes the previously copied value(s) into the selected cell(s)
|
|
133
|
+
const paste = async progressCallback => {
|
|
134
|
+
if (!get(pasteAllowed)) {
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
const { value, multiCellCopy } = get(clipboard)
|
|
138
|
+
const multiCellPaste = get(selectedCellCount) > 1
|
|
139
|
+
|
|
140
|
+
// Choose paste strategy
|
|
141
|
+
if (multiCellCopy) {
|
|
142
|
+
if (multiCellPaste) {
|
|
143
|
+
// Multi to multi - try pasting into all selected cells
|
|
144
|
+
let newValue = value
|
|
145
|
+
|
|
146
|
+
// If we are pasting into more rows than we copied, but the number of
|
|
147
|
+
// columns match, then repeat the copied values as required
|
|
148
|
+
const $selectedCells = get(selectedCells)
|
|
149
|
+
const selectedRows = $selectedCells.length
|
|
150
|
+
const selectedColumns = $selectedCells[0].length
|
|
151
|
+
const copiedRows = value.length
|
|
152
|
+
const copiedColumns = value[0].length
|
|
153
|
+
if (selectedRows > copiedRows && selectedColumns === copiedColumns) {
|
|
154
|
+
newValue = []
|
|
155
|
+
for (let i = 0; i < selectedRows; i++) {
|
|
156
|
+
newValue.push(value[i % copiedRows])
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Paste the new value
|
|
161
|
+
await pasteIntoSelectedCells(newValue, progressCallback)
|
|
162
|
+
} else {
|
|
163
|
+
// Multi to single - expand to paste all values
|
|
164
|
+
// Get indices of focused cell
|
|
165
|
+
const $focusedCellId = get(focusedCellId)
|
|
166
|
+
const { rowId, field } = parseCellID($focusedCellId)
|
|
167
|
+
const $rowLookupMap = get(rowLookupMap)
|
|
168
|
+
const $columnLookupMap = get(columnLookupMap)
|
|
169
|
+
const rowIdx = $rowLookupMap[rowId].__idx
|
|
170
|
+
const colIdx = $columnLookupMap[field].__idx
|
|
171
|
+
|
|
172
|
+
// Get limits of how many rows and columns we're able to paste into
|
|
173
|
+
const $rows = get(rows)
|
|
174
|
+
const $visibleColumns = get(visibleColumns)
|
|
175
|
+
const colCount = $visibleColumns.length
|
|
176
|
+
const rowCount = $rows.length
|
|
177
|
+
const selectedRows = value.length
|
|
178
|
+
const selectedColumns = value[0].length
|
|
179
|
+
const rowExtent = Math.min(selectedRows, rowCount - rowIdx) - 1
|
|
180
|
+
const colExtent = Math.min(selectedColumns, colCount - colIdx) - 1
|
|
181
|
+
|
|
182
|
+
// Get the target cell ID (bottom right of our pastable extent)
|
|
183
|
+
const targetRowId = $rows[rowIdx + rowExtent]._id
|
|
184
|
+
const targetColName = $visibleColumns[colIdx + colExtent].name
|
|
185
|
+
const targetCellId = getCellID(targetRowId, targetColName)
|
|
186
|
+
|
|
187
|
+
// Paste into target cell range
|
|
188
|
+
if (targetCellId === $focusedCellId) {
|
|
189
|
+
// Single cell edge case
|
|
190
|
+
get(focusedCellAPI).setValue(value[0][0])
|
|
191
|
+
} else {
|
|
192
|
+
// Select the new cells to paste into, then paste
|
|
193
|
+
selectedCells.actions.selectRange($focusedCellId, targetCellId)
|
|
194
|
+
await pasteIntoSelectedCells(value, progressCallback)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
if (multiCellPaste) {
|
|
199
|
+
// Single to multi - duplicate value to all selected cells
|
|
200
|
+
const newValue = get(selectedCells).map(row => row.map(() => value))
|
|
201
|
+
await pasteIntoSelectedCells(newValue, progressCallback)
|
|
202
|
+
} else {
|
|
203
|
+
// Single to single - just update the cell's value
|
|
204
|
+
get(focusedCellAPI).setValue(value)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Paste the specified value into the currently selected cells
|
|
210
|
+
const pasteIntoSelectedCells = async (value, progressCallback) => {
|
|
211
|
+
const $selectedCells = get(selectedCells)
|
|
212
|
+
|
|
213
|
+
// Find the extent at which we can paste
|
|
214
|
+
const rowExtent = Math.min(value.length, $selectedCells.length)
|
|
215
|
+
const colExtent = Math.min(value[0].length, $selectedCells[0].length)
|
|
216
|
+
|
|
217
|
+
// Build change map
|
|
218
|
+
let changeMap = {}
|
|
219
|
+
for (let rowIdx = 0; rowIdx < rowExtent; rowIdx++) {
|
|
220
|
+
for (let colIdx = 0; colIdx < colExtent; colIdx++) {
|
|
221
|
+
const cellId = $selectedCells[rowIdx][colIdx]
|
|
222
|
+
const { rowId, field } = parseCellID(cellId)
|
|
223
|
+
if (!changeMap[rowId]) {
|
|
224
|
+
changeMap[rowId] = {}
|
|
225
|
+
}
|
|
226
|
+
changeMap[rowId][field] = value[rowIdx][colIdx]
|
|
227
|
+
}
|
|
33
228
|
}
|
|
229
|
+
await rows.actions.bulkUpdate(changeMap, progressCallback)
|
|
34
230
|
}
|
|
35
231
|
|
|
36
232
|
return {
|
|
37
233
|
clipboard: {
|
|
234
|
+
...clipboard,
|
|
38
235
|
actions: {
|
|
39
236
|
copy,
|
|
40
237
|
paste,
|
|
@@ -1,74 +1,73 @@
|
|
|
1
1
|
import { derived, get, writable } from "svelte/store"
|
|
2
|
-
import {
|
|
2
|
+
import { DefaultColumnWidth, GutterWidth } from "../lib/constants"
|
|
3
3
|
|
|
4
4
|
export const createStores = () => {
|
|
5
5
|
const columns = writable([])
|
|
6
|
-
const stickyColumn = writable(null)
|
|
7
|
-
|
|
8
|
-
// Derive an enriched version of columns with left offsets and indexes
|
|
9
|
-
// automatically calculated
|
|
10
|
-
const enrichedColumns = derived(
|
|
11
|
-
columns,
|
|
12
|
-
$columns => {
|
|
13
|
-
let offset = 0
|
|
14
|
-
return $columns.map(column => {
|
|
15
|
-
const enriched = {
|
|
16
|
-
...column,
|
|
17
|
-
left: offset,
|
|
18
|
-
}
|
|
19
|
-
if (column.visible) {
|
|
20
|
-
offset += column.width
|
|
21
|
-
}
|
|
22
|
-
return enriched
|
|
23
|
-
})
|
|
24
|
-
},
|
|
25
|
-
[]
|
|
26
|
-
)
|
|
27
6
|
|
|
28
|
-
//
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
7
|
+
// Enrich columns with metadata about their display position
|
|
8
|
+
const enrichedColumns = derived(columns, $columns => {
|
|
9
|
+
let offset = GutterWidth
|
|
10
|
+
let idx = 0
|
|
11
|
+
return $columns.map(col => {
|
|
12
|
+
const enriched = {
|
|
13
|
+
...col,
|
|
14
|
+
__idx: idx,
|
|
15
|
+
__left: offset,
|
|
16
|
+
}
|
|
17
|
+
if (col.visible) {
|
|
18
|
+
idx++
|
|
19
|
+
offset += col.width
|
|
20
|
+
}
|
|
21
|
+
return enriched
|
|
22
|
+
})
|
|
23
|
+
})
|
|
36
24
|
|
|
37
25
|
return {
|
|
38
26
|
columns: {
|
|
39
27
|
...columns,
|
|
40
28
|
subscribe: enrichedColumns.subscribe,
|
|
41
29
|
},
|
|
42
|
-
stickyColumn,
|
|
43
|
-
visibleColumns,
|
|
44
30
|
}
|
|
45
31
|
}
|
|
46
32
|
|
|
47
33
|
export const deriveStores = context => {
|
|
48
|
-
const { columns
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
34
|
+
const { columns } = context
|
|
35
|
+
|
|
36
|
+
// Derive a lookup map for all columns by name
|
|
37
|
+
const columnLookupMap = derived(columns, $columns => {
|
|
38
|
+
let map = {}
|
|
39
|
+
$columns.forEach(column => {
|
|
40
|
+
map[column.name] = column
|
|
41
|
+
})
|
|
42
|
+
return map
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Derived list of columns which have not been explicitly hidden
|
|
46
|
+
const visibleColumns = derived(columns, $columns => {
|
|
47
|
+
return $columns.filter(col => col.visible)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Split visible columns into their discrete types
|
|
51
|
+
const displayColumn = derived(visibleColumns, $visibleColumns => {
|
|
52
|
+
return $visibleColumns.find(col => col.primaryDisplay)
|
|
53
|
+
})
|
|
54
|
+
const scrollableColumns = derived(visibleColumns, $visibleColumns => {
|
|
55
|
+
return $visibleColumns.filter(col => !col.primaryDisplay)
|
|
56
|
+
})
|
|
61
57
|
|
|
62
58
|
// Derive if we have any normal columns
|
|
63
|
-
const hasNonAutoColumn = derived(
|
|
64
|
-
const normalCols = $
|
|
59
|
+
const hasNonAutoColumn = derived(columns, $columns => {
|
|
60
|
+
const normalCols = $columns.filter(column => {
|
|
65
61
|
return !column.schema?.autocolumn
|
|
66
62
|
})
|
|
67
63
|
return normalCols.length > 0
|
|
68
64
|
})
|
|
69
65
|
|
|
70
66
|
return {
|
|
71
|
-
|
|
67
|
+
displayColumn,
|
|
68
|
+
columnLookupMap,
|
|
69
|
+
visibleColumns,
|
|
70
|
+
scrollableColumns,
|
|
72
71
|
hasNonAutoColumn,
|
|
73
72
|
}
|
|
74
73
|
}
|
|
@@ -87,60 +86,57 @@ export const createActions = context => {
|
|
|
87
86
|
await datasource.actions.saveSchemaMutations()
|
|
88
87
|
}
|
|
89
88
|
|
|
89
|
+
// Checks if a column is readonly
|
|
90
|
+
const isReadonly = column => {
|
|
91
|
+
if (!column?.schema) {
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
return (
|
|
95
|
+
column.schema.autocolumn ||
|
|
96
|
+
column.schema.disabled ||
|
|
97
|
+
column.schema.type === "formula" ||
|
|
98
|
+
column.schema.readonly
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
90
102
|
return {
|
|
91
103
|
columns: {
|
|
92
104
|
...columns,
|
|
93
105
|
actions: {
|
|
94
106
|
changeAllColumnWidths,
|
|
107
|
+
isReadonly,
|
|
95
108
|
},
|
|
96
109
|
},
|
|
97
110
|
}
|
|
98
111
|
}
|
|
99
112
|
|
|
100
113
|
export const initialise = context => {
|
|
101
|
-
const {
|
|
102
|
-
definition,
|
|
103
|
-
columns,
|
|
104
|
-
stickyColumn,
|
|
105
|
-
allColumns,
|
|
106
|
-
enrichedSchema,
|
|
107
|
-
compact,
|
|
108
|
-
} = context
|
|
114
|
+
const { definition, columns, displayColumn, enrichedSchema } = context
|
|
109
115
|
|
|
110
116
|
// Merge new schema fields with existing schema in order to preserve widths
|
|
111
117
|
const processColumns = $enrichedSchema => {
|
|
112
118
|
if (!$enrichedSchema) {
|
|
113
119
|
columns.set([])
|
|
114
|
-
stickyColumn.set(null)
|
|
115
120
|
return
|
|
116
121
|
}
|
|
117
122
|
const $definition = get(definition)
|
|
118
|
-
const $
|
|
119
|
-
const $
|
|
120
|
-
const $compact = get(compact)
|
|
123
|
+
const $columns = get(columns)
|
|
124
|
+
const $displayColumn = get(displayColumn)
|
|
121
125
|
|
|
122
126
|
// Find primary display
|
|
123
127
|
let primaryDisplay
|
|
124
|
-
const candidatePD = $definition.primaryDisplay || $
|
|
128
|
+
const candidatePD = $definition.primaryDisplay || $displayColumn?.name
|
|
125
129
|
if (candidatePD && $enrichedSchema[candidatePD]) {
|
|
126
130
|
primaryDisplay = candidatePD
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
// Get field list
|
|
130
|
-
let fields = []
|
|
131
|
-
Object.keys($enrichedSchema).forEach(field => {
|
|
132
|
-
if ($compact || field !== primaryDisplay) {
|
|
133
|
-
fields.push(field)
|
|
134
|
-
}
|
|
135
|
-
})
|
|
136
|
-
|
|
137
133
|
// Update columns, removing extraneous columns and adding missing ones
|
|
138
134
|
columns.set(
|
|
139
|
-
|
|
135
|
+
Object.keys($enrichedSchema)
|
|
140
136
|
.map(field => {
|
|
141
137
|
const fieldSchema = $enrichedSchema[field]
|
|
142
|
-
const oldColumn = $
|
|
143
|
-
|
|
138
|
+
const oldColumn = $columns?.find(col => col.name === field)
|
|
139
|
+
let column = {
|
|
144
140
|
name: field,
|
|
145
141
|
label: fieldSchema.displayName || field,
|
|
146
142
|
schema: fieldSchema,
|
|
@@ -148,19 +144,25 @@ export const initialise = context => {
|
|
|
148
144
|
visible: fieldSchema.visible ?? true,
|
|
149
145
|
readonly: fieldSchema.readonly,
|
|
150
146
|
order: fieldSchema.order ?? oldColumn?.order,
|
|
151
|
-
|
|
147
|
+
conditions: fieldSchema.conditions,
|
|
152
148
|
}
|
|
149
|
+
// Override a few properties for primary display
|
|
150
|
+
if (field === primaryDisplay) {
|
|
151
|
+
column.visible = true
|
|
152
|
+
column.order = 0
|
|
153
|
+
column.primaryDisplay = true
|
|
154
|
+
}
|
|
155
|
+
return column
|
|
153
156
|
})
|
|
154
157
|
.sort((a, b) => {
|
|
155
|
-
//
|
|
156
|
-
// the normal columns list, and should be first
|
|
158
|
+
// Display column should always come first
|
|
157
159
|
if (a.name === primaryDisplay) {
|
|
158
160
|
return -1
|
|
159
161
|
} else if (b.name === primaryDisplay) {
|
|
160
162
|
return 1
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
//
|
|
165
|
+
// Then sort by order
|
|
164
166
|
const orderA = a.order
|
|
165
167
|
const orderB = b.order
|
|
166
168
|
if (orderA != null && orderB != null) {
|
|
@@ -180,29 +182,8 @@ export const initialise = context => {
|
|
|
180
182
|
return autoColA ? 1 : -1
|
|
181
183
|
})
|
|
182
184
|
)
|
|
183
|
-
|
|
184
|
-
// Update sticky column
|
|
185
|
-
if ($compact || !primaryDisplay) {
|
|
186
|
-
stickyColumn.set(null)
|
|
187
|
-
return
|
|
188
|
-
}
|
|
189
|
-
const stickySchema = $enrichedSchema[primaryDisplay]
|
|
190
|
-
const oldStickyColumn = $allColumns?.find(x => x.name === primaryDisplay)
|
|
191
|
-
stickyColumn.set({
|
|
192
|
-
name: primaryDisplay,
|
|
193
|
-
label: stickySchema.displayName || primaryDisplay,
|
|
194
|
-
schema: stickySchema,
|
|
195
|
-
width: stickySchema.width || oldStickyColumn?.width || DefaultColumnWidth,
|
|
196
|
-
visible: true,
|
|
197
|
-
order: 0,
|
|
198
|
-
left: GutterWidth,
|
|
199
|
-
primaryDisplay: true,
|
|
200
|
-
})
|
|
201
185
|
}
|
|
202
186
|
|
|
203
187
|
// Process columns when schema changes
|
|
204
188
|
enrichedSchema.subscribe(processColumns)
|
|
205
|
-
|
|
206
|
-
// Process columns when compact flag changes
|
|
207
|
-
compact.subscribe(() => processColumns(get(enrichedSchema)))
|
|
208
189
|
}
|