@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
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { writable, get } from "svelte/store"
|
|
2
|
+
import { derivedMemo, QueryUtils } from "../../../utils"
|
|
3
|
+
import { FieldType, EmptyFilterOption } from "@budibase/types"
|
|
4
|
+
|
|
5
|
+
export const createStores = () => {
|
|
6
|
+
const metadata = writable({})
|
|
7
|
+
return {
|
|
8
|
+
metadata,
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const deriveStores = context => {
|
|
13
|
+
const { columns } = context
|
|
14
|
+
|
|
15
|
+
// Derive and memoize the cell conditions present in our columns so that we
|
|
16
|
+
// only recompute condition metadata when absolutely necessary
|
|
17
|
+
const conditions = derivedMemo(columns, $columns => {
|
|
18
|
+
let newConditions = []
|
|
19
|
+
for (let column of $columns) {
|
|
20
|
+
for (let condition of column.conditions || []) {
|
|
21
|
+
newConditions.push({
|
|
22
|
+
...condition,
|
|
23
|
+
column: column.name,
|
|
24
|
+
type: column.schema.type,
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return newConditions
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
conditions,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const initialise = context => {
|
|
37
|
+
const { metadata, conditions, rows } = context
|
|
38
|
+
|
|
39
|
+
// Recompute all metadata if conditions change
|
|
40
|
+
conditions.subscribe($conditions => {
|
|
41
|
+
let newMetadata = {}
|
|
42
|
+
if ($conditions?.length) {
|
|
43
|
+
for (let row of get(rows)) {
|
|
44
|
+
newMetadata[row._id] = evaluateConditions(row, $conditions)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
metadata.set(newMetadata)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Recompute metadata for specific rows when they change
|
|
51
|
+
rows.subscribe($rows => {
|
|
52
|
+
const $conditions = get(conditions)
|
|
53
|
+
if (!$conditions?.length) {
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
const $metadata = get(metadata)
|
|
57
|
+
let metadataUpdates = {}
|
|
58
|
+
for (let row of $rows) {
|
|
59
|
+
if (!row._rev || $metadata[row._id]?.version !== row._rev) {
|
|
60
|
+
metadataUpdates[row._id] = evaluateConditions(row, $conditions)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (Object.keys(metadataUpdates).length) {
|
|
64
|
+
metadata.update(state => ({
|
|
65
|
+
...state,
|
|
66
|
+
...metadataUpdates,
|
|
67
|
+
}))
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const TypeCoercionMap = {
|
|
73
|
+
[FieldType.NUMBER]: parseFloat,
|
|
74
|
+
[FieldType.DATETIME]: val => {
|
|
75
|
+
if (val) {
|
|
76
|
+
return new Date(val).toISOString()
|
|
77
|
+
}
|
|
78
|
+
return null
|
|
79
|
+
},
|
|
80
|
+
[FieldType.BOOLEAN]: val => {
|
|
81
|
+
if (`${val}`.toLowerCase().trim() === "true") {
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
if (`${val}`.toLowerCase().trim() === "false") {
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
return null
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Evaluates an array of cell conditions against a certain row and returns the
|
|
92
|
+
// resultant metadata
|
|
93
|
+
const evaluateConditions = (row, conditions) => {
|
|
94
|
+
let metadata = {
|
|
95
|
+
version: row._rev,
|
|
96
|
+
row: {},
|
|
97
|
+
cell: {},
|
|
98
|
+
}
|
|
99
|
+
for (let condition of conditions) {
|
|
100
|
+
try {
|
|
101
|
+
let {
|
|
102
|
+
column,
|
|
103
|
+
type,
|
|
104
|
+
referenceValue,
|
|
105
|
+
operator,
|
|
106
|
+
metadataKey,
|
|
107
|
+
metadataValue,
|
|
108
|
+
target,
|
|
109
|
+
} = condition
|
|
110
|
+
let value = row[column]
|
|
111
|
+
|
|
112
|
+
// Coerce values into correct types for primitives
|
|
113
|
+
let coercedType = type
|
|
114
|
+
if (type === FieldType.FORMULA) {
|
|
115
|
+
// For formulas we want to ensure that the reference type matches the
|
|
116
|
+
// real type
|
|
117
|
+
if (value === true || value === false) {
|
|
118
|
+
coercedType = FieldType.BOOLEAN
|
|
119
|
+
} else if (typeof value === "number") {
|
|
120
|
+
coercedType = FieldType.NUMBER
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const coerce = TypeCoercionMap[coercedType]
|
|
124
|
+
if (coerce) {
|
|
125
|
+
value = coerce(value)
|
|
126
|
+
referenceValue = coerce(referenceValue)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Build lucene compatible condition expression
|
|
130
|
+
const luceneFilter = {
|
|
131
|
+
operator,
|
|
132
|
+
type,
|
|
133
|
+
field: "value",
|
|
134
|
+
value: referenceValue,
|
|
135
|
+
}
|
|
136
|
+
let query = QueryUtils.buildQuery([luceneFilter])
|
|
137
|
+
query.onEmptyFilter = EmptyFilterOption.RETURN_NONE
|
|
138
|
+
const result = QueryUtils.runQuery([{ value }], query)
|
|
139
|
+
if (result.length > 0) {
|
|
140
|
+
if (target === "row") {
|
|
141
|
+
metadata.row = {
|
|
142
|
+
...metadata.row,
|
|
143
|
+
[metadataKey]: metadataValue,
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
metadata.cell[column] = {
|
|
147
|
+
...metadata.cell[column],
|
|
148
|
+
[metadataKey]: metadataValue,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
// Swallow
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return metadata
|
|
157
|
+
}
|
|
@@ -12,9 +12,9 @@ export const createStores = context => {
|
|
|
12
12
|
const initialFilter = getProp("initialFilter")
|
|
13
13
|
const fixedRowHeight = getProp("fixedRowHeight")
|
|
14
14
|
const schemaOverrides = getProp("schemaOverrides")
|
|
15
|
-
const columnWhitelist = getProp("columnWhitelist")
|
|
16
15
|
const notifySuccess = getProp("notifySuccess")
|
|
17
16
|
const notifyError = getProp("notifyError")
|
|
17
|
+
const rowConditions = getProp("rowConditions")
|
|
18
18
|
|
|
19
19
|
return {
|
|
20
20
|
datasource,
|
|
@@ -23,9 +23,9 @@ export const createStores = context => {
|
|
|
23
23
|
initialFilter,
|
|
24
24
|
fixedRowHeight,
|
|
25
25
|
schemaOverrides,
|
|
26
|
-
columnWhitelist,
|
|
27
26
|
notifySuccess,
|
|
28
27
|
notifyError,
|
|
28
|
+
rowConditions,
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -13,14 +13,8 @@ export const createStores = () => {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const deriveStores = context => {
|
|
16
|
-
const {
|
|
17
|
-
|
|
18
|
-
definition,
|
|
19
|
-
schemaOverrides,
|
|
20
|
-
columnWhitelist,
|
|
21
|
-
datasource,
|
|
22
|
-
schemaMutations,
|
|
23
|
-
} = context
|
|
16
|
+
const { API, definition, schemaOverrides, datasource, schemaMutations } =
|
|
17
|
+
context
|
|
24
18
|
|
|
25
19
|
const schema = derived(definition, $definition => {
|
|
26
20
|
let schema = getDatasourceSchema({
|
|
@@ -46,17 +40,13 @@ export const deriveStores = context => {
|
|
|
46
40
|
// Derives the total enriched schema, made up of the saved schema and any
|
|
47
41
|
// prop and user overrides
|
|
48
42
|
const enrichedSchema = derived(
|
|
49
|
-
[schema, schemaOverrides, schemaMutations
|
|
50
|
-
([$schema, $schemaOverrides, $schemaMutations
|
|
43
|
+
[schema, schemaOverrides, schemaMutations],
|
|
44
|
+
([$schema, $schemaOverrides, $schemaMutations]) => {
|
|
51
45
|
if (!$schema) {
|
|
52
46
|
return null
|
|
53
47
|
}
|
|
54
48
|
let enrichedSchema = {}
|
|
55
49
|
Object.keys($schema).forEach(field => {
|
|
56
|
-
// Apply whitelist if provided
|
|
57
|
-
if ($columnWhitelist?.length && !$columnWhitelist.includes(field)) {
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
50
|
enrichedSchema[field] = {
|
|
61
51
|
...$schema[field],
|
|
62
52
|
...$schemaOverrides?.[field],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { get } from "svelte/store"
|
|
2
2
|
|
|
3
3
|
export const createActions = context => {
|
|
4
|
-
const { columns,
|
|
4
|
+
const { columns, table, viewV2 } = context
|
|
5
5
|
|
|
6
6
|
const saveDefinition = async () => {
|
|
7
7
|
throw "This datasource does not support updating the definition"
|
|
@@ -30,9 +30,7 @@ export const createActions = context => {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const canUseColumn = name => {
|
|
33
|
-
|
|
34
|
-
const $sticky = get(stickyColumn)
|
|
35
|
-
return $columns.some(col => col.name === name) || $sticky?.name === name
|
|
33
|
+
return get(columns).some(col => col.name === name)
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
return {
|
|
@@ -3,14 +3,17 @@ import { get } from "svelte/store"
|
|
|
3
3
|
const SuppressErrors = true
|
|
4
4
|
|
|
5
5
|
export const createActions = context => {
|
|
6
|
-
const { API, datasource, columns
|
|
6
|
+
const { API, datasource, columns } = context
|
|
7
7
|
|
|
8
8
|
const saveDefinition = async newDefinition => {
|
|
9
9
|
await API.saveTable(newDefinition)
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const saveRow = async row => {
|
|
13
|
-
row
|
|
13
|
+
row = {
|
|
14
|
+
...row,
|
|
15
|
+
tableId: get(datasource)?.tableId,
|
|
16
|
+
}
|
|
14
17
|
return await API.saveRow(row, SuppressErrors)
|
|
15
18
|
}
|
|
16
19
|
|
|
@@ -40,9 +43,7 @@ export const createActions = context => {
|
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
const canUseColumn = name => {
|
|
43
|
-
|
|
44
|
-
const $sticky = get(stickyColumn)
|
|
45
|
-
return $columns.some(col => col.name === name) || $sticky?.name === name
|
|
46
|
+
return get(columns).some(col => col.name === name)
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
return {
|
|
@@ -3,7 +3,7 @@ import { get } from "svelte/store"
|
|
|
3
3
|
const SuppressErrors = true
|
|
4
4
|
|
|
5
5
|
export const createActions = context => {
|
|
6
|
-
const { API, datasource, columns
|
|
6
|
+
const { API, datasource, columns } = context
|
|
7
7
|
|
|
8
8
|
const saveDefinition = async newDefinition => {
|
|
9
9
|
await API.viewV2.update(newDefinition)
|
|
@@ -11,8 +11,11 @@ export const createActions = context => {
|
|
|
11
11
|
|
|
12
12
|
const saveRow = async row => {
|
|
13
13
|
const $datasource = get(datasource)
|
|
14
|
-
row
|
|
15
|
-
|
|
14
|
+
row = {
|
|
15
|
+
...row,
|
|
16
|
+
tableId: $datasource?.tableId,
|
|
17
|
+
_viewId: $datasource?.id,
|
|
18
|
+
}
|
|
16
19
|
return {
|
|
17
20
|
...(await API.saveRow(row, SuppressErrors)),
|
|
18
21
|
_viewId: row._viewId,
|
|
@@ -37,12 +40,7 @@ export const createActions = context => {
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
const canUseColumn = name => {
|
|
40
|
-
|
|
41
|
-
const $sticky = get(stickyColumn)
|
|
42
|
-
return (
|
|
43
|
-
$columns.some(col => col.name === name && col.visible) ||
|
|
44
|
-
$sticky?.name === name
|
|
45
|
-
)
|
|
43
|
+
return get(columns).some(col => col.name === name && col.visible)
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
return {
|
|
@@ -20,28 +20,30 @@ import * as Table from "./datasources/table"
|
|
|
20
20
|
import * as ViewV2 from "./datasources/viewV2"
|
|
21
21
|
import * as NonPlus from "./datasources/nonPlus"
|
|
22
22
|
import * as Cache from "./cache"
|
|
23
|
+
import * as Conditions from "./conditions"
|
|
23
24
|
|
|
24
25
|
const DependencyOrderedStores = [
|
|
25
26
|
Sort,
|
|
26
27
|
Filter,
|
|
27
28
|
Bounds,
|
|
28
|
-
Scroll,
|
|
29
29
|
Table,
|
|
30
30
|
ViewV2,
|
|
31
31
|
NonPlus,
|
|
32
32
|
Datasource,
|
|
33
33
|
Columns,
|
|
34
|
+
Scroll,
|
|
35
|
+
Validation,
|
|
34
36
|
Rows,
|
|
37
|
+
Conditions,
|
|
35
38
|
UI,
|
|
36
|
-
Validation,
|
|
37
39
|
Resize,
|
|
38
40
|
Viewport,
|
|
39
41
|
Reorder,
|
|
40
42
|
Users,
|
|
41
43
|
Menu,
|
|
42
44
|
Pagination,
|
|
43
|
-
Clipboard,
|
|
44
45
|
Config,
|
|
46
|
+
Clipboard,
|
|
45
47
|
Notifications,
|
|
46
48
|
Cache,
|
|
47
49
|
]
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { writable } from "svelte/store"
|
|
1
|
+
import { writable, get } from "svelte/store"
|
|
2
|
+
import { parseCellID } from "../lib/utils"
|
|
2
3
|
|
|
3
4
|
export const createStores = () => {
|
|
4
5
|
const menu = writable({
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
left: 0,
|
|
7
|
+
top: 0,
|
|
7
8
|
visible: false,
|
|
8
|
-
|
|
9
|
+
multiRowMode: false,
|
|
10
|
+
multiCellMode: false,
|
|
9
11
|
})
|
|
10
12
|
return {
|
|
11
13
|
menu,
|
|
@@ -13,7 +15,15 @@ export const createStores = () => {
|
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export const createActions = context => {
|
|
16
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
menu,
|
|
20
|
+
focusedCellId,
|
|
21
|
+
gridID,
|
|
22
|
+
selectedRows,
|
|
23
|
+
selectedRowCount,
|
|
24
|
+
selectedCellMap,
|
|
25
|
+
selectedCellCount,
|
|
26
|
+
} = context
|
|
17
27
|
|
|
18
28
|
const open = (cellId, e) => {
|
|
19
29
|
e.preventDefault()
|
|
@@ -29,11 +39,35 @@ export const createActions = context => {
|
|
|
29
39
|
// Compute bounds of cell relative to outer data node
|
|
30
40
|
const targetBounds = e.target.getBoundingClientRect()
|
|
31
41
|
const dataBounds = dataNode.getBoundingClientRect()
|
|
32
|
-
|
|
42
|
+
|
|
43
|
+
// Check if there are multiple rows selected, and if this is one of them
|
|
44
|
+
let multiRowMode = false
|
|
45
|
+
if (get(selectedRowCount) > 1) {
|
|
46
|
+
const { rowId } = parseCellID(cellId)
|
|
47
|
+
if (get(selectedRows)[rowId]) {
|
|
48
|
+
multiRowMode = true
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check if there are multiple cells selected, and if this is one of them
|
|
53
|
+
let multiCellMode = false
|
|
54
|
+
if (!multiRowMode && get(selectedCellCount) > 1) {
|
|
55
|
+
if (get(selectedCellMap)[cellId]) {
|
|
56
|
+
multiCellMode = true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Only focus this cell if not in multi row mode
|
|
61
|
+
if (!multiRowMode && !multiCellMode) {
|
|
62
|
+
focusedCellId.set(cellId)
|
|
63
|
+
}
|
|
64
|
+
|
|
33
65
|
menu.set({
|
|
34
66
|
left: targetBounds.left - dataBounds.left + e.offsetX,
|
|
35
67
|
top: targetBounds.top - dataBounds.top + e.offsetY,
|
|
36
68
|
visible: true,
|
|
69
|
+
multiRowMode,
|
|
70
|
+
multiCellMode,
|
|
37
71
|
})
|
|
38
72
|
}
|
|
39
73
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { derived
|
|
1
|
+
import { derived } from "svelte/store"
|
|
2
2
|
|
|
3
3
|
export const initialise = context => {
|
|
4
4
|
const { scrolledRowCount, rows, visualRowCapacity } = context
|
|
@@ -15,8 +15,14 @@ export const initialise = context => {
|
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
// Fetch next page when fewer than 25 remaining rows to scroll
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const needsNewPage = derived(
|
|
19
|
+
[remainingRows, rowCount],
|
|
20
|
+
([$remainingRows, $rowCount]) => {
|
|
21
|
+
return $remainingRows < 25 && $rowCount
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
needsNewPage.subscribe($needsNewPage => {
|
|
25
|
+
if ($needsNewPage) {
|
|
20
26
|
rows.actions.loadNextPage()
|
|
21
27
|
}
|
|
22
28
|
})
|
|
@@ -4,10 +4,10 @@ import { parseEventLocation } from "../lib/utils"
|
|
|
4
4
|
const reorderInitialState = {
|
|
5
5
|
sourceColumn: null,
|
|
6
6
|
targetColumn: null,
|
|
7
|
+
insertAfter: false,
|
|
7
8
|
breakpoints: [],
|
|
8
9
|
gridLeft: 0,
|
|
9
10
|
width: 0,
|
|
10
|
-
latestX: 0,
|
|
11
11
|
increment: 0,
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -28,38 +28,41 @@ export const createActions = context => {
|
|
|
28
28
|
const {
|
|
29
29
|
reorder,
|
|
30
30
|
columns,
|
|
31
|
-
|
|
31
|
+
columnLookupMap,
|
|
32
|
+
scrollableColumns,
|
|
32
33
|
scroll,
|
|
33
34
|
bounds,
|
|
34
|
-
|
|
35
|
-
maxScrollLeft,
|
|
36
|
-
width,
|
|
35
|
+
visibleColumns,
|
|
37
36
|
datasource,
|
|
37
|
+
stickyWidth,
|
|
38
|
+
width,
|
|
39
|
+
scrollLeft,
|
|
40
|
+
maxScrollLeft,
|
|
38
41
|
} = context
|
|
39
|
-
|
|
42
|
+
let latestX = 0
|
|
40
43
|
let autoScrollInterval
|
|
41
44
|
let isAutoScrolling
|
|
42
45
|
|
|
43
46
|
// Callback when dragging on a colum header and starting reordering
|
|
44
47
|
const startReordering = (column, e) => {
|
|
45
|
-
const $
|
|
48
|
+
const $scrollableColumns = get(scrollableColumns)
|
|
46
49
|
const $bounds = get(bounds)
|
|
47
|
-
const $
|
|
50
|
+
const $stickyWidth = get(stickyWidth)
|
|
48
51
|
|
|
49
52
|
// Generate new breakpoints for the current columns
|
|
50
|
-
|
|
51
|
-
x: col.
|
|
53
|
+
const breakpoints = $scrollableColumns.map(col => ({
|
|
54
|
+
x: col.__left - $stickyWidth,
|
|
52
55
|
column: col.name,
|
|
56
|
+
insertAfter: false,
|
|
53
57
|
}))
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
column: null,
|
|
58
|
+
|
|
59
|
+
// Add a very left breakpoint as well
|
|
60
|
+
const lastCol = $scrollableColumns[$scrollableColumns.length - 1]
|
|
61
|
+
if (lastCol) {
|
|
62
|
+
breakpoints.push({
|
|
63
|
+
x: lastCol.__left + lastCol.width - $stickyWidth,
|
|
64
|
+
column: lastCol.name,
|
|
65
|
+
insertAfter: true,
|
|
63
66
|
})
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -87,24 +90,23 @@ export const createActions = context => {
|
|
|
87
90
|
const onReorderMouseMove = e => {
|
|
88
91
|
// Immediately handle the current position
|
|
89
92
|
const { x } = parseEventLocation(e)
|
|
90
|
-
|
|
91
|
-
...state,
|
|
92
|
-
latestX: x,
|
|
93
|
-
}))
|
|
93
|
+
latestX = x
|
|
94
94
|
considerReorderPosition()
|
|
95
95
|
|
|
96
96
|
// Check if we need to start auto-scrolling
|
|
97
|
+
const $scrollLeft = get(scrollLeft)
|
|
98
|
+
const $maxScrollLeft = get(maxScrollLeft)
|
|
97
99
|
const $reorder = get(reorder)
|
|
98
100
|
const proximityCutoff = Math.min(140, get(width) / 6)
|
|
99
101
|
const speedFactor = 16
|
|
100
102
|
const rightProximity = Math.max(0, $reorder.gridLeft + $reorder.width - x)
|
|
101
103
|
const leftProximity = Math.max(0, x - $reorder.gridLeft)
|
|
102
|
-
if (rightProximity < proximityCutoff) {
|
|
104
|
+
if (rightProximity < proximityCutoff && $scrollLeft < $maxScrollLeft) {
|
|
103
105
|
const weight = proximityCutoff - rightProximity
|
|
104
106
|
const increment = (weight / proximityCutoff) * speedFactor
|
|
105
107
|
reorder.update(state => ({ ...state, increment }))
|
|
106
108
|
startAutoScroll()
|
|
107
|
-
} else if (leftProximity < proximityCutoff) {
|
|
109
|
+
} else if (leftProximity < proximityCutoff && $scrollLeft > 0) {
|
|
108
110
|
const weight = -1 * (proximityCutoff - leftProximity)
|
|
109
111
|
const increment = (weight / proximityCutoff) * speedFactor
|
|
110
112
|
reorder.update(state => ({ ...state, increment }))
|
|
@@ -117,23 +119,28 @@ export const createActions = context => {
|
|
|
117
119
|
// Actual logic to consider the current position and determine the new order
|
|
118
120
|
const considerReorderPosition = () => {
|
|
119
121
|
const $reorder = get(reorder)
|
|
120
|
-
const $
|
|
122
|
+
const $scrollLeft = get(scrollLeft)
|
|
121
123
|
|
|
122
124
|
// Compute the closest breakpoint to the current position
|
|
123
|
-
let
|
|
125
|
+
let breakpoint
|
|
124
126
|
let minDistance = Number.MAX_SAFE_INTEGER
|
|
125
|
-
const mouseX =
|
|
127
|
+
const mouseX = latestX - $reorder.gridLeft + $scrollLeft
|
|
126
128
|
$reorder.breakpoints.forEach(point => {
|
|
127
129
|
const distance = Math.abs(point.x - mouseX)
|
|
128
130
|
if (distance < minDistance) {
|
|
129
131
|
minDistance = distance
|
|
130
|
-
|
|
132
|
+
breakpoint = point
|
|
131
133
|
}
|
|
132
134
|
})
|
|
133
|
-
if (
|
|
135
|
+
if (
|
|
136
|
+
breakpoint &&
|
|
137
|
+
(breakpoint.column !== $reorder.targetColumn ||
|
|
138
|
+
breakpoint.insertAfter !== $reorder.insertAfter)
|
|
139
|
+
) {
|
|
134
140
|
reorder.update(state => ({
|
|
135
141
|
...state,
|
|
136
|
-
targetColumn,
|
|
142
|
+
targetColumn: breakpoint.column,
|
|
143
|
+
insertAfter: breakpoint.insertAfter,
|
|
137
144
|
}))
|
|
138
145
|
}
|
|
139
146
|
}
|
|
@@ -175,20 +182,29 @@ export const createActions = context => {
|
|
|
175
182
|
document.removeEventListener("touchcancel", stopReordering)
|
|
176
183
|
|
|
177
184
|
// Ensure there's actually a change before saving
|
|
178
|
-
const { sourceColumn, targetColumn } = get(reorder)
|
|
185
|
+
const { sourceColumn, targetColumn, insertAfter } = get(reorder)
|
|
179
186
|
reorder.set(reorderInitialState)
|
|
180
187
|
if (sourceColumn !== targetColumn) {
|
|
181
|
-
await moveColumn(sourceColumn, targetColumn)
|
|
188
|
+
await moveColumn({ sourceColumn, targetColumn, insertAfter })
|
|
182
189
|
}
|
|
183
190
|
}
|
|
184
191
|
|
|
185
192
|
// Moves a column after another columns.
|
|
186
193
|
// An undefined target column will move the source to index 0.
|
|
187
|
-
const moveColumn = async (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
194
|
+
const moveColumn = async ({
|
|
195
|
+
sourceColumn,
|
|
196
|
+
targetColumn,
|
|
197
|
+
insertAfter = false,
|
|
198
|
+
}) => {
|
|
199
|
+
// Find the indices in the overall columns array
|
|
200
|
+
const $columns = get(columns)
|
|
201
|
+
let sourceIdx = $columns.findIndex(col => col.name === sourceColumn)
|
|
202
|
+
let targetIdx = $columns.findIndex(col => col.name === targetColumn)
|
|
203
|
+
if (insertAfter) {
|
|
204
|
+
targetIdx++
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Reorder columns
|
|
192
208
|
columns.update(state => {
|
|
193
209
|
const removed = state.splice(sourceIdx, 1)
|
|
194
210
|
if (--targetIdx < sourceIdx) {
|
|
@@ -209,18 +225,27 @@ export const createActions = context => {
|
|
|
209
225
|
// Moves a column one place left (as appears visually)
|
|
210
226
|
const moveColumnLeft = async column => {
|
|
211
227
|
const $visibleColumns = get(visibleColumns)
|
|
212
|
-
const
|
|
213
|
-
|
|
228
|
+
const $columnLookupMap = get(columnLookupMap)
|
|
229
|
+
const sourceIdx = $columnLookupMap[column].__idx
|
|
230
|
+
await moveColumn({
|
|
231
|
+
sourceColumn: column,
|
|
232
|
+
targetColumn: $visibleColumns[sourceIdx - 1]?.name,
|
|
233
|
+
})
|
|
214
234
|
}
|
|
215
235
|
|
|
216
236
|
// Moves a column one place right (as appears visually)
|
|
217
237
|
const moveColumnRight = async column => {
|
|
218
238
|
const $visibleColumns = get(visibleColumns)
|
|
219
|
-
const
|
|
239
|
+
const $columnLookupMap = get(columnLookupMap)
|
|
240
|
+
const sourceIdx = $columnLookupMap[column].__idx
|
|
220
241
|
if (sourceIdx === $visibleColumns.length - 1) {
|
|
221
242
|
return
|
|
222
243
|
}
|
|
223
|
-
await moveColumn(
|
|
244
|
+
await moveColumn({
|
|
245
|
+
sourceColumn: column,
|
|
246
|
+
targetColumn: $visibleColumns[sourceIdx + 1]?.name,
|
|
247
|
+
insertAfter: true,
|
|
248
|
+
})
|
|
224
249
|
}
|
|
225
250
|
|
|
226
251
|
return {
|