@budibase/frontend-core 2.9.38 → 2.9.39-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.
Files changed (45) hide show
  1. package/package.json +4 -4
  2. package/src/api/groups.js +22 -0
  3. package/src/api/index.js +2 -0
  4. package/src/api/permissions.js +23 -0
  5. package/src/api/rows.js +22 -3
  6. package/src/api/user.js +27 -4
  7. package/src/api/viewsV2.js +72 -0
  8. package/src/components/grid/cells/DataCell.svelte +8 -3
  9. package/src/components/grid/cells/GutterCell.svelte +4 -4
  10. package/src/components/grid/cells/HeaderCell.svelte +3 -3
  11. package/src/components/grid/controls/HideColumnsButton.svelte +10 -7
  12. package/src/components/grid/controls/SizeButton.svelte +10 -4
  13. package/src/components/grid/controls/SortButton.svelte +12 -34
  14. package/src/components/grid/layout/Grid.svelte +22 -17
  15. package/src/components/grid/layout/GridBody.svelte +2 -2
  16. package/src/components/grid/layout/HeaderRow.svelte +3 -4
  17. package/src/components/grid/layout/NewColumnButton.svelte +3 -2
  18. package/src/components/grid/layout/NewRow.svelte +6 -6
  19. package/src/components/grid/layout/StickyColumn.svelte +2 -2
  20. package/src/components/grid/lib/websocket.js +18 -12
  21. package/src/components/grid/overlays/KeyboardManager.svelte +12 -11
  22. package/src/components/grid/overlays/MenuOverlay.svelte +3 -6
  23. package/src/components/grid/stores/clipboard.js +1 -1
  24. package/src/components/grid/stores/columns.js +39 -99
  25. package/src/components/grid/stores/config.js +33 -6
  26. package/src/components/grid/stores/datasource.js +131 -0
  27. package/src/components/grid/stores/filter.js +2 -2
  28. package/src/components/grid/stores/index.js +14 -3
  29. package/src/components/grid/stores/menu.js +1 -1
  30. package/src/components/grid/stores/pagination.js +3 -4
  31. package/src/components/grid/stores/reorder.js +1 -1
  32. package/src/components/grid/stores/resize.js +1 -1
  33. package/src/components/grid/stores/rows.js +61 -101
  34. package/src/components/grid/stores/sort.js +29 -6
  35. package/src/components/grid/stores/table.js +129 -0
  36. package/src/components/grid/stores/ui.js +24 -36
  37. package/src/components/grid/stores/users.js +8 -1
  38. package/src/components/grid/stores/viewV2.js +212 -0
  39. package/src/components/grid/stores/viewport.js +4 -4
  40. package/src/constants.js +21 -13
  41. package/src/fetch/DataFetch.js +35 -24
  42. package/src/fetch/QueryFetch.js +4 -0
  43. package/src/fetch/ViewV2Fetch.js +65 -0
  44. package/src/fetch/fetchData.js +2 -0
  45. package/src/utils/roles.js +5 -3
@@ -15,16 +15,20 @@ import * as Config from "./config"
15
15
  import * as Sort from "./sort"
16
16
  import * as Filter from "./filter"
17
17
  import * as Notifications from "./notifications"
18
+ import * as Table from "./table"
19
+ import * as ViewV2 from "./viewV2"
20
+ import * as Datasource from "./datasource"
18
21
 
19
22
  const DependencyOrderedStores = [
20
- Config,
21
- Notifications,
22
23
  Sort,
23
24
  Filter,
24
25
  Bounds,
25
26
  Scroll,
26
- Rows,
27
+ Table,
28
+ ViewV2,
29
+ Datasource,
27
30
  Columns,
31
+ Rows,
28
32
  UI,
29
33
  Validation,
30
34
  Resize,
@@ -34,6 +38,8 @@ const DependencyOrderedStores = [
34
38
  Menu,
35
39
  Pagination,
36
40
  Clipboard,
41
+ Config,
42
+ Notifications,
37
43
  ]
38
44
 
39
45
  export const attachStores = context => {
@@ -47,6 +53,11 @@ export const attachStores = context => {
47
53
  context = { ...context, ...store.deriveStores?.(context) }
48
54
  }
49
55
 
56
+ // Action creation
57
+ for (let store of DependencyOrderedStores) {
58
+ context = { ...context, ...store.createActions?.(context) }
59
+ }
60
+
50
61
  // Initialise any store logic
51
62
  for (let store of DependencyOrderedStores) {
52
63
  store.initialise?.(context)
@@ -12,7 +12,7 @@ export const createStores = () => {
12
12
  }
13
13
  }
14
14
 
15
- export const deriveStores = context => {
15
+ export const createActions = context => {
16
16
  const { menu, focusedCellId, rand } = context
17
17
 
18
18
  const open = (cellId, e) => {
@@ -1,4 +1,4 @@
1
- import { derived } from "svelte/store"
1
+ import { derived, get } from "svelte/store"
2
2
 
3
3
  export const initialise = context => {
4
4
  const { scrolledRowCount, rows, visualRowCapacity } = context
@@ -11,13 +11,12 @@ export const initialise = context => {
11
11
  [scrolledRowCount, rowCount, visualRowCapacity],
12
12
  ([$scrolledRowCount, $rowCount, $visualRowCapacity]) => {
13
13
  return Math.max(0, $rowCount - $scrolledRowCount - $visualRowCapacity)
14
- },
15
- 100
14
+ }
16
15
  )
17
16
 
18
17
  // Fetch next page when fewer than 25 remaining rows to scroll
19
18
  remainingRows.subscribe(remaining => {
20
- if (remaining < 25) {
19
+ if (remaining < 25 && get(rowCount)) {
21
20
  rows.actions.loadNextPage()
22
21
  }
23
22
  })
@@ -23,7 +23,7 @@ export const createStores = () => {
23
23
  }
24
24
  }
25
25
 
26
- export const deriveStores = context => {
26
+ export const createActions = context => {
27
27
  const {
28
28
  reorder,
29
29
  columns,
@@ -19,7 +19,7 @@ export const createStores = () => {
19
19
  }
20
20
  }
21
21
 
22
- export const deriveStores = context => {
22
+ export const createActions = context => {
23
23
  const { resize, columns, stickyColumn, ui } = context
24
24
 
25
25
  // Starts resizing a certain column
@@ -3,30 +3,24 @@ import { fetchData } from "../../../fetch/fetchData"
3
3
  import { NewRowID, RowPageSize } from "../lib/constants"
4
4
  import { tick } from "svelte"
5
5
 
6
- const SuppressErrors = true
7
-
8
6
  export const createStores = () => {
9
7
  const rows = writable([])
10
- const table = writable(null)
11
8
  const loading = writable(false)
12
9
  const loaded = writable(false)
13
10
  const rowChangeCache = writable({})
14
11
  const inProgressChanges = writable({})
15
12
  const hasNextPage = writable(false)
16
13
  const error = writable(null)
14
+ const fetch = writable(null)
17
15
 
18
16
  // Generate a lookup map to quick find a row by ID
19
- const rowLookupMap = derived(
20
- rows,
21
- $rows => {
22
- let map = {}
23
- for (let i = 0; i < $rows.length; i++) {
24
- map[$rows[i]._id] = i
25
- }
26
- return map
27
- },
28
- {}
29
- )
17
+ const rowLookupMap = derived(rows, $rows => {
18
+ let map = {}
19
+ for (let i = 0; i < $rows.length; i++) {
20
+ map[$rows[i]._id] = i
21
+ }
22
+ return map
23
+ })
30
24
 
31
25
  // Mark loaded as true if we've ever stopped loading
32
26
  let hasStartedLoading = false
@@ -38,10 +32,25 @@ export const createStores = () => {
38
32
  }
39
33
  })
40
34
 
35
+ // Enrich rows with an index property and any pending changes
36
+ const enrichedRows = derived(
37
+ [rows, rowChangeCache],
38
+ ([$rows, $rowChangeCache]) => {
39
+ return $rows.map((row, idx) => ({
40
+ ...row,
41
+ ...$rowChangeCache[row._id],
42
+ __idx: idx,
43
+ }))
44
+ }
45
+ )
46
+
41
47
  return {
42
- rows,
48
+ rows: {
49
+ ...rows,
50
+ subscribe: enrichedRows.subscribe,
51
+ },
52
+ fetch,
43
53
  rowLookupMap,
44
- table,
45
54
  loaded,
46
55
  loading,
47
56
  rowChangeCache,
@@ -51,15 +60,15 @@ export const createStores = () => {
51
60
  }
52
61
  }
53
62
 
54
- export const deriveStores = context => {
63
+ export const createActions = context => {
55
64
  const {
56
65
  rows,
57
66
  rowLookupMap,
58
- table,
67
+ definition,
59
68
  filter,
60
69
  loading,
61
70
  sort,
62
- tableId,
71
+ datasource,
63
72
  API,
64
73
  scroll,
65
74
  validation,
@@ -71,37 +80,29 @@ export const deriveStores = context => {
71
80
  hasNextPage,
72
81
  error,
73
82
  notifications,
83
+ fetch,
74
84
  } = context
75
85
  const instanceLoaded = writable(false)
76
- const fetch = writable(null)
77
86
 
78
87
  // Local cache of row IDs to speed up checking if a row exists
79
88
  let rowCacheMap = {}
80
89
 
81
- // Enrich rows with an index property and any pending changes
82
- const enrichedRows = derived(
83
- [rows, rowChangeCache],
84
- ([$rows, $rowChangeCache]) => {
85
- return $rows.map((row, idx) => ({
86
- ...row,
87
- ...$rowChangeCache[row._id],
88
- __idx: idx,
89
- }))
90
- },
91
- []
92
- )
93
-
94
- // Reset everything when table ID changes
90
+ // Reset everything when datasource changes
95
91
  let unsubscribe = null
96
92
  let lastResetKey = null
97
- tableId.subscribe(async $tableId => {
93
+ datasource.subscribe(async $datasource => {
98
94
  // Unsub from previous fetch if one exists
99
95
  unsubscribe?.()
100
96
  fetch.set(null)
101
97
  instanceLoaded.set(false)
102
98
  loading.set(true)
103
99
 
104
- // Tick to allow other reactive logic to update stores when table ID changes
100
+ // Abandon if we don't have a valid datasource
101
+ if (!datasource.actions.isDatasourceValid($datasource)) {
102
+ return
103
+ }
104
+
105
+ // Tick to allow other reactive logic to update stores when datasource changes
105
106
  // before proceeding. This allows us to wipe filters etc if needed.
106
107
  await tick()
107
108
  const $filter = get(filter)
@@ -110,10 +111,7 @@ export const deriveStores = context => {
110
111
  // Create new fetch model
111
112
  const newFetch = fetchData({
112
113
  API,
113
- datasource: {
114
- type: "table",
115
- tableId: $tableId,
116
- },
114
+ datasource: $datasource,
117
115
  options: {
118
116
  filter: $filter,
119
117
  sortColumn: $sort.column,
@@ -142,7 +140,7 @@ export const deriveStores = context => {
142
140
  const previousResetKey = lastResetKey
143
141
  lastResetKey = $fetch.resetKey
144
142
 
145
- // If resetting rows due to a table change, wipe data and wait for
143
+ // If resetting rows due to a datasource change, wipe data and wait for
146
144
  // derived stores to compute. This prevents stale data being passed
147
145
  // to cells when we save the new schema.
148
146
  if (!$instanceLoaded && previousResetKey) {
@@ -152,16 +150,12 @@ export const deriveStores = context => {
152
150
 
153
151
  // Reset state properties when dataset changes
154
152
  if (!$instanceLoaded || resetRows) {
155
- table.set($fetch.definition)
156
- sort.set({
157
- column: $fetch.sortColumn,
158
- order: $fetch.sortOrder,
159
- })
153
+ definition.set($fetch.definition)
160
154
  }
161
155
 
162
156
  // Reset scroll state when data changes
163
157
  if (!$instanceLoaded) {
164
- // Reset both top and left for a new table ID
158
+ // Reset both top and left for a new datasource ID
165
159
  instanceLoaded.set(true)
166
160
  scroll.set({ top: 0, left: 0 })
167
161
  } else if (resetRows) {
@@ -180,19 +174,6 @@ export const deriveStores = context => {
180
174
  fetch.set(newFetch)
181
175
  })
182
176
 
183
- // Update fetch when filter or sort config changes
184
- filter.subscribe($filter => {
185
- get(fetch)?.update({
186
- filter: $filter,
187
- })
188
- })
189
- sort.subscribe($sort => {
190
- get(fetch)?.update({
191
- sortOrder: $sort.order,
192
- sortColumn: $sort.column,
193
- })
194
- })
195
-
196
177
  // Gets a row by ID
197
178
  const getRow = id => {
198
179
  const index = get(rowLookupMap)[id]
@@ -211,7 +192,7 @@ export const deriveStores = context => {
211
192
  let erroredColumns = []
212
193
  let missingColumns = []
213
194
  for (let column of keys) {
214
- if (columns.actions.hasColumn(column)) {
195
+ if (datasource.actions.canUseColumn(column)) {
215
196
  erroredColumns.push(column)
216
197
  } else {
217
198
  missingColumns.push(column)
@@ -252,11 +233,9 @@ export const deriveStores = context => {
252
233
  // Adds a new row
253
234
  const addRow = async (row, idx, bubble = false) => {
254
235
  try {
255
- // Create row
256
- const newRow = await API.saveRow(
257
- { ...row, tableId: get(tableId) },
258
- SuppressErrors
259
- )
236
+ // Create row. Spread row so we can mutate and enrich safely.
237
+ let newRow = { ...row }
238
+ newRow = await datasource.actions.addRow(newRow)
260
239
 
261
240
  // Update state
262
241
  if (idx != null) {
@@ -294,21 +273,6 @@ export const deriveStores = context => {
294
273
  }
295
274
  }
296
275
 
297
- // Fetches a row by ID using the search endpoint
298
- const fetchRow = async id => {
299
- const res = await API.searchTable({
300
- tableId: get(tableId),
301
- limit: 1,
302
- query: {
303
- equal: {
304
- _id: id,
305
- },
306
- },
307
- paginate: false,
308
- })
309
- return res?.rows?.[0]
310
- }
311
-
312
276
  // Replaces a row in state with the newly defined row, handling updates,
313
277
  // addition and deletion
314
278
  const replaceRow = (id, row) => {
@@ -337,7 +301,7 @@ export const deriveStores = context => {
337
301
 
338
302
  // Refreshes a specific row
339
303
  const refreshRow = async id => {
340
- const row = await fetchRow(id)
304
+ const row = await datasource.actions.getRow(id)
341
305
  replaceRow(id, row)
342
306
  }
343
307
 
@@ -347,7 +311,7 @@ export const deriveStores = context => {
347
311
  }
348
312
 
349
313
  // Patches a row with some changes
350
- const updateRow = async (rowId, changes) => {
314
+ const updateRow = async (rowId, changes, options = { save: true }) => {
351
315
  const $rows = get(rows)
352
316
  const $rowLookupMap = get(rowLookupMap)
353
317
  const index = $rowLookupMap[rowId]
@@ -377,16 +341,23 @@ export const deriveStores = context => {
377
341
  },
378
342
  }))
379
343
 
344
+ // Stop here if we don't want to persist the change
345
+ if (!options?.save) {
346
+ return
347
+ }
348
+
380
349
  // Save change
381
350
  try {
382
351
  inProgressChanges.update(state => ({
383
352
  ...state,
384
353
  [rowId]: true,
385
354
  }))
386
- const saved = await API.saveRow(
387
- { ...row, ...get(rowChangeCache)[rowId] },
388
- SuppressErrors
389
- )
355
+
356
+ // Update row
357
+ const saved = await datasource.actions.updateRow({
358
+ ...row,
359
+ ...get(rowChangeCache)[rowId],
360
+ })
390
361
 
391
362
  // Update state after a successful change
392
363
  if (saved?._id) {
@@ -412,8 +383,8 @@ export const deriveStores = context => {
412
383
  }
413
384
 
414
385
  // Updates a value of a row
415
- const updateValue = async (rowId, column, value) => {
416
- return await updateRow(rowId, { [column]: value })
386
+ const updateValue = async ({ rowId, column, value, save = true }) => {
387
+ return await updateRow(rowId, { [column]: value }, { save })
417
388
  }
418
389
 
419
390
  // Deletes an array of rows
@@ -426,10 +397,7 @@ export const deriveStores = context => {
426
397
  rowsToDelete.forEach(row => {
427
398
  delete row.__idx
428
399
  })
429
- await API.deleteRows({
430
- tableId: get(tableId),
431
- rows: rowsToDelete,
432
- })
400
+ await datasource.actions.deleteRows(rowsToDelete)
433
401
 
434
402
  // Update state
435
403
  handleRemoveRows(rowsToDelete)
@@ -473,12 +441,6 @@ export const deriveStores = context => {
473
441
  get(fetch)?.nextPage()
474
442
  }
475
443
 
476
- // Refreshes the schema of the data fetch subscription
477
- const refreshTableDefinition = async () => {
478
- const definition = await API.fetchTableDefinition(get(tableId))
479
- table.set(definition)
480
- }
481
-
482
444
  // Checks if we have a row with a certain ID
483
445
  const hasRow = id => {
484
446
  if (id === NewRowID) {
@@ -498,7 +460,6 @@ export const deriveStores = context => {
498
460
  })
499
461
 
500
462
  return {
501
- enrichedRows,
502
463
  rows: {
503
464
  ...rows,
504
465
  actions: {
@@ -513,7 +474,6 @@ export const deriveStores = context => {
513
474
  refreshRow,
514
475
  replaceRow,
515
476
  refreshData,
516
- refreshTableDefinition,
517
477
  },
518
478
  },
519
479
  }
@@ -1,12 +1,14 @@
1
- import { writable } from "svelte/store"
1
+ import { derived, get } from "svelte/store"
2
+ import { memo } from "../../../utils"
2
3
 
3
4
  export const createStores = context => {
4
5
  const { props } = context
6
+ const $props = get(props)
5
7
 
6
8
  // Initialise to default props
7
- const sort = writable({
8
- column: props.initialSortColumn,
9
- order: props.initialSortOrder || "ascending",
9
+ const sort = memo({
10
+ column: $props.initialSortColumn,
11
+ order: $props.initialSortOrder || "ascending",
10
12
  })
11
13
 
12
14
  return {
@@ -15,13 +17,34 @@ export const createStores = context => {
15
17
  }
16
18
 
17
19
  export const initialise = context => {
18
- const { sort, initialSortColumn, initialSortOrder } = context
20
+ const { sort, initialSortColumn, initialSortOrder, definition } = context
19
21
 
20
22
  // Reset sort when initial sort props change
21
23
  initialSortColumn.subscribe(newSortColumn => {
22
24
  sort.update(state => ({ ...state, column: newSortColumn }))
23
25
  })
24
26
  initialSortOrder.subscribe(newSortOrder => {
25
- sort.update(state => ({ ...state, order: newSortOrder }))
27
+ sort.update(state => ({ ...state, order: newSortOrder || "ascending" }))
28
+ })
29
+
30
+ // Derive if the current sort column exists in the schema
31
+ const sortColumnExists = derived(
32
+ [sort, definition],
33
+ ([$sort, $definition]) => {
34
+ if (!$sort?.column || !$definition) {
35
+ return true
36
+ }
37
+ return $definition.schema?.[$sort.column] != null
38
+ }
39
+ )
40
+
41
+ // Clear sort state if our sort column does not exist
42
+ sortColumnExists.subscribe(exists => {
43
+ if (!exists) {
44
+ sort.set({
45
+ column: null,
46
+ order: "ascending",
47
+ })
48
+ }
26
49
  })
27
50
  }
@@ -0,0 +1,129 @@
1
+ import { get } from "svelte/store"
2
+
3
+ const SuppressErrors = true
4
+
5
+ export const createActions = context => {
6
+ const { definition, API, datasource, columns, stickyColumn } = context
7
+
8
+ const refreshDefinition = async () => {
9
+ definition.set(await API.fetchTableDefinition(get(datasource).tableId))
10
+ }
11
+
12
+ const saveDefinition = async newDefinition => {
13
+ await API.saveTable(newDefinition)
14
+ }
15
+
16
+ const saveRow = async row => {
17
+ row.tableId = get(datasource)?.tableId
18
+ return await API.saveRow(row, SuppressErrors)
19
+ }
20
+
21
+ const deleteRows = async rows => {
22
+ await API.deleteRows({
23
+ tableId: get(datasource).tableId,
24
+ rows,
25
+ })
26
+ }
27
+
28
+ const isDatasourceValid = datasource => {
29
+ return datasource?.type === "table" && datasource?.tableId
30
+ }
31
+
32
+ const getRow = async id => {
33
+ const res = await API.searchTable({
34
+ tableId: get(datasource).tableId,
35
+ limit: 1,
36
+ query: {
37
+ equal: {
38
+ _id: id,
39
+ },
40
+ },
41
+ paginate: false,
42
+ })
43
+ return res?.rows?.[0]
44
+ }
45
+
46
+ const canUseColumn = name => {
47
+ const $columns = get(columns)
48
+ const $sticky = get(stickyColumn)
49
+ return $columns.some(col => col.name === name) || $sticky?.name === name
50
+ }
51
+
52
+ return {
53
+ table: {
54
+ actions: {
55
+ refreshDefinition,
56
+ saveDefinition,
57
+ addRow: saveRow,
58
+ updateRow: saveRow,
59
+ deleteRows,
60
+ getRow,
61
+ isDatasourceValid,
62
+ canUseColumn,
63
+ },
64
+ },
65
+ }
66
+ }
67
+
68
+ export const initialise = context => {
69
+ const {
70
+ datasource,
71
+ fetch,
72
+ filter,
73
+ sort,
74
+ table,
75
+ initialFilter,
76
+ initialSortColumn,
77
+ initialSortOrder,
78
+ } = context
79
+
80
+ // Keep a list of subscriptions so that we can clear them when the datasource
81
+ // config changes
82
+ let unsubscribers = []
83
+
84
+ // Observe datasource changes and apply logic for table datasources
85
+ datasource.subscribe($datasource => {
86
+ // Clear previous subscriptions
87
+ unsubscribers?.forEach(unsubscribe => unsubscribe())
88
+ unsubscribers = []
89
+ if (!table.actions.isDatasourceValid($datasource)) {
90
+ return
91
+ }
92
+
93
+ // Wipe state
94
+ filter.set(get(initialFilter))
95
+ sort.set({
96
+ column: get(initialSortColumn),
97
+ order: get(initialSortOrder) || "ascending",
98
+ })
99
+
100
+ // Update fetch when filter changes
101
+ unsubscribers.push(
102
+ filter.subscribe($filter => {
103
+ // Ensure we're updating the correct fetch
104
+ const $fetch = get(fetch)
105
+ if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
106
+ return
107
+ }
108
+ $fetch.update({
109
+ filter: $filter,
110
+ })
111
+ })
112
+ )
113
+
114
+ // Update fetch when sorting changes
115
+ unsubscribers.push(
116
+ sort.subscribe($sort => {
117
+ // Ensure we're updating the correct fetch
118
+ const $fetch = get(fetch)
119
+ if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
120
+ return
121
+ }
122
+ $fetch.update({
123
+ sortOrder: $sort.order || "ascending",
124
+ sortColumn: $sort.column,
125
+ })
126
+ })
127
+ )
128
+ })
129
+ }