@budibase/frontend-core 2.5.9 → 2.5.10-alpha.1

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 (68) 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 +91 -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 +165 -0
  9. package/src/components/grid/cells/GutterCell.svelte +127 -0
  10. package/src/components/grid/cells/HeaderCell.svelte +248 -0
  11. package/src/components/grid/cells/JSONCell.svelte +36 -0
  12. package/src/components/grid/cells/LongFormCell.svelte +122 -0
  13. package/src/components/grid/cells/MultiSelectCell.svelte +7 -0
  14. package/src/components/grid/cells/NumberCell.svelte +14 -0
  15. package/src/components/grid/cells/OptionsCell.svelte +249 -0
  16. package/src/components/grid/cells/RelationshipCell.svelte +518 -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 +129 -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 +59 -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/KeyboardShortcut.svelte +53 -0
  34. package/src/components/grid/layout/NewRow.svelte +334 -0
  35. package/src/components/grid/layout/RowCount.svelte +9 -0
  36. package/src/components/grid/layout/StickyColumn.svelte +176 -0
  37. package/src/components/grid/layout/UserAvatars.svelte +20 -0
  38. package/src/components/grid/lib/constants.js +14 -0
  39. package/src/components/grid/lib/events.js +29 -0
  40. package/src/components/grid/lib/renderers.js +29 -0
  41. package/src/components/grid/lib/utils.js +26 -0
  42. package/src/components/grid/lib/websocket.js +63 -0
  43. package/src/components/grid/overlays/KeyboardManager.svelte +232 -0
  44. package/src/components/grid/overlays/MenuOverlay.svelte +96 -0
  45. package/src/components/grid/overlays/ReorderOverlay.svelte +63 -0
  46. package/src/components/grid/overlays/ResizeOverlay.svelte +70 -0
  47. package/src/components/grid/overlays/ScrollOverlay.svelte +121 -0
  48. package/src/components/grid/stores/bounds.js +16 -0
  49. package/src/components/grid/stores/clipboard.js +34 -0
  50. package/src/components/grid/stores/columns.js +206 -0
  51. package/src/components/grid/stores/index.js +48 -0
  52. package/src/components/grid/stores/menu.js +49 -0
  53. package/src/components/grid/stores/pagination.js +24 -0
  54. package/src/components/grid/stores/reorder.js +155 -0
  55. package/src/components/grid/stores/resize.js +124 -0
  56. package/src/components/grid/stores/rows.js +467 -0
  57. package/src/components/grid/stores/scroll.js +201 -0
  58. package/src/components/grid/stores/ui.js +159 -0
  59. package/src/components/grid/stores/users.js +115 -0
  60. package/src/components/grid/stores/validation.js +45 -0
  61. package/src/components/grid/stores/viewport.js +145 -0
  62. package/src/components/index.js +1 -0
  63. package/src/constants.js +1 -0
  64. package/src/fetch/DataFetch.js +27 -21
  65. package/src/fetch/QueryFetch.js +1 -1
  66. package/src/themes/midnight.css +3 -0
  67. package/src/themes/nord.css +3 -0
  68. package/src/utils/utils.js +20 -0
@@ -0,0 +1,334 @@
1
+ <script>
2
+ import { getContext, onDestroy, onMount, tick } from "svelte"
3
+ import { Icon, Button } from "@budibase/bbui"
4
+ import GridScrollWrapper from "./GridScrollWrapper.svelte"
5
+ import DataCell from "../cells/DataCell.svelte"
6
+ import { fade } from "svelte/transition"
7
+ import { GutterWidth } from "../lib/constants"
8
+ import { NewRowID } from "../lib/constants"
9
+ import GutterCell from "../cells/GutterCell.svelte"
10
+ import KeyboardShortcut from "./KeyboardShortcut.svelte"
11
+
12
+ const {
13
+ hoveredRowId,
14
+ focusedCellId,
15
+ stickyColumn,
16
+ scroll,
17
+ dispatch,
18
+ rows,
19
+ focusedCellAPI,
20
+ tableId,
21
+ subscribe,
22
+ renderedRows,
23
+ renderedColumns,
24
+ rowHeight,
25
+ hasNextPage,
26
+ maxScrollTop,
27
+ rowVerticalInversionIndex,
28
+ columnHorizontalInversionIndex,
29
+ } = getContext("grid")
30
+
31
+ let visible = false
32
+ let isAdding = false
33
+ let newRow = {}
34
+ let offset = 0
35
+
36
+ $: firstColumn = $stickyColumn || $renderedColumns[0]
37
+ $: width = GutterWidth + ($stickyColumn?.width || 0)
38
+ $: $tableId, (visible = false)
39
+ $: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows)
40
+
41
+ const shouldInvertY = (offset, inversionIndex, rows) => {
42
+ if (offset === 0) {
43
+ return false
44
+ }
45
+ return rows.length >= inversionIndex
46
+ }
47
+
48
+ const addRow = async () => {
49
+ // Blur the active cell and tick to let final value updates propagate
50
+ isAdding = true
51
+ $focusedCellId = null
52
+ await tick()
53
+
54
+ // Create row
55
+ const newRowIndex = offset ? undefined : 0
56
+ const savedRow = await rows.actions.addRow(newRow, newRowIndex)
57
+ if (savedRow) {
58
+ // Reset state
59
+ clear()
60
+
61
+ // Select the first cell if possible
62
+ if (firstColumn) {
63
+ $focusedCellId = `${savedRow._id}-${firstColumn.name}`
64
+ }
65
+ }
66
+ isAdding = false
67
+ }
68
+
69
+ const clear = () => {
70
+ isAdding = false
71
+ visible = false
72
+ $focusedCellId = null
73
+ $hoveredRowId = null
74
+ document.removeEventListener("keydown", handleKeyPress)
75
+ }
76
+
77
+ const startAdding = async () => {
78
+ if (visible) {
79
+ return
80
+ }
81
+
82
+ // If we have a next page of data then we aren't truly at the bottom, so we
83
+ // render the add row component at the top
84
+ if ($hasNextPage) {
85
+ offset = 0
86
+ }
87
+
88
+ // If we don't have a next page then we're at the bottom and can scroll to
89
+ // the max available offset
90
+ else {
91
+ scroll.update(state => ({
92
+ ...state,
93
+ top: $maxScrollTop,
94
+ }))
95
+ offset = $renderedRows.length * $rowHeight - ($maxScrollTop % $rowHeight)
96
+ if ($renderedRows.length !== 0) {
97
+ offset -= 1
98
+ }
99
+ }
100
+
101
+ // Update state and select initial cell
102
+ newRow = {}
103
+ visible = true
104
+ $hoveredRowId = NewRowID
105
+ if (firstColumn) {
106
+ $focusedCellId = `${NewRowID}-${firstColumn.name}`
107
+ }
108
+
109
+ // Attach key listener
110
+ document.addEventListener("keydown", handleKeyPress)
111
+ }
112
+
113
+ const updateValue = (rowId, columnName, val) => {
114
+ newRow[columnName] = val
115
+ }
116
+
117
+ const addViaModal = () => {
118
+ clear()
119
+ dispatch("add-row")
120
+ }
121
+
122
+ const handleKeyPress = e => {
123
+ if (!visible) {
124
+ return
125
+ }
126
+ if (e.key === "Escape") {
127
+ // Only close the new row component if we aren't actively inside a cell
128
+ if (!$focusedCellAPI?.isActive()) {
129
+ e.preventDefault()
130
+ clear()
131
+ }
132
+ } else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
133
+ e.preventDefault()
134
+ addRow()
135
+ }
136
+ }
137
+
138
+ onMount(() => subscribe("add-row-inline", startAdding))
139
+ onDestroy(() => {
140
+ document.removeEventListener("keydown", handleKeyPress)
141
+ })
142
+ </script>
143
+
144
+ <!-- Only show new row functionality if we have any columns -->
145
+ {#if visible}
146
+ <div
147
+ class="container"
148
+ class:floating={offset > 0}
149
+ style="--offset:{offset}px; --sticky-width:{width}px;"
150
+ >
151
+ <div class="underlay sticky" transition:fade={{ duration: 130 }} />
152
+ <div class="underlay" transition:fade={{ duration: 130 }} />
153
+ <div class="sticky-column" transition:fade={{ duration: 130 }}>
154
+ <GutterCell on:expand={addViaModal} rowHovered>
155
+ <Icon name="Add" color="var(--spectrum-global-color-gray-500)" />
156
+ {#if isAdding}
157
+ <div in:fade={{ duration: 130 }} class="loading-overlay" />
158
+ {/if}
159
+ </GutterCell>
160
+ {#if $stickyColumn}
161
+ {@const cellId = `${NewRowID}-${$stickyColumn.name}`}
162
+ <DataCell
163
+ {cellId}
164
+ rowFocused
165
+ column={$stickyColumn}
166
+ row={newRow}
167
+ focused={$focusedCellId === cellId}
168
+ width={$stickyColumn.width}
169
+ {updateValue}
170
+ rowIdx={0}
171
+ {invertY}
172
+ >
173
+ {#if $stickyColumn?.schema?.autocolumn}
174
+ <div class="readonly-overlay">Can't edit auto column</div>
175
+ {/if}
176
+ {#if isAdding}
177
+ <div in:fade={{ duration: 130 }} class="loading-overlay" />
178
+ {/if}
179
+ </DataCell>
180
+ {/if}
181
+ </div>
182
+ <div class="normal-columns" transition:fade={{ duration: 130 }}>
183
+ <GridScrollWrapper scrollHorizontally wheelInteractive>
184
+ <div class="row">
185
+ {#each $renderedColumns as column, columnIdx}
186
+ {@const cellId = `new-${column.name}`}
187
+ {#key cellId}
188
+ <DataCell
189
+ {cellId}
190
+ {column}
191
+ {updateValue}
192
+ rowFocused
193
+ row={newRow}
194
+ focused={$focusedCellId === cellId}
195
+ width={column.width}
196
+ rowIdx={0}
197
+ invertX={columnIdx >= $columnHorizontalInversionIndex}
198
+ {invertY}
199
+ >
200
+ {#if column?.schema?.autocolumn}
201
+ <div class="readonly-overlay">Can't edit auto column</div>
202
+ {/if}
203
+ {#if isAdding}
204
+ <div in:fade={{ duration: 130 }} class="loading-overlay" />
205
+ {/if}
206
+ </DataCell>
207
+ {/key}
208
+ {/each}
209
+ </div>
210
+ </GridScrollWrapper>
211
+ </div>
212
+ <div class="buttons" transition:fade={{ duration: 130 }}>
213
+ <Button size="M" cta on:click={addRow} disabled={isAdding}>
214
+ <div class="button-with-keys">
215
+ Save
216
+ <KeyboardShortcut overlay keybind="Ctrl+Enter" />
217
+ </div>
218
+ </Button>
219
+ <Button size="M" secondary newStyles on:click={clear}>
220
+ <div class="button-with-keys">
221
+ Cancel
222
+ <KeyboardShortcut overlay keybind="Esc" />
223
+ </div>
224
+ </Button>
225
+ </div>
226
+ </div>
227
+ {/if}
228
+
229
+ <style>
230
+ .container {
231
+ position: absolute;
232
+ top: var(--default-row-height);
233
+ left: 0;
234
+ width: 100%;
235
+ height: 100%;
236
+ display: flex;
237
+ flex-direction: row;
238
+ align-items: stretch;
239
+ }
240
+ .container :global(.cell) {
241
+ --cell-background: var(--spectrum-global-color-gray-75) !important;
242
+ }
243
+ .container.floating :global(.cell) {
244
+ height: calc(var(--row-height) + 1px);
245
+ border-top: var(--cell-border);
246
+ }
247
+
248
+ /* Underlay sits behind everything */
249
+ .underlay {
250
+ position: absolute;
251
+ content: "";
252
+ left: 0;
253
+ top: 0;
254
+ height: 100%;
255
+ width: 100%;
256
+ background: var(--cell-background);
257
+ opacity: 0.8;
258
+ }
259
+ .underlay.sticky {
260
+ z-index: 2;
261
+ width: var(--sticky-width);
262
+ }
263
+
264
+ /* Floating buttons which sit on top of the underlay but below the sticky column */
265
+ .buttons {
266
+ display: flex;
267
+ flex-direction: row;
268
+ gap: 8px;
269
+ pointer-events: all;
270
+ z-index: 3;
271
+ position: absolute;
272
+ top: calc(var(--row-height) + var(--offset) + 24px);
273
+ left: var(--gutter-width);
274
+ }
275
+ .button-with-keys {
276
+ display: flex;
277
+ gap: 6px;
278
+ align-items: center;
279
+ }
280
+ .button-with-keys :global(> div) {
281
+ padding-top: 2px;
282
+ }
283
+
284
+ /* Sticky column styles */
285
+ .sticky-column {
286
+ display: flex;
287
+ z-index: 4;
288
+ position: relative;
289
+ align-self: flex-start;
290
+ flex: 0 0 var(--sticky-width);
291
+ }
292
+ .sticky-column :global(.cell:not(:last-child)) {
293
+ border-right: none;
294
+ }
295
+ .sticky-column,
296
+ .normal-columns {
297
+ margin-top: var(--offset);
298
+ }
299
+
300
+ /* Normal column styles */
301
+ .row {
302
+ width: 0;
303
+ display: flex;
304
+ }
305
+
306
+ /* Readonly cell overlay */
307
+ .readonly-overlay {
308
+ position: absolute;
309
+ top: 0;
310
+ left: 0;
311
+ height: var(--row-height);
312
+ width: 100%;
313
+ padding: var(--cell-padding);
314
+ font-style: italic;
315
+ color: var(--spectrum-global-color-gray-600);
316
+ z-index: 1;
317
+ user-select: none;
318
+ overflow: hidden;
319
+ white-space: nowrap;
320
+ text-overflow: ellipsis;
321
+ }
322
+
323
+ /* Overlay while row is being added */
324
+ .loading-overlay {
325
+ position: absolute;
326
+ top: 0;
327
+ left: 0;
328
+ height: var(--row-height);
329
+ width: 100%;
330
+ z-index: 1;
331
+ background: var(--spectrum-global-color-gray-400);
332
+ opacity: 0.25;
333
+ }
334
+ </style>
@@ -0,0 +1,9 @@
1
+ <script>
2
+ import { getContext } from "svelte"
3
+
4
+ const { rows } = getContext("grid")
5
+ </script>
6
+
7
+ <div>
8
+ {$rows.length} row{$rows.length === 1 ? "" : "s"}
9
+ </div>
@@ -0,0 +1,176 @@
1
+ <script>
2
+ import { getContext } from "svelte"
3
+ import { Icon } from "@budibase/bbui"
4
+ import GridCell from "../cells/GridCell.svelte"
5
+ import DataCell from "../cells/DataCell.svelte"
6
+ import GridScrollWrapper from "./GridScrollWrapper.svelte"
7
+ import HeaderCell from "../cells/HeaderCell.svelte"
8
+ import { GutterWidth, BlankRowID } from "../lib/constants"
9
+ import GutterCell from "../cells/GutterCell.svelte"
10
+ import KeyboardShortcut from "./KeyboardShortcut.svelte"
11
+
12
+ const {
13
+ rows,
14
+ selectedRows,
15
+ stickyColumn,
16
+ renderedColumns,
17
+ renderedRows,
18
+ focusedCellId,
19
+ hoveredRowId,
20
+ config,
21
+ selectedCellMap,
22
+ focusedRow,
23
+ scrollLeft,
24
+ dispatch,
25
+ contentLines,
26
+ } = getContext("grid")
27
+
28
+ $: rowCount = $rows.length
29
+ $: selectedRowCount = Object.values($selectedRows).filter(x => !!x).length
30
+ $: width = GutterWidth + ($stickyColumn?.width || 0)
31
+
32
+ const selectAll = () => {
33
+ const allSelected = selectedRowCount === rowCount
34
+ if (allSelected) {
35
+ $selectedRows = {}
36
+ } else {
37
+ let allRows = {}
38
+ $rows.forEach(row => {
39
+ allRows[row._id] = true
40
+ })
41
+ $selectedRows = allRows
42
+ }
43
+ }
44
+ </script>
45
+
46
+ <div
47
+ class="sticky-column"
48
+ style="flex: 0 0 {width}px"
49
+ class:scrolled={$scrollLeft > 0}
50
+ >
51
+ <div class="header row">
52
+ <GutterCell
53
+ disableExpand
54
+ disableNumber
55
+ on:select={selectAll}
56
+ defaultHeight
57
+ rowSelected={selectedRowCount && selectedRowCount === rowCount}
58
+ disabled={!$renderedRows.length}
59
+ />
60
+ {#if $stickyColumn}
61
+ <HeaderCell column={$stickyColumn} orderable={false} idx="sticky" />
62
+ {/if}
63
+ </div>
64
+
65
+ <div class="content" on:mouseleave={() => ($hoveredRowId = null)}>
66
+ <GridScrollWrapper scrollVertically wheelInteractive>
67
+ {#each $renderedRows as row, idx}
68
+ {@const rowSelected = !!$selectedRows[row._id]}
69
+ {@const rowHovered = $hoveredRowId === row._id}
70
+ {@const rowFocused = $focusedRow?._id === row._id}
71
+ {@const cellId = `${row._id}-${$stickyColumn?.name}`}
72
+ <div
73
+ class="row"
74
+ on:mouseenter={() => ($hoveredRowId = row._id)}
75
+ on:mouseleave={() => ($hoveredRowId = null)}
76
+ >
77
+ <GutterCell {row} {rowFocused} {rowHovered} {rowSelected} />
78
+ {#if $stickyColumn}
79
+ <DataCell
80
+ {row}
81
+ {cellId}
82
+ {rowFocused}
83
+ selected={rowSelected}
84
+ highlighted={rowHovered || rowFocused}
85
+ rowIdx={idx}
86
+ focused={$focusedCellId === cellId}
87
+ selectedUser={$selectedCellMap[cellId]}
88
+ width={$stickyColumn.width}
89
+ column={$stickyColumn}
90
+ contentLines={$contentLines}
91
+ />
92
+ {/if}
93
+ </div>
94
+ {/each}
95
+ {#if $config.allowAddRows && ($renderedColumns.length || $stickyColumn)}
96
+ <div
97
+ class="row new"
98
+ on:mouseenter={() => ($hoveredRowId = BlankRowID)}
99
+ on:mouseleave={() => ($hoveredRowId = null)}
100
+ on:click={() => dispatch("add-row-inline")}
101
+ >
102
+ <GutterCell disableExpand rowHovered={$hoveredRowId === BlankRowID}>
103
+ <Icon name="Add" color="var(--spectrum-global-color-gray-500)" />
104
+ </GutterCell>
105
+ {#if $stickyColumn}
106
+ <GridCell
107
+ width={$stickyColumn.width}
108
+ highlighted={$hoveredRowId === BlankRowID}
109
+ >
110
+ <KeyboardShortcut padded keybind="Ctrl+Enter" />
111
+ </GridCell>
112
+ {/if}
113
+ </div>
114
+ {/if}
115
+ </GridScrollWrapper>
116
+ </div>
117
+ </div>
118
+
119
+ <style>
120
+ .sticky-column {
121
+ display: flex;
122
+ flex-direction: column;
123
+ position: relative;
124
+ z-index: 2;
125
+ background: var(--cell-background);
126
+ }
127
+
128
+ /* Add right border */
129
+ .sticky-column:before {
130
+ position: absolute;
131
+ content: "";
132
+ width: 0;
133
+ height: 100%;
134
+ left: calc(100% - 1px);
135
+ top: 0;
136
+ border-left: var(--cell-border);
137
+ }
138
+
139
+ /* Add shadow when scrolled */
140
+ .sticky-column.scrolled:after {
141
+ position: absolute;
142
+ content: "";
143
+ width: 8px;
144
+ height: 100%;
145
+ left: 100%;
146
+ top: 0;
147
+ opacity: 1;
148
+ background: linear-gradient(to right, var(--drop-shadow), transparent);
149
+ z-index: -1;
150
+ }
151
+
152
+ /* Don't show borders between cells in the sticky column */
153
+ .sticky-column :global(.cell:not(:last-child)) {
154
+ border-right: none;
155
+ }
156
+
157
+ .header {
158
+ z-index: 1;
159
+ }
160
+ .header :global(.cell) {
161
+ background: var(--spectrum-global-color-gray-100);
162
+ }
163
+ .row {
164
+ display: flex;
165
+ flex-direction: row;
166
+ justify-content: flex-start;
167
+ align-items: stretch;
168
+ }
169
+ .content {
170
+ position: relative;
171
+ flex: 1 1 auto;
172
+ }
173
+ .row.new :global(*:hover) {
174
+ cursor: pointer;
175
+ }
176
+ </style>
@@ -0,0 +1,20 @@
1
+ <script>
2
+ import { getContext } from "svelte"
3
+ import Avatar from "./Avatar.svelte"
4
+
5
+ const { users } = getContext("grid")
6
+ </script>
7
+
8
+ <div class="users">
9
+ {#each $users as user}
10
+ <Avatar {user} />
11
+ {/each}
12
+ </div>
13
+
14
+ <style>
15
+ .users {
16
+ display: flex;
17
+ flex-direction: row;
18
+ gap: 8px;
19
+ }
20
+ </style>
@@ -0,0 +1,14 @@
1
+ export const Padding = 128
2
+ export const MaxCellRenderHeight = 252
3
+ export const MaxCellRenderWidthOverflow = 200
4
+ export const ScrollBarSize = 8
5
+ export const GutterWidth = 72
6
+ export const DefaultColumnWidth = 200
7
+ export const MinColumnWidth = 80
8
+ export const SmallRowHeight = 36
9
+ export const MediumRowHeight = 64
10
+ export const LargeRowHeight = 92
11
+ export const DefaultRowHeight = SmallRowHeight
12
+ export const NewRowID = "new"
13
+ export const BlankRowID = "blank"
14
+ export const RowPageSize = 100
@@ -0,0 +1,29 @@
1
+ import { createEventDispatcher } from "svelte"
2
+
3
+ export const createEventManagers = () => {
4
+ const svelteDispatch = createEventDispatcher()
5
+ let subscribers = {}
6
+
7
+ // Dispatches an event, notifying subscribers and also emitting a normal
8
+ // svelte event
9
+ const dispatch = (event, payload) => {
10
+ svelteDispatch(event, payload)
11
+ const subs = subscribers[event] || []
12
+ for (let i = 0; i < subs.length; i++) {
13
+ subs[i](payload)
14
+ }
15
+ }
16
+
17
+ // Subscribes to events
18
+ const subscribe = (event, callback) => {
19
+ const subs = subscribers[event] || []
20
+ subscribers[event] = [...subs, callback]
21
+
22
+ // Return unsubscribe function
23
+ return () => {
24
+ subscribers[event] = subscribers[event].filter(cb => cb !== callback)
25
+ }
26
+ }
27
+
28
+ return { dispatch, subscribe }
29
+ }
@@ -0,0 +1,29 @@
1
+ import OptionsCell from "../cells/OptionsCell.svelte"
2
+ import DateCell from "../cells/DateCell.svelte"
3
+ import MultiSelectCell from "../cells/MultiSelectCell.svelte"
4
+ import NumberCell from "../cells/NumberCell.svelte"
5
+ import RelationshipCell from "../cells/RelationshipCell.svelte"
6
+ import TextCell from "../cells/TextCell.svelte"
7
+ import LongFormCell from "../cells/LongFormCell.svelte"
8
+ import BooleanCell from "../cells/BooleanCell.svelte"
9
+ import FormulaCell from "../cells/FormulaCell.svelte"
10
+ import JSONCell from "../cells/JSONCell.svelte"
11
+ import AttachmentCell from "../cells/AttachmentCell.svelte"
12
+
13
+ const TypeComponentMap = {
14
+ text: TextCell,
15
+ options: OptionsCell,
16
+ datetime: DateCell,
17
+ barcodeqr: TextCell,
18
+ longform: LongFormCell,
19
+ array: MultiSelectCell,
20
+ number: NumberCell,
21
+ boolean: BooleanCell,
22
+ attachment: AttachmentCell,
23
+ link: RelationshipCell,
24
+ formula: FormulaCell,
25
+ json: JSONCell,
26
+ }
27
+ export const getCellRenderer = column => {
28
+ return TypeComponentMap[column?.schema?.type] || TextCell
29
+ }
@@ -0,0 +1,26 @@
1
+ export const getColor = (idx, opacity = 0.3) => {
2
+ if (idx == null || idx === -1) {
3
+ return null
4
+ }
5
+ return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, ${opacity})`
6
+ }
7
+
8
+ const TypeIconMap = {
9
+ text: "Text",
10
+ options: "Dropdown",
11
+ datetime: "Date",
12
+ barcodeqr: "Camera",
13
+ longform: "TextAlignLeft",
14
+ array: "Dropdown",
15
+ number: "123",
16
+ boolean: "Boolean",
17
+ attachment: "AppleFiles",
18
+ link: "DataCorrelated",
19
+ formula: "Calculator",
20
+ json: "Brackets",
21
+ }
22
+
23
+ export const getColumnIcon = column => {
24
+ const type = column.schema.type
25
+ return TypeIconMap[type] || "Text"
26
+ }
@@ -0,0 +1,63 @@
1
+ import { get } from "svelte/store"
2
+ import { io } from "socket.io-client"
3
+
4
+ export const createWebsocket = context => {
5
+ const { rows, tableId, users, userId, focusedCellId } = context
6
+
7
+ // Determine connection info
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
+ })
22
+
23
+ const connectToTable = tableId => {
24
+ if (!socket.connected) {
25
+ return
26
+ }
27
+ // Identify which table we are editing
28
+ socket.emit("select-table", tableId, response => {
29
+ // handle initial connection info
30
+ users.set(response.users)
31
+ userId.set(response.id)
32
+ })
33
+ }
34
+
35
+ // Event handlers
36
+ socket.on("connect", () => {
37
+ connectToTable(get(tableId))
38
+ })
39
+ socket.on("row-update", data => {
40
+ if (data.id) {
41
+ rows.actions.refreshRow(data.id)
42
+ }
43
+ })
44
+ socket.on("user-update", user => {
45
+ users.actions.updateUser(user)
46
+ })
47
+ socket.on("user-disconnect", user => {
48
+ users.actions.removeUser(user)
49
+ })
50
+ socket.on("connect_error", err => {
51
+ console.log("Failed to connect to grid websocket:", err.message)
52
+ })
53
+
54
+ // Change websocket connection when table changes
55
+ tableId.subscribe(connectToTable)
56
+
57
+ // Notify selected cell changes
58
+ focusedCellId.subscribe($focusedCellId => {
59
+ socket.emit("select-cell", $focusedCellId)
60
+ })
61
+
62
+ return () => socket?.disconnect()
63
+ }