@budibase/frontend-core 2.6.22 → 2.6.24-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/api/app.js +6 -0
- package/src/api/automations.js +2 -2
- package/src/api/datasources.js +11 -0
- package/src/api/groups.js +17 -0
- package/src/api/index.js +10 -0
- package/src/api/tables.js +4 -2
- package/src/components/Testimonial.svelte +67 -0
- package/src/components/TestimonialPage.svelte +2 -61
- package/src/components/UserAvatar.svelte +58 -0
- package/src/components/grid/cells/BooleanCell.svelte +3 -0
- package/src/components/grid/cells/DataCell.svelte +3 -0
- package/src/components/grid/cells/DateCell.svelte +46 -4
- package/src/components/grid/cells/GridCell.svelte +36 -16
- package/src/components/grid/cells/GutterCell.svelte +2 -10
- package/src/components/grid/cells/HeaderCell.svelte +5 -1
- package/src/components/grid/layout/Grid.svelte +22 -5
- package/src/components/grid/layout/GridBody.svelte +5 -1
- package/src/components/grid/layout/GridRow.svelte +3 -2
- package/src/components/grid/layout/HeaderRow.svelte +1 -1
- package/src/components/grid/layout/KeyboardShortcut.svelte +1 -1
- package/src/components/grid/layout/NewRow.svelte +3 -3
- package/src/components/grid/layout/StickyColumn.svelte +2 -1
- package/src/components/grid/layout/UserAvatars.svelte +14 -4
- package/src/components/grid/lib/constants.js +1 -1
- package/src/components/grid/lib/websocket.js +40 -36
- package/src/components/grid/overlays/KeyboardManager.svelte +3 -4
- package/src/components/grid/overlays/MenuOverlay.svelte +34 -4
- package/src/components/grid/stores/columns.js +2 -2
- package/src/components/grid/stores/reorder.js +77 -9
- package/src/components/grid/stores/rows.js +27 -19
- package/src/components/grid/stores/ui.js +20 -1
- package/src/components/grid/stores/users.js +18 -63
- package/src/components/index.js +2 -0
- package/src/constants.js +1 -0
- package/src/fetch/DataFetch.js +23 -1
- package/src/fetch/GroupUserFetch.js +51 -0
- package/src/fetch/fetchData.js +2 -0
- package/src/utils/index.js +1 -0
- package/src/utils/websocket.js +60 -0
- package/src/components/grid/controls/BetaButton.svelte +0 -46
- package/src/components/grid/layout/Avatar.svelte +0 -24
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { setContext } from "svelte"
|
|
2
|
+
import { setContext, onMount } from "svelte"
|
|
3
3
|
import { writable } from "svelte/store"
|
|
4
4
|
import { fade } from "svelte/transition"
|
|
5
5
|
import { clickOutside, ProgressCircle } from "@budibase/bbui"
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import { createAPIClient } from "../../../api"
|
|
8
8
|
import { attachStores } from "../stores"
|
|
9
9
|
import BulkDeleteHandler from "../controls/BulkDeleteHandler.svelte"
|
|
10
|
-
import BetaButton from "../controls/BetaButton.svelte"
|
|
11
10
|
import GridBody from "./GridBody.svelte"
|
|
12
11
|
import ResizeOverlay from "../overlays/ResizeOverlay.svelte"
|
|
13
12
|
import ReorderOverlay from "../overlays/ReorderOverlay.svelte"
|
|
@@ -24,6 +23,7 @@
|
|
|
24
23
|
import RowHeightButton from "../controls/RowHeightButton.svelte"
|
|
25
24
|
import ColumnWidthButton from "../controls/ColumnWidthButton.svelte"
|
|
26
25
|
import NewRow from "./NewRow.svelte"
|
|
26
|
+
import { createGridWebsocket } from "../lib/websocket"
|
|
27
27
|
import {
|
|
28
28
|
MaxCellRenderHeight,
|
|
29
29
|
MaxCellRenderWidthOverflow,
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
export let API = null
|
|
35
35
|
export let tableId = null
|
|
36
|
+
export let tableType = null
|
|
36
37
|
export let schemaOverrides = null
|
|
37
38
|
export let allowAddRows = true
|
|
38
39
|
export let allowAddColumns = true
|
|
@@ -40,6 +41,9 @@
|
|
|
40
41
|
export let allowExpandRows = true
|
|
41
42
|
export let allowEditRows = true
|
|
42
43
|
export let allowDeleteRows = true
|
|
44
|
+
export let stripeRows = false
|
|
45
|
+
export let collaboration = true
|
|
46
|
+
export let showAvatars = true
|
|
43
47
|
|
|
44
48
|
// Unique identifier for DOM nodes inside this instance
|
|
45
49
|
const rand = Math.random()
|
|
@@ -54,6 +58,7 @@
|
|
|
54
58
|
allowExpandRows,
|
|
55
59
|
allowEditRows,
|
|
56
60
|
allowDeleteRows,
|
|
61
|
+
stripeRows,
|
|
57
62
|
})
|
|
58
63
|
|
|
59
64
|
// Build up context
|
|
@@ -62,6 +67,7 @@
|
|
|
62
67
|
rand,
|
|
63
68
|
config,
|
|
64
69
|
tableId: tableIdStore,
|
|
70
|
+
tableType,
|
|
65
71
|
schemaOverrides: schemaOverridesStore,
|
|
66
72
|
}
|
|
67
73
|
context = { ...context, ...createEventManagers() }
|
|
@@ -88,6 +94,7 @@
|
|
|
88
94
|
allowExpandRows,
|
|
89
95
|
allowEditRows,
|
|
90
96
|
allowDeleteRows,
|
|
97
|
+
stripeRows,
|
|
91
98
|
})
|
|
92
99
|
|
|
93
100
|
// Set context for children to consume
|
|
@@ -97,7 +104,11 @@
|
|
|
97
104
|
export const getContext = () => context
|
|
98
105
|
|
|
99
106
|
// Initialise websocket for multi-user
|
|
100
|
-
|
|
107
|
+
onMount(() => {
|
|
108
|
+
if (collaboration) {
|
|
109
|
+
return createGridWebsocket(context)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
101
112
|
</script>
|
|
102
113
|
|
|
103
114
|
<div
|
|
@@ -105,6 +116,7 @@
|
|
|
105
116
|
id="grid-{rand}"
|
|
106
117
|
class:is-resizing={$isResizing}
|
|
107
118
|
class:is-reordering={$isReordering}
|
|
119
|
+
class:stripe={$config.stripeRows}
|
|
108
120
|
style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-height:{MaxCellRenderHeight}px; --max-cell-render-width-overflow:{MaxCellRenderWidthOverflow}px; --content-lines:{$contentLines};"
|
|
109
121
|
>
|
|
110
122
|
<div class="controls">
|
|
@@ -118,7 +130,9 @@
|
|
|
118
130
|
<RowHeightButton />
|
|
119
131
|
</div>
|
|
120
132
|
<div class="controls-right">
|
|
121
|
-
|
|
133
|
+
{#if showAvatars}
|
|
134
|
+
<UserAvatars />
|
|
135
|
+
{/if}
|
|
122
136
|
</div>
|
|
123
137
|
</div>
|
|
124
138
|
{#if $loaded}
|
|
@@ -129,7 +143,6 @@
|
|
|
129
143
|
<HeaderRow />
|
|
130
144
|
<GridBody />
|
|
131
145
|
</div>
|
|
132
|
-
<BetaButton />
|
|
133
146
|
{#if allowAddRows}
|
|
134
147
|
<NewRow />
|
|
135
148
|
{/if}
|
|
@@ -167,6 +180,7 @@
|
|
|
167
180
|
/* Variables */
|
|
168
181
|
--cell-background: var(--spectrum-global-color-gray-50);
|
|
169
182
|
--cell-background-hover: var(--spectrum-global-color-gray-100);
|
|
183
|
+
--cell-background-alt: var(--cell-background);
|
|
170
184
|
--cell-padding: 8px;
|
|
171
185
|
--cell-spacing: 4px;
|
|
172
186
|
--cell-border: 1px solid var(--spectrum-global-color-gray-200);
|
|
@@ -183,6 +197,9 @@
|
|
|
183
197
|
.grid.is-reordering :global(*) {
|
|
184
198
|
cursor: grabbing !important;
|
|
185
199
|
}
|
|
200
|
+
.grid.stripe {
|
|
201
|
+
--cell-background-alt: var(--spectrum-global-color-gray-75);
|
|
202
|
+
}
|
|
186
203
|
|
|
187
204
|
.grid-data-outer,
|
|
188
205
|
.grid-data-inner {
|
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
<div bind:this={body} class="grid-body">
|
|
37
37
|
<GridScrollWrapper scrollHorizontally scrollVertically wheelInteractive>
|
|
38
38
|
{#each $renderedRows as row, idx}
|
|
39
|
-
<GridRow
|
|
39
|
+
<GridRow
|
|
40
|
+
{row}
|
|
41
|
+
top={idx === 0}
|
|
42
|
+
invertY={idx >= $rowVerticalInversionIndex}
|
|
43
|
+
/>
|
|
40
44
|
{/each}
|
|
41
45
|
{#if $config.allowAddRows && $renderedColumns.length}
|
|
42
46
|
<div
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import DataCell from "../cells/DataCell.svelte"
|
|
4
4
|
|
|
5
5
|
export let row
|
|
6
|
-
export let
|
|
6
|
+
export let top = false
|
|
7
7
|
export let invertY = false
|
|
8
8
|
|
|
9
9
|
const {
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
invertX={columnIdx >= $columnHorizontalInversionIndex}
|
|
42
42
|
highlighted={rowHovered || rowFocused || reorderSource === column.name}
|
|
43
43
|
selected={rowSelected}
|
|
44
|
-
rowIdx={
|
|
44
|
+
rowIdx={row.__idx}
|
|
45
|
+
topRow={top}
|
|
45
46
|
focused={$focusedCellId === cellId}
|
|
46
47
|
selectedUser={$selectedCellMap[cellId]}
|
|
47
48
|
width={column.width}
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
padding: 2px 6px;
|
|
39
39
|
font-size: 12px;
|
|
40
40
|
font-weight: 600;
|
|
41
|
-
background-color: var(--spectrum-global-color-gray-
|
|
41
|
+
background-color: var(--spectrum-global-color-gray-300);
|
|
42
42
|
color: var(--spectrum-global-color-gray-700);
|
|
43
43
|
border-radius: 4px;
|
|
44
44
|
text-align: center;
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
focused={$focusedCellId === cellId}
|
|
168
168
|
width={$stickyColumn.width}
|
|
169
169
|
{updateValue}
|
|
170
|
-
|
|
170
|
+
topRow={offset === 0}
|
|
171
171
|
{invertY}
|
|
172
172
|
>
|
|
173
173
|
{#if $stickyColumn?.schema?.autocolumn}
|
|
@@ -193,7 +193,7 @@
|
|
|
193
193
|
row={newRow}
|
|
194
194
|
focused={$focusedCellId === cellId}
|
|
195
195
|
width={column.width}
|
|
196
|
-
|
|
196
|
+
topRow={offset === 0}
|
|
197
197
|
invertX={columnIdx >= $columnHorizontalInversionIndex}
|
|
198
198
|
{invertY}
|
|
199
199
|
>
|
|
@@ -219,7 +219,7 @@
|
|
|
219
219
|
<Button size="M" secondary newStyles on:click={clear}>
|
|
220
220
|
<div class="button-with-keys">
|
|
221
221
|
Cancel
|
|
222
|
-
<KeyboardShortcut
|
|
222
|
+
<KeyboardShortcut keybind="Esc" />
|
|
223
223
|
</div>
|
|
224
224
|
</Button>
|
|
225
225
|
</div>
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { getContext } from "svelte"
|
|
3
|
-
import
|
|
3
|
+
import UserAvatar from "../../UserAvatar.svelte"
|
|
4
4
|
|
|
5
5
|
const { users } = getContext("grid")
|
|
6
|
+
|
|
7
|
+
$: uniqueUsers = unique($users)
|
|
8
|
+
|
|
9
|
+
const unique = users => {
|
|
10
|
+
let uniqueUsers = {}
|
|
11
|
+
users?.forEach(user => {
|
|
12
|
+
uniqueUsers[user.email] = user
|
|
13
|
+
})
|
|
14
|
+
return Object.values(uniqueUsers)
|
|
15
|
+
}
|
|
6
16
|
</script>
|
|
7
17
|
|
|
8
18
|
<div class="users">
|
|
9
|
-
{#each
|
|
10
|
-
<
|
|
19
|
+
{#each uniqueUsers as user}
|
|
20
|
+
<UserAvatar {user} />
|
|
11
21
|
{/each}
|
|
12
22
|
</div>
|
|
13
23
|
|
|
@@ -15,6 +25,6 @@
|
|
|
15
25
|
.users {
|
|
16
26
|
display: flex;
|
|
17
27
|
flex-direction: row;
|
|
18
|
-
gap:
|
|
28
|
+
gap: 4px;
|
|
19
29
|
}
|
|
20
30
|
</style>
|
|
@@ -1,54 +1,58 @@
|
|
|
1
1
|
import { get } from "svelte/store"
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const tls = location.protocol === "https:"
|
|
9
|
-
const proto = tls ? "wss:" : "ws:"
|
|
10
|
-
const host = location.hostname
|
|
11
|
-
const port = location.port || (tls ? 443 : 80)
|
|
12
|
-
const socket = io(`${proto}//${host}:${port}`, {
|
|
13
|
-
path: "/socket/grid",
|
|
14
|
-
// Cap reconnection attempts to 3 (total of 15 seconds before giving up)
|
|
15
|
-
reconnectionAttempts: 3,
|
|
16
|
-
// Delay reconnection attempt by 5 seconds
|
|
17
|
-
reconnectionDelay: 5000,
|
|
18
|
-
reconnectionDelayMax: 5000,
|
|
19
|
-
// Timeout after 4 seconds so we never stack requests
|
|
20
|
-
timeout: 4000,
|
|
21
|
-
})
|
|
2
|
+
import { createWebsocket } from "../../../utils"
|
|
3
|
+
import { SocketEvent, GridSocketEvent } from "@budibase/shared-core"
|
|
4
|
+
|
|
5
|
+
export const createGridWebsocket = context => {
|
|
6
|
+
const { rows, tableId, users, focusedCellId, table } = context
|
|
7
|
+
const socket = createWebsocket("/socket/grid")
|
|
22
8
|
|
|
23
9
|
const connectToTable = tableId => {
|
|
24
10
|
if (!socket.connected) {
|
|
25
11
|
return
|
|
26
12
|
}
|
|
27
13
|
// Identify which table we are editing
|
|
28
|
-
socket.emit(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
14
|
+
socket.emit(
|
|
15
|
+
GridSocketEvent.SelectTable,
|
|
16
|
+
{ tableId },
|
|
17
|
+
({ users: gridUsers }) => {
|
|
18
|
+
users.set(gridUsers)
|
|
19
|
+
}
|
|
20
|
+
)
|
|
33
21
|
}
|
|
34
22
|
|
|
35
|
-
//
|
|
23
|
+
// Built-in events
|
|
36
24
|
socket.on("connect", () => {
|
|
37
25
|
connectToTable(get(tableId))
|
|
38
26
|
})
|
|
39
|
-
socket.on("
|
|
40
|
-
|
|
41
|
-
rows.actions.refreshRow(data.id)
|
|
42
|
-
}
|
|
27
|
+
socket.on("connect_error", err => {
|
|
28
|
+
console.log("Failed to connect to grid websocket:", err.message)
|
|
43
29
|
})
|
|
44
|
-
|
|
30
|
+
|
|
31
|
+
// User events
|
|
32
|
+
socket.onOther(SocketEvent.UserUpdate, ({ user }) => {
|
|
45
33
|
users.actions.updateUser(user)
|
|
46
34
|
})
|
|
47
|
-
socket.
|
|
48
|
-
users.actions.removeUser(
|
|
35
|
+
socket.onOther(SocketEvent.UserDisconnect, ({ sessionId }) => {
|
|
36
|
+
users.actions.removeUser(sessionId)
|
|
49
37
|
})
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
|
|
39
|
+
// Row events
|
|
40
|
+
socket.onOther(GridSocketEvent.RowChange, async ({ id, row }) => {
|
|
41
|
+
if (id) {
|
|
42
|
+
rows.actions.replaceRow(id, row)
|
|
43
|
+
} else if (row.id) {
|
|
44
|
+
// Handle users table edge cased
|
|
45
|
+
await rows.actions.refreshRow(row.id)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Table events
|
|
50
|
+
socket.onOther(GridSocketEvent.TableChange, ({ table: newTable }) => {
|
|
51
|
+
// Only update table if one exists. If the table was deleted then we don't
|
|
52
|
+
// want to know - let the builder navigate away
|
|
53
|
+
if (newTable) {
|
|
54
|
+
table.set(newTable)
|
|
55
|
+
}
|
|
52
56
|
})
|
|
53
57
|
|
|
54
58
|
// Change websocket connection when table changes
|
|
@@ -56,7 +60,7 @@ export const createWebsocket = context => {
|
|
|
56
60
|
|
|
57
61
|
// Notify selected cell changes
|
|
58
62
|
focusedCellId.subscribe($focusedCellId => {
|
|
59
|
-
socket.emit(
|
|
63
|
+
socket.emit(GridSocketEvent.SelectCell, { cellId: $focusedCellId })
|
|
60
64
|
})
|
|
61
65
|
|
|
62
66
|
return () => socket?.disconnect()
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
dispatch,
|
|
15
15
|
selectedRows,
|
|
16
16
|
config,
|
|
17
|
+
menu,
|
|
17
18
|
} = getContext("grid")
|
|
18
19
|
|
|
19
20
|
const ignoredOriginSelectors = [
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
} else {
|
|
62
63
|
$focusedCellId = null
|
|
63
64
|
}
|
|
65
|
+
menu.actions.close()
|
|
64
66
|
return
|
|
65
67
|
} else if (e.key === "Tab") {
|
|
66
68
|
e.preventDefault()
|
|
@@ -224,10 +226,7 @@
|
|
|
224
226
|
if (!id || id === NewRowID) {
|
|
225
227
|
return
|
|
226
228
|
}
|
|
227
|
-
selectedRows.
|
|
228
|
-
state[id] = !state[id]
|
|
229
|
-
return state
|
|
230
|
-
})
|
|
229
|
+
selectedRows.actions.toggleRow(id)
|
|
231
230
|
}
|
|
232
231
|
|
|
233
232
|
onMount(() => {
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
clickOutside,
|
|
4
|
+
Menu,
|
|
5
|
+
MenuItem,
|
|
6
|
+
Helpers,
|
|
7
|
+
notifications,
|
|
8
|
+
} from "@budibase/bbui"
|
|
3
9
|
import { getContext } from "svelte"
|
|
10
|
+
import { NewRowID } from "../lib/constants"
|
|
4
11
|
|
|
5
12
|
const {
|
|
6
13
|
focusedRow,
|
|
@@ -14,9 +21,11 @@
|
|
|
14
21
|
clipboard,
|
|
15
22
|
dispatch,
|
|
16
23
|
focusedCellAPI,
|
|
24
|
+
focusedRowId,
|
|
17
25
|
} = getContext("grid")
|
|
18
26
|
|
|
19
27
|
$: style = makeStyle($menu)
|
|
28
|
+
$: isNewRow = $focusedRowId === NewRowID
|
|
20
29
|
|
|
21
30
|
const makeStyle = menu => {
|
|
22
31
|
return `left:${menu.left}px; top:${menu.top}px;`
|
|
@@ -36,6 +45,11 @@
|
|
|
36
45
|
$focusedCellId = `${newRow._id}-${column}`
|
|
37
46
|
}
|
|
38
47
|
}
|
|
48
|
+
|
|
49
|
+
const copyToClipboard = async value => {
|
|
50
|
+
await Helpers.copyToClipboard(value)
|
|
51
|
+
notifications.success("Copied to clipboard")
|
|
52
|
+
}
|
|
39
53
|
</script>
|
|
40
54
|
|
|
41
55
|
{#if $menu.visible}
|
|
@@ -58,22 +72,38 @@
|
|
|
58
72
|
</MenuItem>
|
|
59
73
|
<MenuItem
|
|
60
74
|
icon="Maximize"
|
|
61
|
-
disabled={!$config.allowEditRows}
|
|
75
|
+
disabled={isNewRow || !$config.allowEditRows}
|
|
62
76
|
on:click={() => dispatch("edit-row", $focusedRow)}
|
|
63
77
|
on:click={menu.actions.close}
|
|
64
78
|
>
|
|
65
79
|
Edit row in modal
|
|
66
80
|
</MenuItem>
|
|
81
|
+
<MenuItem
|
|
82
|
+
icon="Copy"
|
|
83
|
+
disabled={isNewRow || !$focusedRow?._id}
|
|
84
|
+
on:click={() => copyToClipboard($focusedRow?._id)}
|
|
85
|
+
on:click={menu.actions.close}
|
|
86
|
+
>
|
|
87
|
+
Copy row _id
|
|
88
|
+
</MenuItem>
|
|
89
|
+
<MenuItem
|
|
90
|
+
icon="Copy"
|
|
91
|
+
disabled={isNewRow || !$focusedRow?._rev}
|
|
92
|
+
on:click={() => copyToClipboard($focusedRow?._rev)}
|
|
93
|
+
on:click={menu.actions.close}
|
|
94
|
+
>
|
|
95
|
+
Copy row _rev
|
|
96
|
+
</MenuItem>
|
|
67
97
|
<MenuItem
|
|
68
98
|
icon="Duplicate"
|
|
69
|
-
disabled={!$config.allowAddRows}
|
|
99
|
+
disabled={isNewRow || !$config.allowAddRows}
|
|
70
100
|
on:click={duplicate}
|
|
71
101
|
>
|
|
72
102
|
Duplicate row
|
|
73
103
|
</MenuItem>
|
|
74
104
|
<MenuItem
|
|
75
105
|
icon="Delete"
|
|
76
|
-
disabled={!$config.allowDeleteRows}
|
|
106
|
+
disabled={isNewRow || !$config.allowDeleteRows}
|
|
77
107
|
on:click={deleteRow}
|
|
78
108
|
>
|
|
79
109
|
Delete row
|
|
@@ -90,8 +90,8 @@ export const deriveStores = context => {
|
|
|
90
90
|
// Update local state
|
|
91
91
|
table.set(newTable)
|
|
92
92
|
|
|
93
|
-
// Broadcast
|
|
94
|
-
//
|
|
93
|
+
// Broadcast change to external state can be updated, as this change
|
|
94
|
+
// will not be received by the builder websocket because we caused it ourselves
|
|
95
95
|
dispatch("updatetable", newTable)
|
|
96
96
|
|
|
97
97
|
// Update server
|
|
@@ -4,9 +4,10 @@ const reorderInitialState = {
|
|
|
4
4
|
sourceColumn: null,
|
|
5
5
|
targetColumn: null,
|
|
6
6
|
breakpoints: [],
|
|
7
|
-
initialMouseX: null,
|
|
8
|
-
scrollLeft: 0,
|
|
9
7
|
gridLeft: 0,
|
|
8
|
+
width: 0,
|
|
9
|
+
latestX: 0,
|
|
10
|
+
increment: 0,
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export const createStores = () => {
|
|
@@ -23,14 +24,24 @@ export const createStores = () => {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export const deriveStores = context => {
|
|
26
|
-
const {
|
|
27
|
-
|
|
27
|
+
const {
|
|
28
|
+
reorder,
|
|
29
|
+
columns,
|
|
30
|
+
visibleColumns,
|
|
31
|
+
scroll,
|
|
32
|
+
bounds,
|
|
33
|
+
stickyColumn,
|
|
34
|
+
ui,
|
|
35
|
+
maxScrollLeft,
|
|
36
|
+
} = context
|
|
37
|
+
|
|
38
|
+
let autoScrollInterval
|
|
39
|
+
let isAutoScrolling
|
|
28
40
|
|
|
29
41
|
// Callback when dragging on a colum header and starting reordering
|
|
30
42
|
const startReordering = (column, e) => {
|
|
31
43
|
const $visibleColumns = get(visibleColumns)
|
|
32
44
|
const $bounds = get(bounds)
|
|
33
|
-
const $scroll = get(scroll)
|
|
34
45
|
const $stickyColumn = get(stickyColumn)
|
|
35
46
|
ui.actions.blur()
|
|
36
47
|
|
|
@@ -51,9 +62,8 @@ export const deriveStores = context => {
|
|
|
51
62
|
sourceColumn: column,
|
|
52
63
|
targetColumn: null,
|
|
53
64
|
breakpoints,
|
|
54
|
-
initialMouseX: e.clientX,
|
|
55
|
-
scrollLeft: $scroll.left,
|
|
56
65
|
gridLeft: $bounds.left,
|
|
66
|
+
width: $bounds.width,
|
|
57
67
|
})
|
|
58
68
|
|
|
59
69
|
// Add listeners to handle mouse movement
|
|
@@ -66,12 +76,44 @@ export const deriveStores = context => {
|
|
|
66
76
|
|
|
67
77
|
// Callback when moving the mouse when reordering columns
|
|
68
78
|
const onReorderMouseMove = e => {
|
|
79
|
+
// Immediately handle the current position
|
|
80
|
+
const x = e.clientX
|
|
81
|
+
reorder.update(state => ({
|
|
82
|
+
...state,
|
|
83
|
+
latestX: x,
|
|
84
|
+
}))
|
|
85
|
+
considerReorderPosition()
|
|
86
|
+
|
|
87
|
+
// Check if we need to start auto-scrolling
|
|
88
|
+
const $reorder = get(reorder)
|
|
89
|
+
const proximityCutoff = 140
|
|
90
|
+
const speedFactor = 8
|
|
91
|
+
const rightProximity = Math.max(0, $reorder.gridLeft + $reorder.width - x)
|
|
92
|
+
const leftProximity = Math.max(0, x - $reorder.gridLeft)
|
|
93
|
+
if (rightProximity < proximityCutoff) {
|
|
94
|
+
const weight = proximityCutoff - rightProximity
|
|
95
|
+
const increment = (weight / proximityCutoff) * speedFactor
|
|
96
|
+
reorder.update(state => ({ ...state, increment }))
|
|
97
|
+
startAutoScroll()
|
|
98
|
+
} else if (leftProximity < proximityCutoff) {
|
|
99
|
+
const weight = -1 * (proximityCutoff - leftProximity)
|
|
100
|
+
const increment = (weight / proximityCutoff) * speedFactor
|
|
101
|
+
reorder.update(state => ({ ...state, increment }))
|
|
102
|
+
startAutoScroll()
|
|
103
|
+
} else {
|
|
104
|
+
stopAutoScroll()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Actual logic to consider the current position and determine the new order
|
|
109
|
+
const considerReorderPosition = () => {
|
|
69
110
|
const $reorder = get(reorder)
|
|
111
|
+
const $scroll = get(scroll)
|
|
70
112
|
|
|
71
113
|
// Compute the closest breakpoint to the current position
|
|
72
114
|
let targetColumn
|
|
73
115
|
let minDistance = Number.MAX_SAFE_INTEGER
|
|
74
|
-
const mouseX =
|
|
116
|
+
const mouseX = $reorder.latestX - $reorder.gridLeft + $scroll.left
|
|
75
117
|
$reorder.breakpoints.forEach(point => {
|
|
76
118
|
const distance = Math.abs(point.x - mouseX)
|
|
77
119
|
if (distance < minDistance) {
|
|
@@ -79,7 +121,6 @@ export const deriveStores = context => {
|
|
|
79
121
|
targetColumn = point.column
|
|
80
122
|
}
|
|
81
123
|
})
|
|
82
|
-
|
|
83
124
|
if (targetColumn !== $reorder.targetColumn) {
|
|
84
125
|
reorder.update(state => ({
|
|
85
126
|
...state,
|
|
@@ -88,8 +129,35 @@ export const deriveStores = context => {
|
|
|
88
129
|
}
|
|
89
130
|
}
|
|
90
131
|
|
|
132
|
+
// Commences auto-scrolling in a certain direction, triggered when the mouse
|
|
133
|
+
// approaches the edges of the grid
|
|
134
|
+
const startAutoScroll = () => {
|
|
135
|
+
if (isAutoScrolling) {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
isAutoScrolling = true
|
|
139
|
+
autoScrollInterval = setInterval(() => {
|
|
140
|
+
const $maxLeft = get(maxScrollLeft)
|
|
141
|
+
const { increment } = get(reorder)
|
|
142
|
+
scroll.update(state => ({
|
|
143
|
+
...state,
|
|
144
|
+
left: Math.max(0, Math.min($maxLeft, state.left + increment)),
|
|
145
|
+
}))
|
|
146
|
+
considerReorderPosition()
|
|
147
|
+
}, 10)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Stops auto scrolling
|
|
151
|
+
const stopAutoScroll = () => {
|
|
152
|
+
isAutoScrolling = false
|
|
153
|
+
clearInterval(autoScrollInterval)
|
|
154
|
+
}
|
|
155
|
+
|
|
91
156
|
// Callback when stopping reordering columns
|
|
92
157
|
const stopReordering = async () => {
|
|
158
|
+
// Ensure auto-scrolling is stopped
|
|
159
|
+
stopAutoScroll()
|
|
160
|
+
|
|
93
161
|
// Swap position of columns
|
|
94
162
|
let { sourceColumn, targetColumn } = get(reorder)
|
|
95
163
|
moveColumn(sourceColumn, targetColumn)
|