@budibase/frontend-core 3.15.0 → 3.16.0

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@budibase/frontend-core",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Budibase frontend core libraries used in builder and client",
5
5
  "author": "Budibase",
6
6
  "license": "MPL-2.0",
@@ -17,5 +17,5 @@
17
17
  "shortid": "2.2.15",
18
18
  "socket.io-client": "^4.7.5"
19
19
  },
20
- "gitHead": "d220e217e0f03ff770421191b5b49cf8268f007f"
20
+ "gitHead": "d20f1f44d10db8eb79d7b7e47c670f3fce502547"
21
21
  }
package/src/api/app.ts CHANGED
@@ -20,7 +20,6 @@ import {
20
20
  PublishAppResponse,
21
21
  RevertAppClientResponse,
22
22
  RevertAppResponse,
23
- SyncAppResponse,
24
23
  UnpublishAppResponse,
25
24
  UpdateAppClientResponse,
26
25
  UpdateAppRequest,
@@ -54,7 +53,6 @@ export interface AppEndpoints {
54
53
  body: ImportToUpdateAppRequest
55
54
  ) => Promise<ImportToUpdateAppResponse>
56
55
  fetchSystemDebugInfo: () => Promise<GetDiagnosticsResponse>
57
- syncApp: (appId: string) => Promise<SyncAppResponse>
58
56
  getApps: () => Promise<FetchAppsResponse>
59
57
  fetchComponentLibDefinitions: (
60
58
  appId: string
@@ -235,16 +233,6 @@ export const buildAppEndpoints = (API: BaseAPIClient): AppEndpoints => ({
235
233
  })
236
234
  },
237
235
 
238
- /**
239
- * Syncs an app with the production database.
240
- * @param appId the ID of the app to sync
241
- */
242
- syncApp: async appId => {
243
- return await API.post({
244
- url: `/api/applications/${appId}/sync`,
245
- })
246
- },
247
-
248
236
  /**
249
237
  * Gets a list of apps.
250
238
  */
@@ -4,6 +4,7 @@ import {
4
4
  SearchAppBackupsRequest,
5
5
  ClearBackupErrorRequest,
6
6
  ClearBackupErrorResponse,
7
+ DeleteAppBackupsResponse,
7
8
  } from "@budibase/types"
8
9
  import { BaseAPIClient } from "./types"
9
10
 
@@ -21,6 +22,10 @@ export interface BackupEndpoints {
21
22
  appId: string,
22
23
  backupId: string
23
24
  ) => Promise<{ message: string }>
25
+ deleteBackups: (
26
+ appId: string,
27
+ backupIds: string[]
28
+ ) => Promise<DeleteAppBackupsResponse>
24
29
  clearBackupErrors: (
25
30
  appId: string,
26
31
  backupId?: string
@@ -44,6 +49,12 @@ export const buildBackupEndpoints = (API: BaseAPIClient): BackupEndpoints => ({
44
49
  url: `/api/apps/${appId}/backups/${backupId}`,
45
50
  })
46
51
  },
52
+ deleteBackups: async (appId, backupIds) => {
53
+ return await API.delete({
54
+ url: `/api/apps/${appId}/backups`,
55
+ body: { backupIds },
56
+ })
57
+ },
47
58
  restoreBackup: async (appId, backupId, name) => {
48
59
  return await API.post({
49
60
  url: `/api/apps/${appId}/backups/${backupId}/import`,
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  FetchWorkspaceAppResponse,
3
+ FindWorkspaceAppResponse,
3
4
  InsertWorkspaceAppRequest,
4
5
  InsertWorkspaceAppResponse,
5
6
  UpdateWorkspaceAppRequest,
@@ -8,6 +9,7 @@ import {
8
9
  import { BaseAPIClient } from "./types"
9
10
 
10
11
  export interface WorkspaceAppEndpoints {
12
+ find: (id: string) => Promise<FindWorkspaceAppResponse>
11
13
  fetch: () => Promise<FetchWorkspaceAppResponse>
12
14
  create: (
13
15
  workspaceApp: InsertWorkspaceAppRequest
@@ -21,6 +23,11 @@ export interface WorkspaceAppEndpoints {
21
23
  export const buildWorkspaceAppEndpoints = (
22
24
  API: BaseAPIClient
23
25
  ): WorkspaceAppEndpoints => ({
26
+ find: async id => {
27
+ return await API.get({
28
+ url: `/api/workspaceApp/${id}`,
29
+ })
30
+ },
24
31
  fetch: async () => {
25
32
  return await API.get({
26
33
  url: "/api/workspaceApp",
@@ -359,23 +359,23 @@
359
359
  </div>
360
360
  {:else}
361
361
  <Menu>
362
- <MenuItem icon="pencil" on:click={editColumn} disabled={!editable}>
363
- Edit column
364
- </MenuItem>
365
- <MenuItem
366
- icon="copy"
367
- on:click={duplicateColumn}
368
- disabled={!$config.canEditColumns}
369
- >
370
- Duplicate column
371
- </MenuItem>
372
- <MenuItem
373
- icon="tag"
374
- on:click={makeDisplayColumn}
375
- disabled={column.primaryDisplay || !canBeDisplayColumn(column.schema)}
376
- >
377
- Use as display column
378
- </MenuItem>
362
+ {#if $config.canEditColumns}
363
+ <MenuItem icon="pencil" on:click={editColumn} disabled={!editable}>
364
+ Edit column
365
+ </MenuItem>
366
+ <MenuItem icon="copy" on:click={duplicateColumn}>
367
+ Duplicate column
368
+ </MenuItem>
369
+ <MenuItem
370
+ icon="tag"
371
+ on:click={makeDisplayColumn}
372
+ disabled={!$config.canEditColumns ||
373
+ column.primaryDisplay ||
374
+ !canBeDisplayColumn(column.schema)}
375
+ >
376
+ Use as display column
377
+ </MenuItem>
378
+ {/if}
379
379
  <MenuItem
380
380
  icon="sort-ascending"
381
381
  on:click={sortAscending}
@@ -394,27 +394,35 @@
394
394
  >
395
395
  Sort {sortingLabels.descending}
396
396
  </MenuItem>
397
- <MenuItem disabled={!canMoveLeft} icon="caret-left" on:click={moveLeft}>
398
- Move left
399
- </MenuItem>
400
- <MenuItem
401
- disabled={!canMoveRight}
402
- icon="caret-right"
403
- on:click={moveRight}
404
- >
405
- Move right
406
- </MenuItem>
407
- <MenuItem
408
- disabled={column.primaryDisplay || !$config.canHideColumns}
409
- icon="eye-slash"
410
- on:click={hideColumn}
411
- >
412
- Hide column
413
- </MenuItem>
414
- {#if $config.canEditColumns && column.schema.type === "link" && column.schema.tableId === TableNames.USERS && !column.schema.autocolumn}
415
- <MenuItem icon="user" on:click={openMigrationModal}>
416
- Migrate to user column
397
+ {#if $config.canEditColumns}
398
+ <MenuItem
399
+ disabled={!canMoveLeft}
400
+ icon="caret-left"
401
+ on:click={moveLeft}
402
+ >
403
+ Move left
404
+ </MenuItem>
405
+ <MenuItem
406
+ disabled={!canMoveRight}
407
+ icon="caret-right"
408
+ on:click={moveRight}
409
+ >
410
+ Move right
417
411
  </MenuItem>
412
+ <MenuItem
413
+ disabled={!$config.canEditColumns ||
414
+ column.primaryDisplay ||
415
+ !$config.canHideColumns}
416
+ icon="eye-slash"
417
+ on:click={hideColumn}
418
+ >
419
+ Hide column
420
+ </MenuItem>
421
+ {#if $config.canEditColumns && column.schema.type === "link" && column.schema.tableId === TableNames.USERS && !column.schema.autocolumn}
422
+ <MenuItem icon="user" on:click={openMigrationModal}>
423
+ Migrate to user column
424
+ </MenuItem>
425
+ {/if}
418
426
  {/if}
419
427
  </Menu>
420
428
  {/if}
@@ -455,6 +463,9 @@
455
463
  display: none;
456
464
  width: 18px;
457
465
  }
466
+ .clear-icon {
467
+ z-index: 99;
468
+ }
458
469
  .header-cell.searchable:not(.open):hover .search-icon,
459
470
  .header-cell.searchable.searching .search-icon {
460
471
  display: block;
@@ -4,11 +4,12 @@
4
4
  import { fade } from "svelte/transition"
5
5
  import { clickOutside, ProgressCircle } from "@budibase/bbui"
6
6
  import { createEventManagers } from "../lib/events"
7
- import { createAPIClient } from "../../../api"
7
+ import { type APIClient, createAPIClient } from "../../../api"
8
8
  import { attachStores } from "../stores"
9
9
  import BulkDeleteHandler from "../controls/BulkDeleteHandler.svelte"
10
10
  import BulkDuplicationHandler from "../controls/BulkDuplicationHandler.svelte"
11
11
  import ClipboardHandler from "../controls/ClipboardHandler.svelte"
12
+ import { type ExternalClipboardData } from "../../../stores/gridClipboard"
12
13
  import GridBody from "./GridBody.svelte"
13
14
  import ResizeOverlay from "../overlays/ResizeOverlay.svelte"
14
15
  import ReorderOverlay from "../overlays/ReorderOverlay.svelte"
@@ -23,9 +24,20 @@
23
24
  import { createGridWebsocket } from "../lib/websocket"
24
25
  import * as Constants from "../lib/constants"
25
26
 
26
- export let API = null
27
- export let datasource = null
28
- export let schemaOverrides = null
27
+ type SchemaOverride = {
28
+ displayName?: string
29
+ type?: string
30
+ disabled?: boolean
31
+ roles?: any
32
+ }
33
+
34
+ export let API: APIClient | null | undefined = null
35
+ // TODO: work out best type to suit datasource
36
+ export let datasource: any = null
37
+ export let schemaOverrides:
38
+ | Record<string, SchemaOverride>
39
+ | null
40
+ | undefined = null
29
41
  export let canAddRows = true
30
42
  export let canExpandRows = true
31
43
  export let canEditRows = true
@@ -42,13 +54,14 @@
42
54
  export let fixedRowHeight = null
43
55
  export let notifySuccess = null
44
56
  export let notifyError = null
45
- export let buttons = null
57
+ export let buttons: { text: string; onClick: any }[] | null | undefined = null
46
58
  export let buttonsCollapsed = false
47
59
  export let buttonsCollapsedText = null
48
60
  export let darkMode = false
49
- export let isCloud = null
61
+ export let isCloud: boolean | null | undefined = null
50
62
  export let aiEnabled = false
51
63
  export let canHideColumns = true
64
+ export let externalClipboard: ExternalClipboardData | undefined = undefined
52
65
 
53
66
  // Unique identifier for DOM nodes inside this instance
54
67
  const gridID = `grid-${Math.random().toString().slice(2)}`
@@ -77,6 +90,8 @@
77
90
  contentLines,
78
91
  gridFocused,
79
92
  error,
93
+ definitionMissing,
94
+ dispatch,
80
95
  } = context
81
96
 
82
97
  // Keep config store up to date with props
@@ -106,8 +121,16 @@
106
121
  isCloud,
107
122
  aiEnabled,
108
123
  canHideColumns,
124
+ externalClipboard,
109
125
  })
110
126
 
127
+ // missing definition, propagate this
128
+ $: if ($definitionMissing) {
129
+ dispatch("definitionMissing", {
130
+ datasource,
131
+ })
132
+ }
133
+
111
134
  // Derive min height and make available in context
112
135
  const minHeight = derived(rowHeight, $height => {
113
136
  const heightForControls = $$slots.controls ? Constants.ControlsHeight : 0
@@ -209,7 +232,9 @@
209
232
  <BulkDeleteHandler />
210
233
  <ClipboardHandler />
211
234
  <KeyboardManager />
212
- <slot />
235
+ {#if $loaded}
236
+ <slot />
237
+ {/if}
213
238
  </div>
214
239
 
215
240
  <style>
@@ -3,6 +3,7 @@ import { Helpers } from "@budibase/bbui"
3
3
  import { parseCellID, getCellID } from "../lib/utils"
4
4
  import { NewRowID } from "../lib/constants"
5
5
  import { Store as StoreContext } from "."
6
+ import { ExternalClipboardData } from "../../../stores/gridClipboard"
6
7
 
7
8
  type ClipboardStoreData =
8
9
  | {
@@ -34,19 +35,47 @@ interface ClipboardActions {
34
35
 
35
36
  export type Store = ClipboardStore & ClipboardDerivedStore & ClipboardActions
36
37
 
37
- export const createStores = (): ClipboardStore => {
38
- const clipboard = writable<ClipboardStoreData>({
39
- value: null,
40
- multiCellCopy: false,
41
- })
38
+ export const createStores = (context?: {
39
+ externalClipboard: ExternalClipboardData
40
+ }): ClipboardStore => {
41
+ // Initialize with external clipboard state if provided
42
+ let initialState: ClipboardStoreData
43
+
44
+ if (context?.externalClipboard?.clipboard) {
45
+ const externalState = context.externalClipboard.clipboard.get()
46
+ if (externalState.multiCellCopy) {
47
+ initialState = {
48
+ value: externalState.value,
49
+ multiCellCopy: true,
50
+ }
51
+ } else {
52
+ initialState = {
53
+ value: externalState.value,
54
+ multiCellCopy: false,
55
+ }
56
+ }
57
+ } else {
58
+ initialState = {
59
+ value: undefined,
60
+ multiCellCopy: false,
61
+ }
62
+ }
63
+
64
+ const clipboard = writable<ClipboardStoreData>(initialState)
42
65
  return {
43
66
  clipboard,
44
67
  }
45
68
  }
46
69
 
47
70
  export const deriveStores = (context: StoreContext): ClipboardDerivedStore => {
48
- const { clipboard, focusedCellAPI, selectedCellCount, config, focusedRowId } =
49
- context
71
+ const {
72
+ clipboard,
73
+ focusedCellAPI,
74
+ selectedCellCount,
75
+ config,
76
+ focusedRowId,
77
+ props,
78
+ } = context
50
79
 
51
80
  // Derive whether or not we're able to copy
52
81
  const copyAllowed = derived(focusedCellAPI, $focusedCellAPI => {
@@ -55,16 +84,24 @@ export const deriveStores = (context: StoreContext): ClipboardDerivedStore => {
55
84
 
56
85
  // Derive whether or not we're able to paste
57
86
  const pasteAllowed = derived(
58
- [clipboard, focusedCellAPI, selectedCellCount, config, focusedRowId],
87
+ [clipboard, focusedCellAPI, selectedCellCount, config, focusedRowId, props],
59
88
  ([
60
89
  $clipboard,
61
90
  $focusedCellAPI,
62
91
  $selectedCellCount,
63
92
  $config,
64
93
  $focusedRowId,
94
+ $props,
65
95
  ]) => {
96
+ // Check if we have clipboard data (internal or external)
97
+ let hasClipboardData = $clipboard.value != null
98
+ if (!hasClipboardData && $props.externalClipboard?.clipboard) {
99
+ const externalState = $props.externalClipboard.clipboard.get()
100
+ hasClipboardData = externalState.value != null
101
+ }
102
+
66
103
  if (
67
- $clipboard.value == null ||
104
+ !hasClipboardData ||
68
105
  !$config.canEditRows ||
69
106
  !$focusedCellAPI ||
70
107
  $focusedRowId === NewRowID
@@ -105,6 +142,7 @@ export const createActions = (context: StoreContext): ClipboardActions => {
105
142
  focusedCellId,
106
143
  columnLookupMap,
107
144
  visibleColumns,
145
+ props,
108
146
  } = context
109
147
 
110
148
  // Copies the currently selected value (or values)
@@ -137,11 +175,22 @@ export const createActions = (context: StoreContext): ClipboardActions => {
137
175
  value.push(rowValues)
138
176
  }
139
177
 
140
- // Update state
178
+ // Update internal state
141
179
  clipboard.set({
142
180
  value,
143
181
  multiCellCopy: true,
144
182
  })
183
+
184
+ const { externalClipboard } = get(props)
185
+ // Sync with external clipboard if provided
186
+ if (externalClipboard?.onCopy) {
187
+ externalClipboard.onCopy({
188
+ value,
189
+ multiCellCopy: true,
190
+ tableId: externalClipboard.tableId,
191
+ viewId: externalClipboard.viewId,
192
+ })
193
+ }
145
194
  } else {
146
195
  // Single value to copy
147
196
  const value = $focusedCellAPI?.getValue()
@@ -150,6 +199,17 @@ export const createActions = (context: StoreContext): ClipboardActions => {
150
199
  multiCellCopy,
151
200
  })
152
201
 
202
+ const { externalClipboard } = get(props)
203
+ // Sync with external clipboard if provided
204
+ if (externalClipboard?.onCopy) {
205
+ externalClipboard.onCopy({
206
+ value,
207
+ multiCellCopy: false,
208
+ tableId: externalClipboard.tableId,
209
+ viewId: externalClipboard.viewId,
210
+ })
211
+ }
212
+
153
213
  // Also copy a stringified version to the clipboard
154
214
  let stringified = ""
155
215
  if (value != null && value !== "") {
@@ -165,7 +225,30 @@ export const createActions = (context: StoreContext): ClipboardActions => {
165
225
  if (!get(pasteAllowed)) {
166
226
  return
167
227
  }
168
- const { value, multiCellCopy } = get(clipboard)
228
+
229
+ // Check for external clipboard data first
230
+ const { externalClipboard } = get(props)
231
+ let clipboardData = get(clipboard)
232
+
233
+ if (externalClipboard?.clipboard) {
234
+ const externalState = externalClipboard.clipboard.get()
235
+ // Use external clipboard data if it has a value
236
+ if (externalState.value !== undefined) {
237
+ if (externalState.multiCellCopy) {
238
+ clipboardData = {
239
+ value: externalState.value as any[][],
240
+ multiCellCopy: true,
241
+ }
242
+ } else {
243
+ clipboardData = {
244
+ value: externalState.value,
245
+ multiCellCopy: false,
246
+ }
247
+ }
248
+ }
249
+ }
250
+
251
+ const { value, multiCellCopy } = clipboardData
169
252
  const multiCellPaste = get(selectedCellCount) > 1
170
253
 
171
254
  // Choose paste strategy
@@ -26,6 +26,8 @@ import * as Cache from "./cache"
26
26
  import * as Conditions from "./conditions"
27
27
  import { SortOrder, UIDatasource, UISearchFilter } from "@budibase/types"
28
28
  import * as Constants from "../lib/constants"
29
+ import * as GridClipboard from "../../../stores/gridClipboard"
30
+ import { ExternalClipboardData } from "../../../stores/gridClipboard"
29
31
 
30
32
  const DependencyOrderedStores = [
31
33
  Sort,
@@ -76,6 +78,7 @@ export interface BaseStoreProps {
76
78
  canSaveSchema?: boolean
77
79
  minHeight?: number
78
80
  canHideColumns?: boolean
81
+ externalClipboard?: ExternalClipboardData
79
82
  }
80
83
 
81
84
  export interface BaseStore {
@@ -109,7 +112,8 @@ export type Store = BaseStore &
109
112
  Viewport.Store &
110
113
  Notifications.Store &
111
114
  Sort.Store &
112
- Bounds.Store
115
+ Bounds.Store &
116
+ GridClipboard.Store
113
117
 
114
118
  export const attachStores = (context: BaseStore): Store => {
115
119
  // Atomic store creation
@@ -29,6 +29,7 @@ interface RowStore {
29
29
  inProgressChanges: Writable<Record<string, number>>
30
30
  hasNextPage: Writable<boolean>
31
31
  error: Writable<string | null>
32
+ definitionMissing: Writable<boolean>
32
33
  }
33
34
 
34
35
  interface RowDerivedStore {
@@ -87,6 +88,7 @@ export const createStores = (): RowStore => {
87
88
  const inProgressChanges = writable({})
88
89
  const hasNextPage = writable(false)
89
90
  const error = writable(null)
91
+ const definitionMissing = writable(false)
90
92
  const fetch = writable(null)
91
93
 
92
94
  // Mark loaded as true if we've ever stopped loading
@@ -109,6 +111,7 @@ export const createStores = (): RowStore => {
109
111
  inProgressChanges,
110
112
  hasNextPage,
111
113
  error,
114
+ definitionMissing,
112
115
  }
113
116
  }
114
117
 
@@ -192,6 +195,7 @@ export const createActions = (context: StoreContext): RowActionStore => {
192
195
  inProgressChanges,
193
196
  hasNextPage,
194
197
  error,
198
+ definitionMissing,
195
199
  notifications,
196
200
  fetch,
197
201
  hasBudibaseIdentifiers,
@@ -250,6 +254,14 @@ export const createActions = (context: StoreContext): RowActionStore => {
250
254
  let message = "An unknown error occurred"
251
255
  if ($fetch.error.status === 403) {
252
256
  message = "You don't have access to this data"
257
+ } else if (
258
+ ($fetch.error.status === 404 &&
259
+ $fetch.error.url &&
260
+ $fetch.error.url.includes("/api/tables/")) ||
261
+ $fetch.error.url.includes("/api/v2/views/")
262
+ ) {
263
+ definitionMissing.set(true)
264
+ message = $fetch.error.message
253
265
  } else if ($fetch.error.message) {
254
266
  message = $fetch.error.message
255
267
  }
@@ -30,6 +30,7 @@ interface DataFetchStore<TDefinition, TQuery> {
30
30
  error: {
31
31
  message: string
32
32
  status: number
33
+ url: string
33
34
  } | null
34
35
  definition?: TDefinition | null
35
36
  }
@@ -0,0 +1,67 @@
1
+ import { writable, get, type Writable } from "svelte/store"
2
+
3
+ export interface GridClipboardState {
4
+ value: any
5
+ multiCellCopy: boolean
6
+ sourceTableId?: string
7
+ sourceViewId?: string
8
+ }
9
+
10
+ export interface GridClipboardStore extends Writable<GridClipboardState> {
11
+ copy: (
12
+ value: any,
13
+ multiCellCopy: boolean,
14
+ tableId?: string,
15
+ viewId?: string
16
+ ) => void
17
+ clear: () => void
18
+ get: () => GridClipboardState
19
+ }
20
+
21
+ export interface ExternalClipboardData {
22
+ clipboard: GridClipboardStore
23
+ tableId?: string
24
+ viewId?: string
25
+ onCopy: (data: {
26
+ value: any
27
+ multiCellCopy: boolean
28
+ tableId?: string
29
+ viewId?: string
30
+ }) => void
31
+ }
32
+
33
+ export type Store = GridClipboardStore
34
+
35
+ const createGridClipboard = (): GridClipboardStore => {
36
+ const store = writable<GridClipboardState>({
37
+ value: undefined,
38
+ multiCellCopy: false,
39
+ sourceTableId: undefined,
40
+ sourceViewId: undefined,
41
+ })
42
+
43
+ return {
44
+ subscribe: store.subscribe,
45
+ set: store.set,
46
+ update: store.update,
47
+ copy: (value, multiCellCopy, tableId, viewId) => {
48
+ store.set({
49
+ value,
50
+ multiCellCopy,
51
+ sourceTableId: tableId,
52
+ sourceViewId: viewId,
53
+ })
54
+ },
55
+ clear: () => {
56
+ store.set({
57
+ value: undefined,
58
+ multiCellCopy: false,
59
+ sourceTableId: undefined,
60
+ sourceViewId: undefined,
61
+ })
62
+ },
63
+ get: () => get(store),
64
+ }
65
+ }
66
+
67
+ export const gridClipboard = createGridClipboard()
@@ -1,2 +1,3 @@
1
1
  export { createLocalStorageStore } from "./localStorage"
2
2
  export { createSessionStorageStore } from "./sessionStorage"
3
+ export { gridClipboard } from "./gridClipboard"