@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 +4 -4
- package/src/api/app.js +9 -0
- package/src/api/index.js +2 -0
- package/src/api/logs.js +14 -0
- package/src/components/UserAvatar.svelte +9 -44
- package/src/components/UserAvatars.svelte +67 -0
- package/src/components/grid/cells/HeaderCell.svelte +83 -49
- package/src/components/grid/cells/OptionsCell.svelte +3 -2
- package/src/components/grid/cells/RelationshipCell.svelte +1 -1
- package/src/components/grid/layout/Grid.svelte +15 -3
- package/src/components/grid/layout/GridBody.svelte +2 -2
- package/src/components/grid/layout/HeaderRow.svelte +19 -41
- package/src/components/grid/layout/NewColumnButton.svelte +79 -0
- package/src/components/grid/layout/NewRow.svelte +20 -12
- package/src/components/grid/layout/StickyColumn.svelte +5 -4
- package/src/components/grid/overlays/KeyboardManager.svelte +3 -2
- package/src/components/grid/overlays/MenuOverlay.svelte +2 -1
- package/src/components/grid/overlays/ResizeOverlay.svelte +2 -9
- package/src/components/grid/stores/columns.js +16 -0
- package/src/components/grid/stores/ui.js +11 -0
- package/src/components/grid/stores/users.js +3 -2
- package/src/components/index.js +1 -0
- package/src/utils/download.js +23 -0
- package/src/utils/index.js +1 -1
- package/src/components/grid/controls/AddColumnButton.svelte +0 -16
- package/src/components/grid/controls/AddRowButton.svelte +0 -18
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "2.8.
|
|
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.
|
|
10
|
-
"@budibase/shared-core": "2.8.
|
|
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": "
|
|
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
|
}
|
package/src/api/logs.js
ADDED
|
@@ -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,
|
|
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
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
196
|
-
</
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 $
|
|
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 {
|
|
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
|
-
|
|
17
|
-
(
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
{
|
|
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
|
-
|
|
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 $
|
|
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 ($
|
|
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 ($
|
|
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 || !$
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
33
|
+
const cellId = user.gridMetadata?.focusedCellId
|
|
34
|
+
if (cellId && cellId !== $focusedCellId) {
|
|
35
|
+
map[cellId] = user
|
|
35
36
|
}
|
|
36
37
|
})
|
|
37
38
|
return map
|
package/src/components/index.js
CHANGED
|
@@ -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"
|
package/src/utils/download.js
CHANGED
|
@@ -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
|
+
}
|
package/src/utils/index.js
CHANGED
|
@@ -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>
|