@budibase/frontend-core 2.8.21 → 2.8.22-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.21",
3
+ "version": "2.8.22-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.21",
10
- "@budibase/shared-core": "2.8.21",
9
+ "@budibase/bbui": "2.8.22-alpha.1",
10
+ "@budibase/shared-core": "2.8.22-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": "26c531ec81a0bdfb5805f2c8a85d6bbcc5ac7d81"
16
+ "gitHead": "efcef8d6f8ce0744d1f80f8161a41f55ef6c8ea4"
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>
@@ -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
@@ -143,7 +144,7 @@
143
144
  <HeaderRow />
144
145
  <GridBody />
145
146
  </div>
146
- {#if allowAddRows}
147
+ {#if $canAddRows}
147
148
  <NewRow />
148
149
  {/if}
149
150
  <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}
@@ -2,7 +2,7 @@
2
2
  import { getContext } from "svelte"
3
3
  import GridScrollWrapper from "./GridScrollWrapper.svelte"
4
4
  import HeaderCell from "../cells/HeaderCell.svelte"
5
- import { Icon } from "@budibase/bbui"
5
+ import { Icon, TempTooltip, TooltipType } from "@budibase/bbui"
6
6
 
7
7
  const {
8
8
  renderedColumns,
@@ -11,10 +11,13 @@
11
11
  hiddenColumnsWidth,
12
12
  width,
13
13
  config,
14
+ hasNonAutoColumn,
15
+ tableId,
16
+ loading,
14
17
  } = getContext("grid")
15
18
 
16
19
  $: columnsWidth = $renderedColumns.reduce(
17
- (total, col) => (total += col.width),
20
+ (total, col) => total + col.width,
18
21
  0
19
22
  )
20
23
  $: end = $hiddenColumnsWidth + columnsWidth - 1 - $scroll.left
@@ -30,13 +33,21 @@
30
33
  </div>
31
34
  </GridScrollWrapper>
32
35
  {#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>
36
+ {#key $tableId}
37
+ <TempTooltip
38
+ text="Click here to create your first column"
39
+ type={TooltipType.Info}
40
+ condition={!$hasNonAutoColumn && !$loading}
41
+ >
42
+ <div
43
+ class="add"
44
+ style="left:{left}px;"
45
+ on:click={() => dispatch("add-column")}
46
+ >
47
+ <Icon name="Add" />
48
+ </div>
49
+ </TempTooltip>
50
+ {/key}
40
51
  {/if}
41
52
  </div>
42
53
 
@@ -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,
@@ -93,7 +92,7 @@
93
92
  {/if}
94
93
  </div>
95
94
  {/each}
96
- {#if $config.allowAddRows && ($renderedColumns.length || $stickyColumn)}
95
+ {#if $canAddRows}
97
96
  <div
98
97
  class="row new"
99
98
  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>