@budibase/frontend-core 2.8.31 → 2.8.32-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.
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@budibase/frontend-core",
3
- "version": "2.8.31",
3
+ "version": "2.8.32-alpha.1",
4
4
  "description": "Budibase frontend core libraries used in builder and client",
5
5
  "author": "Budibase",
6
6
  "license": "MPL-2.0",
7
7
  "svelte": "src/index.js",
8
8
  "dependencies": {
9
- "@budibase/bbui": "2.8.31",
10
- "@budibase/shared-core": "2.8.31",
9
+ "@budibase/bbui": "2.8.32-alpha.1",
10
+ "@budibase/shared-core": "2.8.32-alpha.1",
11
11
  "dayjs": "^1.11.7",
12
12
  "lodash": "^4.17.21",
13
13
  "socket.io-client": "^4.6.1",
14
14
  "svelte": "^3.46.2"
15
15
  },
16
- "gitHead": "4613b17c1e76328bf279998c821a17195fbaab37"
16
+ "gitHead": "4c316b05e31e14d417e69fbd143978e45eeba972"
17
17
  }
package/src/api/app.js CHANGED
@@ -123,6 +123,15 @@ export const buildAppEndpoints = API => ({
123
123
  })
124
124
  },
125
125
 
126
+ /**
127
+ * Gets budibase platform debug information.
128
+ */
129
+ fetchSystemDebugInfo: async () => {
130
+ return await API.get({
131
+ url: `/api/debug/diagnostics`,
132
+ })
133
+ },
134
+
126
135
  /**
127
136
  * Syncs an app with the production database.
128
137
  * @param appId the ID of the app to sync
package/src/api/index.js CHANGED
@@ -30,6 +30,7 @@ import { buildBackupsEndpoints } from "./backups"
30
30
  import { buildEnvironmentVariableEndpoints } from "./environmentVariables"
31
31
  import { buildEventEndpoints } from "./events"
32
32
  import { buildAuditLogsEndpoints } from "./auditLogs"
33
+ import { buildLogsEndpoints } from "./logs"
33
34
 
34
35
  /**
35
36
  * Random identifier to uniquely identify a session in a tab. This is
@@ -277,5 +278,6 @@ export const createAPIClient = config => {
277
278
  ...buildEnvironmentVariableEndpoints(API),
278
279
  ...buildEventEndpoints(API),
279
280
  ...buildAuditLogsEndpoints(API),
281
+ ...buildLogsEndpoints(API),
280
282
  }
281
283
  }
@@ -0,0 +1,14 @@
1
+ export const buildLogsEndpoints = API => ({
2
+ /**
3
+ * Gets a stream for the system logs.
4
+ */
5
+ getSystemLogs: async () => {
6
+ return await API.get({
7
+ url: "/api/system/logs",
8
+ json: false,
9
+ parseResponse: async response => {
10
+ return response
11
+ },
12
+ })
13
+ },
14
+ })
@@ -1,58 +1,23 @@
1
1
  <script>
2
- import { Avatar, Tooltip } from "@budibase/bbui"
2
+ import { Avatar, AbsTooltip, TooltipPosition } from "@budibase/bbui"
3
3
  import { helpers } from "@budibase/shared-core"
4
4
 
5
5
  export let user
6
- export let size
7
- export let tooltipDirection = "top"
6
+ export let size = "S"
7
+ export let tooltipPosition = TooltipPosition.Top
8
8
  export let showTooltip = true
9
-
10
- $: tooltipStyle = getTooltipStyle(tooltipDirection)
11
-
12
- const getTooltipStyle = direction => {
13
- if (!direction) {
14
- return ""
15
- }
16
- if (direction === "top") {
17
- return "transform: translateX(-50%) translateY(-100%);"
18
- } else if (direction === "bottom") {
19
- return "transform: translateX(-50%) translateY(100%);"
20
- }
21
- }
22
9
  </script>
23
10
 
24
11
  {#if user}
25
- <div class="user-avatar">
12
+ <AbsTooltip
13
+ text={showTooltip ? helpers.getUserLabel(user) : null}
14
+ position={tooltipPosition}
15
+ color={helpers.getUserColor(user)}
16
+ >
26
17
  <Avatar
27
18
  {size}
28
19
  initials={helpers.getUserInitials(user)}
29
20
  color={helpers.getUserColor(user)}
30
21
  />
31
- {#if showTooltip}
32
- <div class="tooltip" style={tooltipStyle}>
33
- <Tooltip
34
- direction={tooltipDirection}
35
- textWrapping
36
- text={user.email}
37
- size="S"
38
- />
39
- </div>
40
- {/if}
41
- </div>
22
+ </AbsTooltip>
42
23
  {/if}
43
-
44
- <style>
45
- .user-avatar {
46
- position: relative;
47
- }
48
- .tooltip {
49
- display: none;
50
- position: absolute;
51
- top: 0;
52
- left: 50%;
53
- white-space: nowrap;
54
- }
55
- .user-avatar:hover .tooltip {
56
- display: block;
57
- }
58
- </style>
@@ -0,0 +1,67 @@
1
+ <script>
2
+ import { UserAvatar } from "@budibase/frontend-core"
3
+ import { TooltipPosition, Avatar } from "@budibase/bbui"
4
+
5
+ export let users = []
6
+ export let order = "ltr"
7
+ export let size = "S"
8
+ export let tooltipPosition = TooltipPosition.Top
9
+
10
+ $: uniqueUsers = unique(users, order)
11
+ $: avatars = getAvatars(uniqueUsers, order)
12
+
13
+ const unique = users => {
14
+ let uniqueUsers = {}
15
+ users?.forEach(user => {
16
+ uniqueUsers[user.email] = user
17
+ })
18
+ return Object.values(uniqueUsers)
19
+ }
20
+
21
+ const getAvatars = (users, order) => {
22
+ const avatars = users.slice(0, 3)
23
+ if (users.length > 3) {
24
+ const overflow = {
25
+ _id: "overflow",
26
+ label: `+${users.length - 3}`,
27
+ }
28
+ if (order === "ltr") {
29
+ avatars.push(overflow)
30
+ } else {
31
+ avatars.unshift(overflow)
32
+ }
33
+ }
34
+ return avatars.map((user, idx) => ({
35
+ ...user,
36
+ zIndex: order === "ltr" ? idx : uniqueUsers.length - idx,
37
+ }))
38
+ }
39
+ </script>
40
+
41
+ <div class="avatars">
42
+ {#each avatars as user}
43
+ <span style="z-index:{user.zIndex};">
44
+ {#if user._id === "overflow"}
45
+ <Avatar
46
+ {size}
47
+ initials={user.label}
48
+ color="var(--spectrum-global-color-gray-500)"
49
+ />
50
+ {:else}
51
+ <UserAvatar {size} {user} {tooltipPosition} />
52
+ {/if}
53
+ </span>
54
+ {/each}
55
+ </div>
56
+
57
+ <style>
58
+ .avatars {
59
+ display: flex;
60
+ }
61
+ span:not(:first-of-type) {
62
+ margin-left: -6px;
63
+ }
64
+ .avatars :global(.spectrum-Avatar) {
65
+ border: 2px solid var(--avatars-background, var(--background));
66
+ }
67
+ </style>
@@ -1,7 +1,7 @@
1
1
  <script>
2
- import { getContext } from "svelte"
2
+ import { getContext, onMount, tick } from "svelte"
3
3
  import GridCell from "./GridCell.svelte"
4
- import { Icon, Popover, Menu, MenuItem } from "@budibase/bbui"
4
+ import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
5
5
  import { getColumnIcon } from "../lib/utils"
6
6
 
7
7
  export let column
@@ -16,6 +16,7 @@
16
16
  sort,
17
17
  renderedColumns,
18
18
  dispatch,
19
+ subscribe,
19
20
  config,
20
21
  ui,
21
22
  columns,
@@ -26,13 +27,14 @@
26
27
  "array",
27
28
  "attachment",
28
29
  "boolean",
29
- "formula",
30
30
  "json",
31
31
  ]
32
32
 
33
33
  let anchor
34
34
  let open = false
35
+ let editIsOpen = false
35
36
  let timeout
37
+ let popover
36
38
 
37
39
  $: sortedBy = column.name === $sort.column
38
40
  $: canMoveLeft = orderable && idx > 0
@@ -44,11 +46,16 @@
44
46
  ? "high-low"
45
47
  : "Z-A"
46
48
 
47
- const editColumn = () => {
49
+ const editColumn = async () => {
50
+ editIsOpen = true
51
+ await tick()
48
52
  dispatch("edit-column", column.schema)
49
- open = false
50
53
  }
51
54
 
55
+ const cancelEdit = () => {
56
+ popover.hide()
57
+ editIsOpen = false
58
+ }
52
59
  const onMouseDown = e => {
53
60
  if (e.button === 0 && orderable) {
54
61
  timeout = setTimeout(() => {
@@ -109,6 +116,7 @@
109
116
  columns.actions.saveChanges()
110
117
  open = false
111
118
  }
119
+ onMount(() => subscribe("close-edit-column", cancelEdit))
112
120
  </script>
113
121
 
114
122
  <div
@@ -157,57 +165,74 @@
157
165
 
158
166
  <Popover
159
167
  bind:open
168
+ bind:this={popover}
160
169
  {anchor}
161
170
  align="right"
162
171
  offset={0}
163
172
  popoverTarget={document.getElementById(`grid-${rand}`)}
164
173
  animate={false}
174
+ customZindex={100}
165
175
  >
166
- <Menu>
167
- <MenuItem
168
- icon="Edit"
169
- on:click={editColumn}
170
- disabled={!$config.allowSchemaChanges || column.schema.disabled}
171
- >
172
- Edit column
173
- </MenuItem>
174
- <MenuItem
175
- icon="Label"
176
- on:click={makeDisplayColumn}
177
- disabled={idx === "sticky" ||
178
- !$config.allowSchemaChanges ||
179
- bannedDisplayColumnTypes.includes(column.schema.type)}
180
- >
181
- Use as display column
182
- </MenuItem>
183
- <MenuItem
184
- icon="SortOrderUp"
185
- on:click={sortAscending}
186
- disabled={column.name === $sort.column && $sort.order === "ascending"}
187
- >
188
- Sort {ascendingLabel}
189
- </MenuItem>
190
- <MenuItem
191
- icon="SortOrderDown"
192
- on:click={sortDescending}
193
- disabled={column.name === $sort.column && $sort.order === "descending"}
176
+ {#if editIsOpen}
177
+ <div
178
+ use:clickOutside={() => {
179
+ editIsOpen = false
180
+ }}
181
+ class="content"
194
182
  >
195
- Sort {descendingLabel}
196
- </MenuItem>
197
- <MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
198
- Move left
199
- </MenuItem>
200
- <MenuItem disabled={!canMoveRight} icon="ChevronRight" on:click={moveRight}>
201
- Move right
202
- </MenuItem>
203
- <MenuItem
204
- disabled={idx === "sticky" || !$config.showControls}
205
- icon="VisibilityOff"
206
- on:click={hideColumn}
207
- >
208
- Hide column
209
- </MenuItem>
210
- </Menu>
183
+ <slot />
184
+ </div>
185
+ {:else}
186
+ <Menu>
187
+ <MenuItem
188
+ icon="Edit"
189
+ on:click={editColumn}
190
+ disabled={!$config.allowSchemaChanges || column.schema.disabled}
191
+ >
192
+ Edit column
193
+ </MenuItem>
194
+ <MenuItem
195
+ icon="Label"
196
+ on:click={makeDisplayColumn}
197
+ disabled={idx === "sticky" ||
198
+ !$config.allowSchemaChanges ||
199
+ bannedDisplayColumnTypes.includes(column.schema.type)}
200
+ >
201
+ Use as display column
202
+ </MenuItem>
203
+ <MenuItem
204
+ icon="SortOrderUp"
205
+ on:click={sortAscending}
206
+ disabled={column.name === $sort.column && $sort.order === "ascending"}
207
+ >
208
+ Sort {ascendingLabel}
209
+ </MenuItem>
210
+ <MenuItem
211
+ icon="SortOrderDown"
212
+ on:click={sortDescending}
213
+ disabled={column.name === $sort.column && $sort.order === "descending"}
214
+ >
215
+ Sort {descendingLabel}
216
+ </MenuItem>
217
+ <MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
218
+ Move left
219
+ </MenuItem>
220
+ <MenuItem
221
+ disabled={!canMoveRight}
222
+ icon="ChevronRight"
223
+ on:click={moveRight}
224
+ >
225
+ Move right
226
+ </MenuItem>
227
+ <MenuItem
228
+ disabled={idx === "sticky" || !$config.showControls}
229
+ icon="VisibilityOff"
230
+ on:click={hideColumn}
231
+ >
232
+ Hide column
233
+ </MenuItem>
234
+ </Menu>
235
+ {/if}
211
236
  </Popover>
212
237
 
213
238
  <style>
@@ -255,4 +280,13 @@
255
280
  .header-cell:hover .sort-indicator {
256
281
  display: none;
257
282
  }
283
+
284
+ .content {
285
+ width: 300px;
286
+ padding: 20px;
287
+ display: flex;
288
+ flex-direction: column;
289
+ gap: 20px;
290
+ background: var(--spectrum-alias-background-color-secondary);
291
+ }
258
292
  </style>
@@ -18,6 +18,7 @@
18
18
  let focusedOptionIdx = null
19
19
 
20
20
  $: options = schema?.constraints?.inclusion || []
21
+ $: optionColors = schema?.optionColors || {}
21
22
  $: editable = focused && !readonly
22
23
  $: values = Array.isArray(value) ? value : [value].filter(x => x != null)
23
24
  $: {
@@ -93,7 +94,7 @@
93
94
  on:click={editable ? open : null}
94
95
  >
95
96
  {#each values as val}
96
- {@const color = getOptionColor(val)}
97
+ {@const color = optionColors[val] || getOptionColor(val)}
97
98
  {#if color}
98
99
  <div class="badge text" style="--color: {color}">
99
100
  <span>
@@ -121,7 +122,7 @@
121
122
  use:clickOutside={close}
122
123
  >
123
124
  {#each options as option, idx}
124
- {@const color = getOptionColor(option)}
125
+ {@const color = optionColors[option] || getOptionColor(option)}
125
126
  <div
126
127
  class="option"
127
128
  on:click={() => toggleOption(option)}
@@ -258,7 +258,7 @@
258
258
  class:wrap={editable || contentLines > 1}
259
259
  on:wheel={e => (focused ? e.stopPropagation() : null)}
260
260
  >
261
- {#each value || [] as relationship, idx}
261
+ {#each value || [] as relationship}
262
262
  {#if relationship.primaryDisplay}
263
263
  <div class="badge">
264
264
  <span
@@ -71,6 +71,7 @@
71
71
  contentLines,
72
72
  gridFocused,
73
73
  error,
74
+ canAddRows,
74
75
  } = context
75
76
 
76
77
  // Keep config store up to date with props
@@ -138,12 +139,23 @@
138
139
  {#if $loaded}
139
140
  <div class="grid-data-outer" use:clickOutside={ui.actions.blur}>
140
141
  <div class="grid-data-inner">
141
- <StickyColumn />
142
+ <StickyColumn>
143
+ <svelte:fragment slot="edit-column">
144
+ <slot name="edit-column" />
145
+ </svelte:fragment>
146
+ </StickyColumn>
142
147
  <div class="grid-data-content">
143
- <HeaderRow />
148
+ <HeaderRow>
149
+ <svelte:fragment slot="add-column">
150
+ <slot name="add-column" />
151
+ </svelte:fragment>
152
+ <svelte:fragment slot="edit-column">
153
+ <slot name="edit-column" />
154
+ </svelte:fragment>
155
+ </HeaderRow>
144
156
  <GridBody />
145
157
  </div>
146
- {#if allowAddRows}
158
+ {#if $canAddRows}
147
159
  <NewRow />
148
160
  {/if}
149
161
  <div class="overlays">
@@ -9,7 +9,7 @@
9
9
  renderedRows,
10
10
  renderedColumns,
11
11
  rowVerticalInversionIndex,
12
- config,
12
+ canAddRows,
13
13
  hoveredRowId,
14
14
  dispatch,
15
15
  isDragging,
@@ -43,7 +43,7 @@
43
43
  invertY={idx >= $rowVerticalInversionIndex}
44
44
  />
45
45
  {/each}
46
- {#if $config.allowAddRows && $renderedColumns.length}
46
+ {#if $canAddRows}
47
47
  <div
48
48
  class="blank"
49
49
  class:highlighted={$hoveredRowId === BlankRowID}
@@ -1,42 +1,37 @@
1
1
  <script>
2
+ import NewColumnButton from "./NewColumnButton.svelte"
3
+
2
4
  import { getContext } from "svelte"
3
5
  import GridScrollWrapper from "./GridScrollWrapper.svelte"
4
6
  import HeaderCell from "../cells/HeaderCell.svelte"
5
- import { Icon } from "@budibase/bbui"
6
-
7
- const {
8
- renderedColumns,
9
- dispatch,
10
- scroll,
11
- hiddenColumnsWidth,
12
- width,
13
- config,
14
- } = getContext("grid")
7
+ import { TempTooltip, TooltipType } from "@budibase/bbui"
15
8
 
16
- $: columnsWidth = $renderedColumns.reduce(
17
- (total, col) => (total += col.width),
18
- 0
19
- )
20
- $: end = $hiddenColumnsWidth + columnsWidth - 1 - $scroll.left
21
- $: left = Math.min($width - 40, end)
9
+ const { renderedColumns, config, hasNonAutoColumn, tableId, loading } =
10
+ getContext("grid")
22
11
  </script>
23
12
 
24
13
  <div class="header">
25
14
  <GridScrollWrapper scrollHorizontally>
26
15
  <div class="row">
27
16
  {#each $renderedColumns as column, idx}
28
- <HeaderCell {column} {idx} />
17
+ <HeaderCell {column} {idx}>
18
+ <slot name="edit-column" />
19
+ </HeaderCell>
29
20
  {/each}
30
21
  </div>
31
22
  </GridScrollWrapper>
32
23
  {#if $config.allowSchemaChanges}
33
- <div
34
- class="add"
35
- style="left:{left}px"
36
- on:click={() => dispatch("add-column")}
37
- >
38
- <Icon name="Add" />
39
- </div>
24
+ {#key $tableId}
25
+ <TempTooltip
26
+ text="Click here to create your first column"
27
+ type={TooltipType.Info}
28
+ condition={!$hasNonAutoColumn && !$loading}
29
+ >
30
+ <NewColumnButton>
31
+ <slot name="add-column" />
32
+ </NewColumnButton>
33
+ </TempTooltip>
34
+ {/key}
40
35
  {/if}
41
36
  </div>
42
37
 
@@ -50,21 +45,4 @@
50
45
  .row {
51
46
  display: flex;
52
47
  }
53
- .add {
54
- height: var(--default-row-height);
55
- display: grid;
56
- place-items: center;
57
- width: 40px;
58
- position: absolute;
59
- top: 0;
60
- border-left: var(--cell-border);
61
- border-right: var(--cell-border);
62
- border-bottom: var(--cell-border);
63
- background: var(--grid-background-alt);
64
- z-index: 1;
65
- }
66
- .add:hover {
67
- background: var(--spectrum-global-color-gray-200);
68
- cursor: pointer;
69
- }
70
48
  </style>
@@ -0,0 +1,79 @@
1
+ <script>
2
+ import { getContext, onMount } from "svelte"
3
+ import { Icon, Popover, clickOutside } from "@budibase/bbui"
4
+
5
+ const { renderedColumns, scroll, hiddenColumnsWidth, width, subscribe } =
6
+ getContext("grid")
7
+
8
+ let anchor
9
+ let open = false
10
+ $: columnsWidth = $renderedColumns.reduce(
11
+ (total, col) => (total += col.width),
12
+ 0
13
+ )
14
+ $: end = $hiddenColumnsWidth + columnsWidth - 1 - $scroll.left
15
+ $: left = Math.min($width - 40, end)
16
+
17
+ const close = () => {
18
+ open = false
19
+ }
20
+ onMount(() => subscribe("close-edit-column", close))
21
+ </script>
22
+
23
+ <div
24
+ id="add-column-button"
25
+ bind:this={anchor}
26
+ class="add"
27
+ style="left:{left}px"
28
+ on:click={() => (open = true)}
29
+ >
30
+ <Icon name="Add" />
31
+ </div>
32
+ <Popover
33
+ bind:open
34
+ {anchor}
35
+ align="right"
36
+ offset={0}
37
+ popoverTarget={document.getElementById(`add-column-button`)}
38
+ animate={false}
39
+ customZindex={100}
40
+ >
41
+ <div
42
+ use:clickOutside={() => {
43
+ open = false
44
+ }}
45
+ class="content"
46
+ >
47
+ <slot />
48
+ </div>
49
+ </Popover>
50
+
51
+ <style>
52
+ .add {
53
+ height: var(--default-row-height);
54
+ display: grid;
55
+ place-items: center;
56
+ width: 40px;
57
+ position: absolute;
58
+ top: 0;
59
+ border-left: var(--cell-border);
60
+ border-right: var(--cell-border);
61
+ border-bottom: var(--cell-border);
62
+ background: var(--grid-background-alt);
63
+ z-index: 1;
64
+ }
65
+ .add:hover {
66
+ background: var(--spectrum-global-color-gray-200);
67
+ cursor: pointer;
68
+ }
69
+
70
+ .content {
71
+ width: 300px;
72
+ padding: 20px;
73
+ display: flex;
74
+ flex-direction: column;
75
+ gap: 20px;
76
+ z-index: 2;
77
+ background: var(--spectrum-alias-background-color-secondary);
78
+ }
79
+ </style>
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  import { getContext, onDestroy, onMount, tick } from "svelte"
3
- import { Icon, Button } from "@budibase/bbui"
3
+ import { Icon, Button, TempTooltip, TooltipType } from "@budibase/bbui"
4
4
  import GridScrollWrapper from "./GridScrollWrapper.svelte"
5
5
  import DataCell from "../cells/DataCell.svelte"
6
6
  import { fade } from "svelte/transition"
@@ -27,7 +27,8 @@
27
27
  rowVerticalInversionIndex,
28
28
  columnHorizontalInversionIndex,
29
29
  selectedRows,
30
- config,
30
+ loading,
31
+ canAddRows,
31
32
  } = getContext("grid")
32
33
 
33
34
  let visible = false
@@ -40,6 +41,7 @@
40
41
  $: $tableId, (visible = false)
41
42
  $: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows)
42
43
  $: selectedRowCount = Object.values($selectedRows).length
44
+ $: hasNoRows = !$rows.length
43
45
 
44
46
  const shouldInvertY = (offset, inversionIndex, rows) => {
45
47
  if (offset === 0) {
@@ -147,16 +149,22 @@
147
149
  </script>
148
150
 
149
151
  <!-- New row FAB -->
150
- {#if !visible && !selectedRowCount && $config.allowAddRows && firstColumn}
151
- <div
152
- class="new-row-fab"
153
- on:click={() => dispatch("add-row-inline")}
154
- transition:fade|local={{ duration: 130 }}
155
- class:offset={!$stickyColumn}
156
- >
157
- <Icon name="Add" size="S" />
158
- </div>
159
- {/if}
152
+ <TempTooltip
153
+ text="Click here to create your first row"
154
+ condition={hasNoRows && !$loading}
155
+ type={TooltipType.Info}
156
+ >
157
+ {#if !visible && !selectedRowCount && $canAddRows}
158
+ <div
159
+ class="new-row-fab"
160
+ on:click={() => dispatch("add-row-inline")}
161
+ transition:fade|local={{ duration: 130 }}
162
+ class:offset={!$stickyColumn}
163
+ >
164
+ <Icon name="Add" size="S" />
165
+ </div>
166
+ {/if}
167
+ </TempTooltip>
160
168
 
161
169
  <!-- Only show new row functionality if we have any columns -->
162
170
  {#if visible}
@@ -13,11 +13,10 @@
13
13
  rows,
14
14
  selectedRows,
15
15
  stickyColumn,
16
- renderedColumns,
17
16
  renderedRows,
18
17
  focusedCellId,
19
18
  hoveredRowId,
20
- config,
19
+ canAddRows,
21
20
  selectedCellMap,
22
21
  focusedRow,
23
22
  scrollLeft,
@@ -58,7 +57,9 @@
58
57
  disabled={!$renderedRows.length}
59
58
  />
60
59
  {#if $stickyColumn}
61
- <HeaderCell column={$stickyColumn} orderable={false} idx="sticky" />
60
+ <HeaderCell column={$stickyColumn} orderable={false} idx="sticky">
61
+ <slot name="edit-column" />
62
+ </HeaderCell>
62
63
  {/if}
63
64
  </div>
64
65
 
@@ -93,7 +94,7 @@
93
94
  {/if}
94
95
  </div>
95
96
  {/each}
96
- {#if $config.allowAddRows && ($renderedColumns.length || $stickyColumn)}
97
+ {#if $canAddRows}
97
98
  <div
98
99
  class="row new"
99
100
  on:mouseenter={$isDragging
@@ -16,6 +16,7 @@
16
16
  config,
17
17
  menu,
18
18
  gridFocused,
19
+ canAddRows,
19
20
  } = getContext("grid")
20
21
 
21
22
  const ignoredOriginSelectors = [
@@ -45,7 +46,7 @@
45
46
  e.preventDefault()
46
47
  focusFirstCell()
47
48
  } else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
48
- if ($config.allowAddRows) {
49
+ if ($canAddRows) {
49
50
  e.preventDefault()
50
51
  dispatch("add-row-inline")
51
52
  }
@@ -99,7 +100,7 @@
99
100
  }
100
101
  break
101
102
  case "Enter":
102
- if ($config.allowAddRows) {
103
+ if ($canAddRows) {
103
104
  dispatch("add-row-inline")
104
105
  }
105
106
  }
@@ -17,6 +17,7 @@
17
17
  focusedCellAPI,
18
18
  focusedRowId,
19
19
  notifications,
20
+ canAddRows,
20
21
  } = getContext("grid")
21
22
 
22
23
  $: style = makeStyle($menu)
@@ -93,7 +94,7 @@
93
94
  </MenuItem>
94
95
  <MenuItem
95
96
  icon="Duplicate"
96
- disabled={isNewRow || !$config.allowAddRows}
97
+ disabled={isNewRow || !$canAddRows}
97
98
  on:click={duplicate}
98
99
  >
99
100
  Duplicate row
@@ -2,16 +2,9 @@
2
2
  import { getContext } from "svelte"
3
3
  import { GutterWidth } from "../lib/constants"
4
4
 
5
- const {
6
- columns,
7
- resize,
8
- renderedColumns,
9
- stickyColumn,
10
- isReordering,
11
- scrollLeft,
12
- } = getContext("grid")
5
+ const { resize, renderedColumns, stickyColumn, isReordering, scrollLeft } =
6
+ getContext("grid")
13
7
 
14
- $: cutoff = $scrollLeft + GutterWidth + ($columns[0]?.width || 0)
15
8
  $: offset = GutterWidth + ($stickyColumn?.width || 0)
16
9
  $: activeColumn = $resize.column
17
10
 
@@ -83,6 +83,21 @@ export const deriveStores = context => {
83
83
  await saveChanges()
84
84
  }
85
85
 
86
+ // Derive if we have any normal columns
87
+ const hasNonAutoColumn = derived(
88
+ [columns, stickyColumn],
89
+ ([$columns, $stickyColumn]) => {
90
+ let allCols = $columns || []
91
+ if ($stickyColumn) {
92
+ allCols = [...allCols, $stickyColumn]
93
+ }
94
+ const normalCols = allCols.filter(column => {
95
+ return !column.schema?.autocolumn
96
+ })
97
+ return normalCols.length > 0
98
+ }
99
+ )
100
+
86
101
  // Persists column changes by saving metadata against table schema
87
102
  const saveChanges = async () => {
88
103
  const $columns = get(columns)
@@ -128,6 +143,7 @@ export const deriveStores = context => {
128
143
  }
129
144
 
130
145
  return {
146
+ hasNonAutoColumn,
131
147
  columns: {
132
148
  ...columns,
133
149
  actions: {
@@ -70,6 +70,8 @@ export const deriveStores = context => {
70
70
  rowHeight,
71
71
  stickyColumn,
72
72
  width,
73
+ hasNonAutoColumn,
74
+ config,
73
75
  } = context
74
76
 
75
77
  // Derive the row that contains the selected cell
@@ -112,7 +114,16 @@ export const deriveStores = context => {
112
114
  return ($stickyColumn?.width || 0) + $width + GutterWidth < 1100
113
115
  })
114
116
 
117
+ // Derive if we're able to add rows
118
+ const canAddRows = derived(
119
+ [config, hasNonAutoColumn],
120
+ ([$config, $hasNonAutoColumn]) => {
121
+ return $config.allowAddRows && $hasNonAutoColumn
122
+ }
123
+ )
124
+
115
125
  return {
126
+ canAddRows,
116
127
  focusedRow,
117
128
  contentLines,
118
129
  compact,
@@ -30,8 +30,9 @@ export const deriveStores = context => {
30
30
  ([$users, $focusedCellId]) => {
31
31
  let map = {}
32
32
  $users.forEach(user => {
33
- if (user.focusedCellId && user.focusedCellId !== $focusedCellId) {
34
- map[user.focusedCellId] = user
33
+ const cellId = user.gridMetadata?.focusedCellId
34
+ if (cellId && cellId !== $focusedCellId) {
35
+ map[cellId] = user
35
36
  }
36
37
  })
37
38
  return map
@@ -2,4 +2,5 @@ export { default as SplitPage } from "./SplitPage.svelte"
2
2
  export { default as TestimonialPage } from "./TestimonialPage.svelte"
3
3
  export { default as Testimonial } from "./Testimonial.svelte"
4
4
  export { default as UserAvatar } from "./UserAvatar.svelte"
5
+ export { default as UserAvatars } from "./UserAvatars.svelte"
5
6
  export { Grid } from "./grid"
@@ -11,3 +11,26 @@ export function downloadText(filename, text) {
11
11
 
12
12
  URL.revokeObjectURL(url)
13
13
  }
14
+
15
+ export async function downloadStream(streamResponse) {
16
+ const blob = await streamResponse.blob()
17
+
18
+ const contentDisposition = streamResponse.headers.get("Content-Disposition")
19
+
20
+ const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(
21
+ contentDisposition
22
+ )
23
+
24
+ const filename = matches[1].replace(/['"]/g, "")
25
+
26
+ const resBlob = new Blob([blob])
27
+
28
+ const blobUrl = URL.createObjectURL(resBlob)
29
+
30
+ const link = document.createElement("a")
31
+ link.href = blobUrl
32
+ link.download = filename
33
+ link.click()
34
+
35
+ URL.revokeObjectURL(blobUrl)
36
+ }
@@ -5,4 +5,4 @@ export * as RoleUtils from "./roles"
5
5
  export * as Utils from "./utils"
6
6
  export { memo, derivedMemo } from "./memo"
7
7
  export { createWebsocket } from "./websocket"
8
- export { downloadText } from "./download"
8
+ export * from "./download"
@@ -1,16 +0,0 @@
1
- <script>
2
- import { ActionButton } from "@budibase/bbui"
3
- import { getContext } from "svelte"
4
-
5
- const { config, dispatch } = getContext("grid")
6
- </script>
7
-
8
- <ActionButton
9
- icon="TableColumnAddRight"
10
- quiet
11
- size="M"
12
- on:click={() => dispatch("add-column")}
13
- disabled={!$config.allowSchemaChanges}
14
- >
15
- Add column
16
- </ActionButton>
@@ -1,18 +0,0 @@
1
- <script>
2
- import { ActionButton } from "@budibase/bbui"
3
- import { getContext } from "svelte"
4
-
5
- const { dispatch, columns, stickyColumn, config, loaded } = getContext("grid")
6
- </script>
7
-
8
- <ActionButton
9
- icon="TableRowAddBottom"
10
- quiet
11
- size="M"
12
- on:click={() => dispatch("add-row-inline")}
13
- disabled={!loaded ||
14
- !$config.allowAddRows ||
15
- (!$columns.length && !$stickyColumn)}
16
- >
17
- Add row
18
- </ActionButton>