@budibase/frontend-core 2.5.6-alpha.3 → 2.5.6-alpha.31

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 (66) hide show
  1. package/package.json +6 -4
  2. package/src/api/other.js +0 -9
  3. package/src/components/grid/cells/AttachmentCell.svelte +154 -0
  4. package/src/components/grid/cells/BooleanCell.svelte +44 -0
  5. package/src/components/grid/cells/DataCell.svelte +88 -0
  6. package/src/components/grid/cells/DateCell.svelte +77 -0
  7. package/src/components/grid/cells/FormulaCell.svelte +5 -0
  8. package/src/components/grid/cells/GridCell.svelte +162 -0
  9. package/src/components/grid/cells/GutterCell.svelte +127 -0
  10. package/src/components/grid/cells/HeaderCell.svelte +246 -0
  11. package/src/components/grid/cells/JSONCell.svelte +36 -0
  12. package/src/components/grid/cells/LongFormCell.svelte +120 -0
  13. package/src/components/grid/cells/MultiSelectCell.svelte +7 -0
  14. package/src/components/grid/cells/NumberCell.svelte +7 -0
  15. package/src/components/grid/cells/OptionsCell.svelte +240 -0
  16. package/src/components/grid/cells/RelationshipCell.svelte +504 -0
  17. package/src/components/grid/cells/TextCell.svelte +111 -0
  18. package/src/components/grid/controls/AddColumnButton.svelte +16 -0
  19. package/src/components/grid/controls/AddRowButton.svelte +18 -0
  20. package/src/components/grid/controls/BetaButton.svelte +46 -0
  21. package/src/components/grid/controls/ColumnWidthButton.svelte +91 -0
  22. package/src/components/grid/controls/DeleteButton.svelte +66 -0
  23. package/src/components/grid/controls/HideColumnsButton.svelte +95 -0
  24. package/src/components/grid/controls/RowHeightButton.svelte +70 -0
  25. package/src/components/grid/controls/SortButton.svelte +116 -0
  26. package/src/components/grid/index.js +1 -0
  27. package/src/components/grid/layout/Avatar.svelte +24 -0
  28. package/src/components/grid/layout/Grid.svelte +254 -0
  29. package/src/components/grid/layout/GridBody.svelte +73 -0
  30. package/src/components/grid/layout/GridRow.svelte +57 -0
  31. package/src/components/grid/layout/GridScrollWrapper.svelte +81 -0
  32. package/src/components/grid/layout/HeaderRow.svelte +62 -0
  33. package/src/components/grid/layout/NewRow.svelte +265 -0
  34. package/src/components/grid/layout/RowCount.svelte +9 -0
  35. package/src/components/grid/layout/StickyColumn.svelte +171 -0
  36. package/src/components/grid/layout/UserAvatars.svelte +20 -0
  37. package/src/components/grid/lib/constants.js +14 -0
  38. package/src/components/grid/lib/events.js +29 -0
  39. package/src/components/grid/lib/renderers.js +29 -0
  40. package/src/components/grid/lib/utils.js +26 -0
  41. package/src/components/grid/lib/websocket.js +63 -0
  42. package/src/components/grid/overlays/KeyboardManager.svelte +223 -0
  43. package/src/components/grid/overlays/MenuOverlay.svelte +96 -0
  44. package/src/components/grid/overlays/ReorderOverlay.svelte +63 -0
  45. package/src/components/grid/overlays/ResizeOverlay.svelte +70 -0
  46. package/src/components/grid/overlays/ScrollOverlay.svelte +121 -0
  47. package/src/components/grid/stores/bounds.js +16 -0
  48. package/src/components/grid/stores/clipboard.js +34 -0
  49. package/src/components/grid/stores/columns.js +206 -0
  50. package/src/components/grid/stores/index.js +48 -0
  51. package/src/components/grid/stores/menu.js +49 -0
  52. package/src/components/grid/stores/pagination.js +24 -0
  53. package/src/components/grid/stores/reorder.js +155 -0
  54. package/src/components/grid/stores/resize.js +124 -0
  55. package/src/components/grid/stores/rows.js +467 -0
  56. package/src/components/grid/stores/scroll.js +201 -0
  57. package/src/components/grid/stores/ui.js +159 -0
  58. package/src/components/grid/stores/users.js +115 -0
  59. package/src/components/grid/stores/validation.js +45 -0
  60. package/src/components/grid/stores/viewport.js +145 -0
  61. package/src/components/index.js +1 -0
  62. package/src/fetch/DataFetch.js +27 -21
  63. package/src/fetch/QueryFetch.js +1 -1
  64. package/src/themes/midnight.css +3 -0
  65. package/src/themes/nord.css +3 -0
  66. package/src/utils/utils.js +20 -0
@@ -0,0 +1,206 @@
1
+ import { derived, get, writable } from "svelte/store"
2
+ import { cloneDeep } from "lodash/fp"
3
+ import { GutterWidth, DefaultColumnWidth } from "../lib/constants"
4
+
5
+ export const createStores = () => {
6
+ const columns = writable([])
7
+ const stickyColumn = writable(null)
8
+
9
+ // Derive an enriched version of columns with left offsets and indexes
10
+ // automatically calculated
11
+ const enrichedColumns = derived(
12
+ columns,
13
+ $columns => {
14
+ let offset = 0
15
+ return $columns.map(column => {
16
+ const enriched = {
17
+ ...column,
18
+ left: offset,
19
+ }
20
+ if (column.visible) {
21
+ offset += column.width
22
+ }
23
+ return enriched
24
+ })
25
+ },
26
+ []
27
+ )
28
+
29
+ // Derived list of columns which have not been explicitly hidden
30
+ const visibleColumns = derived(
31
+ enrichedColumns,
32
+ $columns => {
33
+ return $columns.filter(col => col.visible)
34
+ },
35
+ []
36
+ )
37
+
38
+ return {
39
+ columns: {
40
+ ...columns,
41
+ subscribe: enrichedColumns.subscribe,
42
+ },
43
+ stickyColumn,
44
+ visibleColumns,
45
+ }
46
+ }
47
+
48
+ export const deriveStores = context => {
49
+ const { table, columns, stickyColumn, API, dispatch } = context
50
+
51
+ // Updates the tables primary display column
52
+ const changePrimaryDisplay = async column => {
53
+ return await saveTable({
54
+ ...get(table),
55
+ primaryDisplay: column,
56
+ })
57
+ }
58
+
59
+ // Persists column changes by saving metadata against table schema
60
+ const saveChanges = async () => {
61
+ const $columns = get(columns)
62
+ const $table = get(table)
63
+ const $stickyColumn = get(stickyColumn)
64
+ const newSchema = cloneDeep($table.schema)
65
+
66
+ // Build new updated table schema
67
+ Object.keys(newSchema).forEach(column => {
68
+ // Respect order specified by columns
69
+ const index = $columns.findIndex(x => x.name === column)
70
+ if (index !== -1) {
71
+ newSchema[column].order = index
72
+ } else {
73
+ delete newSchema[column].order
74
+ }
75
+
76
+ // Copy over metadata
77
+ if (column === $stickyColumn?.name) {
78
+ newSchema[column].visible = true
79
+ newSchema[column].width = $stickyColumn.width || DefaultColumnWidth
80
+ } else {
81
+ newSchema[column].visible = $columns[index]?.visible ?? true
82
+ newSchema[column].width = $columns[index]?.width || DefaultColumnWidth
83
+ }
84
+ })
85
+
86
+ await saveTable({ ...$table, schema: newSchema })
87
+ }
88
+
89
+ const saveTable = async newTable => {
90
+ // Update local state
91
+ table.set(newTable)
92
+
93
+ // Broadcast event so that we can keep sync with external state
94
+ // (e.g. data section which maintains a list of table definitions)
95
+ dispatch("updatetable", newTable)
96
+
97
+ // Update server
98
+ await API.saveTable(newTable)
99
+ }
100
+
101
+ return {
102
+ columns: {
103
+ ...columns,
104
+ actions: {
105
+ saveChanges,
106
+ saveTable,
107
+ changePrimaryDisplay,
108
+ },
109
+ },
110
+ }
111
+ }
112
+
113
+ export const initialise = context => {
114
+ const { table, columns, stickyColumn, schemaOverrides } = context
115
+
116
+ const schema = derived(
117
+ [table, schemaOverrides],
118
+ ([$table, $schemaOverrides]) => {
119
+ let newSchema = $table?.schema
120
+ if (!newSchema) {
121
+ return null
122
+ }
123
+ Object.keys($schemaOverrides || {}).forEach(field => {
124
+ if (newSchema[field]) {
125
+ newSchema[field] = {
126
+ ...newSchema[field],
127
+ ...$schemaOverrides[field],
128
+ }
129
+ }
130
+ })
131
+ return newSchema
132
+ }
133
+ )
134
+
135
+ // Merge new schema fields with existing schema in order to preserve widths
136
+ schema.subscribe($schema => {
137
+ if (!$schema) {
138
+ columns.set([])
139
+ stickyColumn.set(null)
140
+ return
141
+ }
142
+ const $table = get(table)
143
+
144
+ // Find primary display
145
+ let primaryDisplay
146
+ if ($table.primaryDisplay && $schema[$table.primaryDisplay]) {
147
+ primaryDisplay = $table.primaryDisplay
148
+ }
149
+
150
+ // Get field list
151
+ let fields = []
152
+ Object.keys($schema).forEach(field => {
153
+ if (field !== primaryDisplay) {
154
+ fields.push(field)
155
+ }
156
+ })
157
+
158
+ // Update columns, removing extraneous columns and adding missing ones
159
+ columns.set(
160
+ fields
161
+ .map(field => ({
162
+ name: field,
163
+ label: $schema[field].name || field,
164
+ schema: $schema[field],
165
+ width: $schema[field].width || DefaultColumnWidth,
166
+ visible: $schema[field].visible ?? true,
167
+ order: $schema[field].order,
168
+ }))
169
+ .sort((a, b) => {
170
+ // Sort by order first
171
+ const orderA = a.order
172
+ const orderB = b.order
173
+ if (orderA != null && orderB != null) {
174
+ return orderA < orderB ? -1 : 1
175
+ } else if (orderA != null) {
176
+ return -1
177
+ } else if (orderB != null) {
178
+ return 1
179
+ }
180
+
181
+ // Then sort by auto columns
182
+ const autoColA = a.schema?.autocolumn
183
+ const autoColB = b.schema?.autocolumn
184
+ if (autoColA === autoColB) {
185
+ return 0
186
+ }
187
+ return autoColA ? 1 : -1
188
+ })
189
+ )
190
+
191
+ // Update sticky column
192
+ if (!primaryDisplay) {
193
+ stickyColumn.set(null)
194
+ return
195
+ }
196
+ stickyColumn.set({
197
+ name: primaryDisplay,
198
+ label: $schema[primaryDisplay].name || primaryDisplay,
199
+ schema: $schema[primaryDisplay],
200
+ width: $schema[primaryDisplay].width || DefaultColumnWidth,
201
+ visible: true,
202
+ order: 0,
203
+ left: GutterWidth,
204
+ })
205
+ })
206
+ }
@@ -0,0 +1,48 @@
1
+ import * as Bounds from "./bounds"
2
+ import * as Columns from "./columns"
3
+ import * as Menu from "./menu"
4
+ import * as Pagination from "./pagination"
5
+ import * as Reorder from "./reorder"
6
+ import * as Resize from "./resize"
7
+ import * as Rows from "./rows"
8
+ import * as Scroll from "./scroll"
9
+ import * as UI from "./ui"
10
+ import * as Users from "./users"
11
+ import * as Validation from "./validation"
12
+ import * as Viewport from "./viewport"
13
+ import * as Clipboard from "./clipboard"
14
+
15
+ const DependencyOrderedStores = [
16
+ Bounds,
17
+ Scroll,
18
+ Rows,
19
+ Columns,
20
+ UI,
21
+ Validation,
22
+ Resize,
23
+ Viewport,
24
+ Reorder,
25
+ Users,
26
+ Menu,
27
+ Pagination,
28
+ Clipboard,
29
+ ]
30
+
31
+ export const attachStores = context => {
32
+ // Atomic store creation
33
+ for (let store of DependencyOrderedStores) {
34
+ context = { ...context, ...store.createStores?.(context) }
35
+ }
36
+
37
+ // Derived store creation
38
+ for (let store of DependencyOrderedStores) {
39
+ context = { ...context, ...store.deriveStores?.(context) }
40
+ }
41
+
42
+ // Initialise any store logic
43
+ for (let store of DependencyOrderedStores) {
44
+ store.initialise?.(context)
45
+ }
46
+
47
+ return context
48
+ }
@@ -0,0 +1,49 @@
1
+ import { writable, get } from "svelte/store"
2
+ import { GutterWidth } from "../lib/constants"
3
+
4
+ export const createStores = () => {
5
+ const menu = writable({
6
+ x: 0,
7
+ y: 0,
8
+ visible: false,
9
+ selectedRow: null,
10
+ })
11
+ return {
12
+ menu,
13
+ }
14
+ }
15
+
16
+ export const deriveStores = context => {
17
+ const { menu, bounds, focusedCellId, stickyColumn, rowHeight } = context
18
+
19
+ const open = (cellId, e) => {
20
+ const $bounds = get(bounds)
21
+ const $stickyColumn = get(stickyColumn)
22
+ const $rowHeight = get(rowHeight)
23
+ e.preventDefault()
24
+ focusedCellId.set(cellId)
25
+ menu.set({
26
+ left:
27
+ e.clientX - $bounds.left + GutterWidth + ($stickyColumn?.width || 0),
28
+ top: e.clientY - $bounds.top + $rowHeight,
29
+ visible: true,
30
+ })
31
+ }
32
+
33
+ const close = () => {
34
+ menu.update(state => ({
35
+ ...state,
36
+ visible: false,
37
+ }))
38
+ }
39
+
40
+ return {
41
+ menu: {
42
+ ...menu,
43
+ actions: {
44
+ open,
45
+ close,
46
+ },
47
+ },
48
+ }
49
+ }
@@ -0,0 +1,24 @@
1
+ import { derived } from "svelte/store"
2
+
3
+ export const initialise = context => {
4
+ const { scrolledRowCount, rows, visualRowCapacity } = context
5
+
6
+ // Derive how many rows we have in total
7
+ const rowCount = derived(rows, $rows => $rows.length, 0)
8
+
9
+ // Derive how many rows we have available to scroll
10
+ const remainingRows = derived(
11
+ [scrolledRowCount, rowCount, visualRowCapacity],
12
+ ([$scrolledRowCount, $rowCount, $visualRowCapacity]) => {
13
+ return Math.max(0, $rowCount - $scrolledRowCount - $visualRowCapacity)
14
+ },
15
+ 100
16
+ )
17
+
18
+ // Fetch next page when fewer than 25 remaining rows to scroll
19
+ remainingRows.subscribe(remaining => {
20
+ if (remaining < 25) {
21
+ rows.actions.loadNextPage()
22
+ }
23
+ })
24
+ }
@@ -0,0 +1,155 @@
1
+ import { get, writable, derived } from "svelte/store"
2
+
3
+ const reorderInitialState = {
4
+ sourceColumn: null,
5
+ targetColumn: null,
6
+ breakpoints: [],
7
+ initialMouseX: null,
8
+ scrollLeft: 0,
9
+ gridLeft: 0,
10
+ }
11
+
12
+ export const createStores = () => {
13
+ const reorder = writable(reorderInitialState)
14
+ const isReordering = derived(
15
+ reorder,
16
+ $reorder => !!$reorder.sourceColumn,
17
+ false
18
+ )
19
+ return {
20
+ reorder,
21
+ isReordering,
22
+ }
23
+ }
24
+
25
+ export const deriveStores = context => {
26
+ const { reorder, columns, visibleColumns, scroll, bounds, stickyColumn, ui } =
27
+ context
28
+
29
+ // Callback when dragging on a colum header and starting reordering
30
+ const startReordering = (column, e) => {
31
+ const $visibleColumns = get(visibleColumns)
32
+ const $bounds = get(bounds)
33
+ const $scroll = get(scroll)
34
+ const $stickyColumn = get(stickyColumn)
35
+ ui.actions.blur()
36
+
37
+ // Generate new breakpoints for the current columns
38
+ let breakpoints = $visibleColumns.map(col => ({
39
+ x: col.left + col.width,
40
+ column: col.name,
41
+ }))
42
+ if ($stickyColumn) {
43
+ breakpoints.unshift({
44
+ x: 0,
45
+ column: $stickyColumn.name,
46
+ })
47
+ }
48
+
49
+ // Update state
50
+ reorder.set({
51
+ sourceColumn: column,
52
+ targetColumn: null,
53
+ breakpoints,
54
+ initialMouseX: e.clientX,
55
+ scrollLeft: $scroll.left,
56
+ gridLeft: $bounds.left,
57
+ })
58
+
59
+ // Add listeners to handle mouse movement
60
+ document.addEventListener("mousemove", onReorderMouseMove)
61
+ document.addEventListener("mouseup", stopReordering)
62
+
63
+ // Trigger a move event immediately so ensure a candidate column is chosen
64
+ onReorderMouseMove(e)
65
+ }
66
+
67
+ // Callback when moving the mouse when reordering columns
68
+ const onReorderMouseMove = e => {
69
+ const $reorder = get(reorder)
70
+
71
+ // Compute the closest breakpoint to the current position
72
+ let targetColumn
73
+ let minDistance = Number.MAX_SAFE_INTEGER
74
+ const mouseX = e.clientX - $reorder.gridLeft + $reorder.scrollLeft
75
+ $reorder.breakpoints.forEach(point => {
76
+ const distance = Math.abs(point.x - mouseX)
77
+ if (distance < minDistance) {
78
+ minDistance = distance
79
+ targetColumn = point.column
80
+ }
81
+ })
82
+
83
+ if (targetColumn !== $reorder.targetColumn) {
84
+ reorder.update(state => ({
85
+ ...state,
86
+ targetColumn,
87
+ }))
88
+ }
89
+ }
90
+
91
+ // Callback when stopping reordering columns
92
+ const stopReordering = async () => {
93
+ // Swap position of columns
94
+ let { sourceColumn, targetColumn } = get(reorder)
95
+ moveColumn(sourceColumn, targetColumn)
96
+
97
+ // Reset state
98
+ reorder.set(reorderInitialState)
99
+
100
+ // Remove event handlers
101
+ document.removeEventListener("mousemove", onReorderMouseMove)
102
+ document.removeEventListener("mouseup", stopReordering)
103
+
104
+ // Save column changes
105
+ await columns.actions.saveChanges()
106
+ }
107
+
108
+ // Moves a column after another columns.
109
+ // An undefined target column will move the source to index 0.
110
+ const moveColumn = (sourceColumn, targetColumn) => {
111
+ let $columns = get(columns)
112
+ let sourceIdx = $columns.findIndex(x => x.name === sourceColumn)
113
+ let targetIdx = $columns.findIndex(x => x.name === targetColumn)
114
+ targetIdx++
115
+ columns.update(state => {
116
+ const removed = state.splice(sourceIdx, 1)
117
+ if (--targetIdx < sourceIdx) {
118
+ targetIdx++
119
+ }
120
+ state.splice(targetIdx, 0, removed[0])
121
+ return state.slice()
122
+ })
123
+ }
124
+
125
+ // Moves a column one place left (as appears visually)
126
+ const moveColumnLeft = async column => {
127
+ const $visibleColumns = get(visibleColumns)
128
+ const sourceIdx = $visibleColumns.findIndex(x => x.name === column)
129
+ moveColumn(column, $visibleColumns[sourceIdx - 2]?.name)
130
+ await columns.actions.saveChanges()
131
+ }
132
+
133
+ // Moves a column one place right (as appears visually)
134
+ const moveColumnRight = async column => {
135
+ const $visibleColumns = get(visibleColumns)
136
+ const sourceIdx = $visibleColumns.findIndex(x => x.name === column)
137
+ if (sourceIdx === $visibleColumns.length - 1) {
138
+ return
139
+ }
140
+ moveColumn(column, $visibleColumns[sourceIdx + 1]?.name)
141
+ await columns.actions.saveChanges()
142
+ }
143
+
144
+ return {
145
+ reorder: {
146
+ ...reorder,
147
+ actions: {
148
+ startReordering,
149
+ stopReordering,
150
+ moveColumnLeft,
151
+ moveColumnRight,
152
+ },
153
+ },
154
+ }
155
+ }
@@ -0,0 +1,124 @@
1
+ import { writable, get, derived } from "svelte/store"
2
+ import { MinColumnWidth, DefaultColumnWidth } from "../lib/constants"
3
+
4
+ const initialState = {
5
+ initialMouseX: null,
6
+ initialWidth: null,
7
+ column: null,
8
+ columnIdx: null,
9
+ width: 0,
10
+ left: 0,
11
+ }
12
+
13
+ export const createStores = () => {
14
+ const resize = writable(initialState)
15
+ const isResizing = derived(resize, $resize => $resize.column != null, false)
16
+ return {
17
+ resize,
18
+ isResizing,
19
+ }
20
+ }
21
+
22
+ export const deriveStores = context => {
23
+ const { resize, columns, stickyColumn, ui } = context
24
+
25
+ // Starts resizing a certain column
26
+ const startResizing = (column, e) => {
27
+ // Prevent propagation to stop reordering triggering
28
+ e.stopPropagation()
29
+ ui.actions.blur()
30
+
31
+ // Find and cache index
32
+ let columnIdx = get(columns).findIndex(col => col.name === column.name)
33
+ if (columnIdx === -1) {
34
+ columnIdx = "sticky"
35
+ }
36
+
37
+ // Set initial store state
38
+ resize.set({
39
+ width: column.width,
40
+ left: column.left,
41
+ initialWidth: column.width,
42
+ initialMouseX: e.clientX,
43
+ column: column.name,
44
+ columnIdx,
45
+ })
46
+
47
+ // Add mouse event listeners to handle resizing
48
+ document.addEventListener("mousemove", onResizeMouseMove)
49
+ document.addEventListener("mouseup", stopResizing)
50
+ }
51
+
52
+ // Handler for moving the mouse to resize columns
53
+ const onResizeMouseMove = e => {
54
+ const { initialMouseX, initialWidth, width, columnIdx } = get(resize)
55
+ const dx = e.clientX - initialMouseX
56
+ const newWidth = Math.round(Math.max(MinColumnWidth, initialWidth + dx))
57
+
58
+ // Ignore small changes
59
+ if (Math.abs(width - newWidth) < 5) {
60
+ return
61
+ }
62
+
63
+ // Update column state
64
+ if (columnIdx === "sticky") {
65
+ stickyColumn.update(state => ({
66
+ ...state,
67
+ width: newWidth,
68
+ }))
69
+ } else {
70
+ columns.update(state => {
71
+ state[columnIdx].width = newWidth
72
+ return [...state]
73
+ })
74
+ }
75
+
76
+ // Update state
77
+ resize.update(state => ({
78
+ ...state,
79
+ width: newWidth,
80
+ }))
81
+ }
82
+
83
+ // Stop resizing any columns
84
+ const stopResizing = async () => {
85
+ // Reset state
86
+ const $resize = get(resize)
87
+ resize.set(initialState)
88
+ document.removeEventListener("mousemove", onResizeMouseMove)
89
+ document.removeEventListener("mouseup", stopResizing)
90
+
91
+ // Persist width if it changed
92
+ if ($resize.width !== $resize.initialWidth) {
93
+ await columns.actions.saveChanges()
94
+ }
95
+ }
96
+
97
+ // Resets a column size back to default
98
+ const resetSize = async column => {
99
+ const $stickyColumn = get(stickyColumn)
100
+ if (column.name === $stickyColumn?.name) {
101
+ stickyColumn.update(state => ({
102
+ ...state,
103
+ width: DefaultColumnWidth,
104
+ }))
105
+ } else {
106
+ columns.update(state => {
107
+ const columnIdx = state.findIndex(x => x.name === column.name)
108
+ state[columnIdx].width = DefaultColumnWidth
109
+ return [...state]
110
+ })
111
+ }
112
+ await columns.actions.saveChanges()
113
+ }
114
+
115
+ return {
116
+ resize: {
117
+ ...resize,
118
+ actions: {
119
+ startResizing,
120
+ resetSize,
121
+ },
122
+ },
123
+ }
124
+ }