@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,265 @@
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
+
11
+ const {
12
+ hoveredRowId,
13
+ focusedCellId,
14
+ stickyColumn,
15
+ scroll,
16
+ dispatch,
17
+ rows,
18
+ focusedCellAPI,
19
+ tableId,
20
+ subscribe,
21
+ renderedRows,
22
+ renderedColumns,
23
+ rowHeight,
24
+ hasNextPage,
25
+ maxScrollTop,
26
+ rowVerticalInversionIndex,
27
+ columnHorizontalInversionIndex,
28
+ } = getContext("grid")
29
+
30
+ let isAdding = false
31
+ let newRow = {}
32
+ let offset = 0
33
+
34
+ $: firstColumn = $stickyColumn || $renderedColumns[0]
35
+ $: width = GutterWidth + ($stickyColumn?.width || 0)
36
+ $: $tableId, (isAdding = false)
37
+ $: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows)
38
+
39
+ const shouldInvertY = (offset, inversionIndex, rows) => {
40
+ if (offset === 0) {
41
+ return false
42
+ }
43
+ return rows.length >= inversionIndex
44
+ }
45
+
46
+ const addRow = async () => {
47
+ // Blur the active cell and tick to let final value updates propagate
48
+ $focusedCellAPI?.blur()
49
+ await tick()
50
+
51
+ // Create row
52
+ const newRowIndex = offset ? undefined : 0
53
+ const savedRow = await rows.actions.addRow(newRow, newRowIndex)
54
+ if (savedRow) {
55
+ // Reset state
56
+ clear()
57
+
58
+ // Select the first cell if possible
59
+ if (firstColumn) {
60
+ $focusedCellId = `${savedRow._id}-${firstColumn.name}`
61
+ }
62
+ }
63
+ }
64
+
65
+ const clear = () => {
66
+ isAdding = false
67
+ $focusedCellId = null
68
+ $hoveredRowId = null
69
+ document.removeEventListener("keydown", handleKeyPress)
70
+ }
71
+
72
+ const startAdding = async () => {
73
+ if (isAdding) {
74
+ return
75
+ }
76
+
77
+ // If we have a next page of data then we aren't truly at the bottom, so we
78
+ // render the add row component at the top
79
+ if ($hasNextPage) {
80
+ offset = 0
81
+ }
82
+
83
+ // If we don't have a next page then we're at the bottom and can scroll to
84
+ // the max available offset
85
+ else {
86
+ scroll.update(state => ({
87
+ ...state,
88
+ top: $maxScrollTop,
89
+ }))
90
+ offset = $renderedRows.length * $rowHeight - ($maxScrollTop % $rowHeight)
91
+ if ($renderedRows.length !== 0) {
92
+ offset -= 1
93
+ }
94
+ }
95
+
96
+ // Update state and select initial cell
97
+ newRow = {}
98
+ isAdding = true
99
+ $hoveredRowId = NewRowID
100
+ if (firstColumn) {
101
+ $focusedCellId = `${NewRowID}-${firstColumn.name}`
102
+ }
103
+
104
+ // Attach key listener
105
+ document.addEventListener("keydown", handleKeyPress)
106
+ }
107
+
108
+ const updateValue = (rowId, columnName, val) => {
109
+ newRow[columnName] = val
110
+ }
111
+
112
+ const addViaModal = () => {
113
+ clear()
114
+ dispatch("add-row")
115
+ }
116
+
117
+ const handleKeyPress = e => {
118
+ if (!isAdding) {
119
+ return
120
+ }
121
+ if (e.key === "Escape") {
122
+ // Only close the new row component if we aren't actively inside a cell
123
+ if (!$focusedCellAPI?.isActive()) {
124
+ e.preventDefault()
125
+ clear()
126
+ }
127
+ } else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
128
+ e.preventDefault()
129
+ addRow()
130
+ }
131
+ }
132
+
133
+ onMount(() => subscribe("add-row-inline", startAdding))
134
+ onDestroy(() => {
135
+ document.removeEventListener("keydown", handleKeyPress)
136
+ })
137
+ </script>
138
+
139
+ <!-- Only show new row functionality if we have any columns -->
140
+ {#if isAdding}
141
+ <div
142
+ class="container"
143
+ class:floating={offset > 0}
144
+ style="--offset:{offset}px; --sticky-width:{width}px;"
145
+ >
146
+ <div class="underlay sticky" transition:fade={{ duration: 130 }} />
147
+ <div class="underlay" transition:fade={{ duration: 130 }} />
148
+ <div class="sticky-column" transition:fade={{ duration: 130 }}>
149
+ <GutterCell on:expand={addViaModal} rowHovered>
150
+ <Icon name="Add" color="var(--spectrum-global-color-gray-500)" />
151
+ </GutterCell>
152
+ {#if $stickyColumn}
153
+ {@const cellId = `${NewRowID}-${$stickyColumn.name}`}
154
+ <DataCell
155
+ {cellId}
156
+ rowFocused
157
+ column={$stickyColumn}
158
+ row={newRow}
159
+ focused={$focusedCellId === cellId}
160
+ width={$stickyColumn.width}
161
+ {updateValue}
162
+ rowIdx={0}
163
+ {invertY}
164
+ />
165
+ {/if}
166
+ </div>
167
+ <div class="normal-columns" transition:fade={{ duration: 130 }}>
168
+ <GridScrollWrapper scrollHorizontally wheelInteractive>
169
+ <div class="row">
170
+ {#each $renderedColumns as column, columnIdx}
171
+ {@const cellId = `new-${column.name}`}
172
+ {#key cellId}
173
+ <DataCell
174
+ {cellId}
175
+ {column}
176
+ {updateValue}
177
+ rowFocused
178
+ row={newRow}
179
+ focused={$focusedCellId === cellId}
180
+ width={column.width}
181
+ rowIdx={0}
182
+ invertX={columnIdx >= $columnHorizontalInversionIndex}
183
+ {invertY}
184
+ />
185
+ {/key}
186
+ {/each}
187
+ </div>
188
+ </GridScrollWrapper>
189
+ </div>
190
+ <div class="buttons" transition:fade={{ duration: 130 }}>
191
+ <Button size="M" cta on:click={addRow}>Save</Button>
192
+ <Button size="M" secondary newStyles on:click={clear}>Cancel</Button>
193
+ </div>
194
+ </div>
195
+ {/if}
196
+
197
+ <style>
198
+ .container {
199
+ position: absolute;
200
+ top: var(--default-row-height);
201
+ left: 0;
202
+ width: 100%;
203
+ height: 100%;
204
+ display: flex;
205
+ flex-direction: row;
206
+ align-items: stretch;
207
+ }
208
+ .container :global(.cell) {
209
+ --cell-background: var(--spectrum-global-color-gray-75) !important;
210
+ }
211
+ .container.floating :global(.cell) {
212
+ height: calc(var(--row-height) + 1px);
213
+ border-top: var(--cell-border);
214
+ }
215
+
216
+ /* Underlay sits behind everything */
217
+ .underlay {
218
+ position: absolute;
219
+ content: "";
220
+ left: 0;
221
+ top: 0;
222
+ height: 100%;
223
+ width: 100%;
224
+ background: var(--cell-background);
225
+ opacity: 0.8;
226
+ }
227
+ .underlay.sticky {
228
+ z-index: 2;
229
+ width: var(--sticky-width);
230
+ }
231
+
232
+ /* Floating buttons which sit on top of the underlay but below the sticky column */
233
+ .buttons {
234
+ display: flex;
235
+ flex-direction: row;
236
+ gap: 8px;
237
+ pointer-events: all;
238
+ z-index: 3;
239
+ position: absolute;
240
+ top: calc(var(--row-height) + var(--offset) + 24px);
241
+ left: var(--gutter-width);
242
+ }
243
+
244
+ /* Sticky column styles */
245
+ .sticky-column {
246
+ display: flex;
247
+ z-index: 4;
248
+ position: relative;
249
+ align-self: flex-start;
250
+ flex: 0 0 var(--sticky-width);
251
+ }
252
+ .sticky-column :global(.cell:not(:last-child)) {
253
+ border-right: none;
254
+ }
255
+ .sticky-column,
256
+ .normal-columns {
257
+ margin-top: var(--offset);
258
+ }
259
+
260
+ /* Normal column styles */
261
+ .row {
262
+ width: 0;
263
+ display: flex;
264
+ }
265
+ </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,171 @@
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
+
11
+ const {
12
+ rows,
13
+ selectedRows,
14
+ stickyColumn,
15
+ renderedColumns,
16
+ renderedRows,
17
+ focusedCellId,
18
+ hoveredRowId,
19
+ config,
20
+ selectedCellMap,
21
+ focusedRow,
22
+ scrollLeft,
23
+ dispatch,
24
+ } = getContext("grid")
25
+
26
+ $: rowCount = $rows.length
27
+ $: selectedRowCount = Object.values($selectedRows).filter(x => !!x).length
28
+ $: width = GutterWidth + ($stickyColumn?.width || 0)
29
+
30
+ const selectAll = () => {
31
+ const allSelected = selectedRowCount === rowCount
32
+ if (allSelected) {
33
+ $selectedRows = {}
34
+ } else {
35
+ let allRows = {}
36
+ $rows.forEach(row => {
37
+ allRows[row._id] = true
38
+ })
39
+ $selectedRows = allRows
40
+ }
41
+ }
42
+ </script>
43
+
44
+ <div
45
+ class="sticky-column"
46
+ style="flex: 0 0 {width}px"
47
+ class:scrolled={$scrollLeft > 0}
48
+ >
49
+ <div class="header row">
50
+ <GutterCell
51
+ disableExpand
52
+ disableNumber
53
+ on:select={selectAll}
54
+ defaultHeight
55
+ rowSelected={selectedRowCount && selectedRowCount === rowCount}
56
+ disabled={!$renderedRows.length}
57
+ />
58
+ {#if $stickyColumn}
59
+ <HeaderCell column={$stickyColumn} orderable={false} idx="sticky" />
60
+ {/if}
61
+ </div>
62
+
63
+ <div class="content" on:mouseleave={() => ($hoveredRowId = null)}>
64
+ <GridScrollWrapper scrollVertically wheelInteractive>
65
+ {#each $renderedRows as row, idx}
66
+ {@const rowSelected = !!$selectedRows[row._id]}
67
+ {@const rowHovered = $hoveredRowId === row._id}
68
+ {@const rowFocused = $focusedRow?._id === row._id}
69
+ {@const cellId = `${row._id}-${$stickyColumn?.name}`}
70
+ <div
71
+ class="row"
72
+ on:mouseenter={() => ($hoveredRowId = row._id)}
73
+ on:mouseleave={() => ($hoveredRowId = null)}
74
+ >
75
+ <GutterCell {row} {rowFocused} {rowHovered} {rowSelected} />
76
+ {#if $stickyColumn}
77
+ <DataCell
78
+ {row}
79
+ {cellId}
80
+ {rowFocused}
81
+ selected={rowSelected}
82
+ highlighted={rowHovered || rowFocused}
83
+ rowIdx={idx}
84
+ focused={$focusedCellId === cellId}
85
+ selectedUser={$selectedCellMap[cellId]}
86
+ width={$stickyColumn.width}
87
+ column={$stickyColumn}
88
+ />
89
+ {/if}
90
+ </div>
91
+ {/each}
92
+ {#if $config.allowAddRows && ($renderedColumns.length || $stickyColumn)}
93
+ <div
94
+ class="row new"
95
+ on:mouseenter={() => ($hoveredRowId = BlankRowID)}
96
+ on:mouseleave={() => ($hoveredRowId = null)}
97
+ on:click={() => dispatch("add-row-inline")}
98
+ >
99
+ <GutterCell disableExpand rowHovered={$hoveredRowId === BlankRowID}>
100
+ <Icon name="Add" color="var(--spectrum-global-color-gray-500)" />
101
+ </GutterCell>
102
+ {#if $stickyColumn}
103
+ <GridCell
104
+ width={$stickyColumn.width}
105
+ highlighted={$hoveredRowId === BlankRowID}
106
+ />
107
+ {/if}
108
+ </div>
109
+ {/if}
110
+ </GridScrollWrapper>
111
+ </div>
112
+ </div>
113
+
114
+ <style>
115
+ .sticky-column {
116
+ display: flex;
117
+ flex-direction: column;
118
+ position: relative;
119
+ z-index: 2;
120
+ background: var(--cell-background);
121
+ }
122
+
123
+ /* Add right border */
124
+ .sticky-column:before {
125
+ position: absolute;
126
+ content: "";
127
+ width: 0;
128
+ height: 100%;
129
+ left: calc(100% - 1px);
130
+ top: 0;
131
+ border-left: var(--cell-border);
132
+ }
133
+
134
+ /* Add shadow when scrolled */
135
+ .sticky-column.scrolled:after {
136
+ position: absolute;
137
+ content: "";
138
+ width: 8px;
139
+ height: 100%;
140
+ left: 100%;
141
+ top: 0;
142
+ opacity: 1;
143
+ background: linear-gradient(to right, var(--drop-shadow), transparent);
144
+ z-index: -1;
145
+ }
146
+
147
+ /* Don't show borders between cells in the sticky column */
148
+ .sticky-column :global(.cell:not(:last-child)) {
149
+ border-right: none;
150
+ }
151
+
152
+ .header {
153
+ z-index: 1;
154
+ }
155
+ .header :global(.cell) {
156
+ background: var(--spectrum-global-color-gray-100);
157
+ }
158
+ .row {
159
+ display: flex;
160
+ flex-direction: row;
161
+ justify-content: flex-start;
162
+ align-items: stretch;
163
+ }
164
+ .content {
165
+ position: relative;
166
+ flex: 1 1 auto;
167
+ }
168
+ .row.new :global(*:hover) {
169
+ cursor: pointer;
170
+ }
171
+ </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
+ }