diff --git a/lerna.json b/lerna.json
index c893e4b402..afcb33918b 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,6 +1,6 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
- "version": "3.4.6",
+ "version": "3.4.9",
"npmClient": "yarn",
"concurrency": 20,
"command": {
diff --git a/packages/backend-core/src/utils/utils.ts b/packages/backend-core/src/utils/utils.ts
index 30cf55b149..7f2e25b6d4 100644
--- a/packages/backend-core/src/utils/utils.ts
+++ b/packages/backend-core/src/utils/utils.ts
@@ -247,3 +247,7 @@ export function hasCircularStructure(json: any) {
}
return false
}
+
+export function urlHasProtocol(url: string): boolean {
+ return !!url.match(/^.+:\/\/.+$/)
+}
diff --git a/packages/bbui/src/Typography/Body.svelte b/packages/bbui/src/Typography/Body.svelte
index 2123eeee95..06664c9033 100644
--- a/packages/bbui/src/Typography/Body.svelte
+++ b/packages/bbui/src/Typography/Body.svelte
@@ -1,11 +1,11 @@
-
+ import { onMount } from "svelte"
+ import { Input, Label } from "@budibase/bbui"
+ import { previewStore, selectedScreen } from "@/stores/builder"
+ import type { ComponentContext } from "@budibase/types"
+
+ export let baseRoute = ""
+
+ let testValue: string | undefined
+
+ $: routeParams = baseRoute.match(/:[a-zA-Z]+/g) || []
+ $: hasUrlParams = routeParams.length > 0
+ $: placeholder = getPlaceholder(baseRoute)
+ $: baseInput = createBaseInput(baseRoute)
+ $: updateTestValueFromContext($previewStore.selectedComponentContext)
+ $: if ($selectedScreen) {
+ testValue = ""
+ }
+
+ const getPlaceholder = (route: string) => {
+ const trimmed = route.replace(/\/$/, "")
+ if (trimmed.startsWith("/:")) {
+ return "1"
+ }
+ const segments = trimmed.split("/").slice(2)
+ let count = 1
+ return segments
+ .map(segment => (segment.startsWith(":") ? count++ : segment))
+ .join("/")
+ }
+
+ // This function is needed to repopulate the test value from componentContext
+ // when a user navigates to another component and then back again
+ const updateTestValueFromContext = (context: ComponentContext | null) => {
+ if (context?.url && !testValue) {
+ const { wild, ...urlParams } = context.url
+ const queryParams = context.query
+ if (Object.values(urlParams).some(v => Boolean(v))) {
+ let value = baseRoute
+ .split("/")
+ .slice(2)
+ .map(segment =>
+ segment.startsWith(":")
+ ? urlParams[segment.slice(1)] || ""
+ : segment
+ )
+ .join("/")
+ const qs = new URLSearchParams(queryParams).toString()
+ if (qs) {
+ value += `?${qs}`
+ }
+ testValue = value
+ }
+ }
+ }
+
+ const createBaseInput = (baseRoute: string) => {
+ return baseRoute === "/" || baseRoute.split("/")[1]?.startsWith(":")
+ ? "/"
+ : `/${baseRoute.split("/")[1]}/`
+ }
+
+ const onVariableChange = (e: CustomEvent) => {
+ previewStore.setUrlTestData({ route: baseRoute, testValue: e.detail })
+ }
+
+ onMount(() => {
+ previewStore.requestComponentContext()
+ })
+
+
+{#if hasUrlParams}
+
+
+
+
+
+
+{/if}
+
+
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte
index 3a6e7a702c..31479bc820 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte
@@ -15,6 +15,7 @@
import ButtonActionEditor from "@/components/design/settings/controls/ButtonActionEditor/ButtonActionEditor.svelte"
import { getBindableProperties } from "@/dataBinding"
import BarButtonList from "@/components/design/settings/controls/BarButtonList.svelte"
+ import URLVariableTestInput from "@/components/design/settings/controls/URLVariableTestInput.svelte"
$: bindings = getBindableProperties($selectedScreen, null)
$: screenSettings = getScreenSettings($selectedScreen)
@@ -93,6 +94,13 @@
],
},
},
+ {
+ key: "urlTest",
+ control: URLVariableTestInput,
+ props: {
+ baseRoute: screen.routing?.route,
+ },
+ },
]
return settings
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/dndStore.js b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/dndStore.js
index 2361abce89..bfbcc9ea5b 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/dndStore.js
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/dndStore.js
@@ -1,12 +1,8 @@
import { writable, get } from "svelte/store"
import { findComponentParent, findComponentPath } from "@/helpers/components"
import { selectedScreen, componentStore } from "@/stores/builder"
-
-export const DropPosition = {
- ABOVE: "above",
- BELOW: "below",
- INSIDE: "inside",
-}
+import { DropPosition } from "@budibase/types"
+export { DropPosition } from "@budibase/types"
const initialState = {
source: null,
diff --git a/packages/builder/src/stores/builder/components.ts b/packages/builder/src/stores/builder/components.ts
index b3e897fa3c..38fa9a6a41 100644
--- a/packages/builder/src/stores/builder/components.ts
+++ b/packages/builder/src/stores/builder/components.ts
@@ -311,8 +311,6 @@ export class ComponentStore extends BudiStore {
component[setting.key] = fieldOptions[0]
component.label = fieldOptions[0]
}
- } else if (setting.type === "icon") {
- component[setting.key] = "ri-star-fill"
} else if (useDefaultValues && setting.defaultValue !== undefined) {
// Use default value where required
component[setting.key] = setting.defaultValue
diff --git a/packages/builder/src/stores/builder/index.js b/packages/builder/src/stores/builder/index.js
index 76679d8088..56c2d17cc3 100644
--- a/packages/builder/src/stores/builder/index.js
+++ b/packages/builder/src/stores/builder/index.js
@@ -1,20 +1,20 @@
-import { layoutStore } from "./layouts.js"
-import { appStore } from "./app.js"
+import { layoutStore } from "./layouts"
+import { appStore } from "./app"
import { componentStore, selectedComponent } from "./components"
-import { navigationStore } from "./navigation.js"
-import { themeStore } from "./theme.js"
+import { navigationStore } from "./navigation"
+import { themeStore } from "./theme"
import { screenStore, selectedScreen, sortedScreens } from "./screens"
-import { builderStore } from "./builder.js"
-import { hoverStore } from "./hover.js"
-import { previewStore } from "./preview.js"
+import { builderStore } from "./builder"
+import { hoverStore } from "./hover"
+import { previewStore } from "./preview"
import {
automationStore,
selectedAutomation,
automationHistoryStore,
-} from "./automations.js"
-import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js"
-import { deploymentStore } from "./deployments.js"
-import { contextMenuStore } from "./contextMenu.js"
+} from "./automations"
+import { userStore, userSelectedResourceMap, isOnlyUser } from "./users"
+import { deploymentStore } from "./deployments"
+import { contextMenuStore } from "./contextMenu"
import { snippets } from "./snippets"
import {
screenComponentsList,
diff --git a/packages/builder/src/stores/builder/preview.ts b/packages/builder/src/stores/builder/preview.ts
index a382eeefb0..0fef91d6b9 100644
--- a/packages/builder/src/stores/builder/preview.ts
+++ b/packages/builder/src/stores/builder/preview.ts
@@ -1,9 +1,8 @@
import { get } from "svelte/store"
import { BudiStore } from "../BudiStore"
+import { PreviewDevice, ComponentContext } from "@budibase/types"
-type PreviewDevice = "desktop" | "tablet" | "mobile"
type PreviewEventHandler = (name: string, payload?: any) => void
-type ComponentContext = Record
interface PreviewState {
previewDevice: PreviewDevice
@@ -86,6 +85,10 @@ export class PreviewStore extends BudiStore {
this.sendEvent("builder-state", data)
}
+ setUrlTestData(data: Record) {
+ this.sendEvent("builder-url-test-data", data)
+ }
+
requestComponentContext() {
this.sendEvent("request-context")
}
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index c236dd1ad9..930d5addff 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -1455,7 +1455,8 @@
"type": "icon",
"label": "Icon",
"key": "icon",
- "required": true
+ "required": true,
+ "defaultValue": "ri-star-fill"
},
{
"type": "select",
diff --git a/packages/client/src/components/Block.svelte b/packages/client/src/components/Block.svelte
index 5f39f7eef1..5e16842f9a 100644
--- a/packages/client/src/components/Block.svelte
+++ b/packages/client/src/components/Block.svelte
@@ -1,6 +1,6 @@
+
+
+
+
diff --git a/packages/client/src/index.js b/packages/client/src/index.js
new file mode 100644
index 0000000000..f19c46a452
--- /dev/null
+++ b/packages/client/src/index.js
@@ -0,0 +1,142 @@
+import ClientApp from "./components/ClientApp.svelte"
+import UpdatingApp from "./components/UpdatingApp.svelte"
+import {
+ builderStore,
+ appStore,
+ blockStore,
+ componentStore,
+ environmentStore,
+ dndStore,
+ eventStore,
+ hoverStore,
+ stateStore,
+ routeStore,
+} from "./stores"
+import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
+import { get } from "svelte/store"
+import { initWebsocket } from "./websocket.js"
+
+// Provide svelte and svelte/internal as globals for custom components
+import * as svelte from "svelte"
+import * as internal from "svelte/internal"
+
+window.svelte_internal = internal
+window.svelte = svelte
+
+// Initialise spectrum icons
+loadSpectrumIcons()
+
+let app
+
+const loadBudibase = async () => {
+ // Update builder store with any builder flags
+ builderStore.set({
+ ...get(builderStore),
+ inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
+ layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
+ screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
+ selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"],
+ previewId: window["##BUDIBASE_PREVIEW_ID##"],
+ theme: window["##BUDIBASE_PREVIEW_THEME##"],
+ customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"],
+ previewDevice: window["##BUDIBASE_PREVIEW_DEVICE##"],
+ navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"],
+ hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"],
+ usedPlugins: window["##BUDIBASE_USED_PLUGINS##"],
+ location: window["##BUDIBASE_LOCATION##"],
+ snippets: window["##BUDIBASE_SNIPPETS##"],
+ componentErrors: window["##BUDIBASE_COMPONENT_ERRORS##"],
+ })
+
+ // Set app ID - this window flag is set by both the preview and the real
+ // server rendered app HTML
+ appStore.actions.setAppId(window["##BUDIBASE_APP_ID##"])
+
+ // Set the flag used to determine if the app is being loaded via an iframe
+ appStore.actions.setAppEmbedded(
+ window["##BUDIBASE_APP_EMBEDDED##"] === "true"
+ )
+
+ if (window.MIGRATING_APP) {
+ new UpdatingApp({
+ target: window.document.body,
+ })
+ return
+ }
+
+ // Fetch environment info
+ if (!get(environmentStore)?.loaded) {
+ await environmentStore.actions.fetchEnvironment()
+ }
+
+ // Register handler for runtime events from the builder
+ window.handleBuilderRuntimeEvent = (type, data) => {
+ if (!window["##BUDIBASE_IN_BUILDER##"]) {
+ return
+ }
+ if (type === "event-completed") {
+ eventStore.actions.resolveEvent(data)
+ } else if (type === "eject-block") {
+ const block = blockStore.actions.getBlock(data)
+ block?.eject()
+ } else if (type === "dragging-new-component") {
+ const { dragging, component } = data
+ if (dragging) {
+ const definition =
+ componentStore.actions.getComponentDefinition(component)
+ dndStore.actions.startDraggingNewComponent({ component, definition })
+ } else {
+ dndStore.actions.reset()
+ }
+ } else if (type === "request-context") {
+ const { selectedComponentInstance, screenslotInstance } =
+ get(componentStore)
+ const instance = selectedComponentInstance || screenslotInstance
+ const context = instance?.getDataContext()
+ let stringifiedContext = null
+ try {
+ stringifiedContext = JSON.stringify(context)
+ } catch (error) {
+ // Ignore - invalid context
+ }
+ eventStore.actions.dispatchEvent("provide-context", {
+ context: stringifiedContext,
+ })
+ } else if (type === "hover-component") {
+ hoverStore.actions.hoverComponent(data, false)
+ } else if (type === "builder-meta") {
+ builderStore.actions.setMetadata(data)
+ } else if (type === "builder-state") {
+ const [[key, value]] = Object.entries(data)
+ stateStore.actions.setValue(key, value)
+ } else if (type === "builder-url-test-data") {
+ const { route, testValue } = data
+ routeStore.actions.setTestUrlParams(route, testValue)
+ }
+ }
+
+ // Register any custom components
+ if (window["##BUDIBASE_CUSTOM_COMPONENTS##"]) {
+ window["##BUDIBASE_CUSTOM_COMPONENTS##"].forEach(component => {
+ componentStore.actions.registerCustomComponent(component)
+ })
+ }
+
+ // Make a callback available for custom component bundles to register
+ // themselves at runtime
+ window.registerCustomComponent =
+ componentStore.actions.registerCustomComponent
+
+ // Initialise websocket
+ initWebsocket()
+
+ // Create app if one hasn't been created yet
+ if (!app) {
+ app = new ClientApp({
+ target: window.document.body,
+ })
+ }
+}
+
+// Attach to window so the HTML template can call this when it loads
+window.loadBudibase = loadBudibase
diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts
index 443648eb08..f055e7fc15 100644
--- a/packages/client/src/index.ts
+++ b/packages/client/src/index.ts
@@ -10,6 +10,7 @@ import {
eventStore,
hoverStore,
stateStore,
+ routeStore,
} from "@/stores"
import { get } from "svelte/store"
import { initWebsocket } from "@/websocket"
@@ -18,7 +19,6 @@ import type { ActionTypes } from "@/constants"
import { Readable } from "svelte/store"
import {
Screen,
- Layout,
Theme,
AppCustomTheme,
PreviewDevice,
@@ -47,7 +47,6 @@ declare global {
// Data from builder
"##BUDIBASE_APP_ID##"?: string
"##BUDIBASE_IN_BUILDER##"?: true
- "##BUDIBASE_PREVIEW_LAYOUT##"?: Layout
"##BUDIBASE_PREVIEW_SCREEN##"?: Screen
"##BUDIBASE_SELECTED_COMPONENT_ID##"?: string
"##BUDIBASE_PREVIEW_ID##"?: number
@@ -58,13 +57,8 @@ declare global {
"##BUDIBASE_PREVIEW_NAVIGATION##"?: AppNavigation
"##BUDIBASE_HIDDEN_COMPONENT_IDS##"?: string[]
"##BUDIBASE_USED_PLUGINS##"?: Plugin[]
- "##BUDIBASE_LOCATION##"?: {
- protocol: string
- hostname: string
- port: string
- }
"##BUDIBASE_SNIPPETS##"?: Snippet[]
- "##BUDIBASE_COMPONENT_ERRORS##"?: Record[]
+ "##BUDIBASE_COMPONENT_ERRORS##"?: Record
"##BUDIBASE_CUSTOM_COMPONENTS##"?: CustomComponent[]
// Other flags
@@ -114,7 +108,6 @@ const loadBudibase = async () => {
builderStore.set({
...get(builderStore),
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
- layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"],
previewId: window["##BUDIBASE_PREVIEW_ID##"],
@@ -124,7 +117,6 @@ const loadBudibase = async () => {
navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"],
hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"],
usedPlugins: window["##BUDIBASE_USED_PLUGINS##"],
- location: window["##BUDIBASE_LOCATION##"],
snippets: window["##BUDIBASE_SNIPPETS##"],
componentErrors: window["##BUDIBASE_COMPONENT_ERRORS##"],
})
@@ -188,6 +180,9 @@ const loadBudibase = async () => {
} else if (type === "builder-state") {
const [[key, value]] = Object.entries(data)
stateStore.actions.setValue(key, value)
+ } else if (type === "builder-url-test-data") {
+ const { route, testValue } = data
+ routeStore.actions.setTestUrlParams(route, testValue)
}
}
diff --git a/packages/client/src/stores/builder.js b/packages/client/src/stores/builder.ts
similarity index 59%
rename from packages/client/src/stores/builder.js
rename to packages/client/src/stores/builder.ts
index 287d23e767..36ba7fffb8 100644
--- a/packages/client/src/stores/builder.js
+++ b/packages/client/src/stores/builder.ts
@@ -2,9 +2,39 @@ import { writable, get } from "svelte/store"
import { API } from "@/api"
import { devToolsStore } from "./devTools.js"
import { eventStore } from "./events.js"
+import {
+ ComponentDefinition,
+ DropPosition,
+ PingSource,
+ PreviewDevice,
+ Screen,
+ Theme,
+ AppCustomTheme,
+ AppNavigation,
+ Plugin,
+ Snippet,
+ UIComponentError,
+} from "@budibase/types"
+
+interface BuilderStore {
+ inBuilder: boolean
+ screen?: Screen | null
+ selectedComponentId?: string | null
+ editMode: boolean
+ previewId?: number | null
+ theme?: Theme | null
+ customTheme?: AppCustomTheme | null
+ previewDevice?: PreviewDevice
+ navigation?: AppNavigation | null
+ hiddenComponentIds?: string[]
+ usedPlugins?: Plugin[] | null
+ metadata: { componentId: string; step: number } | null
+ snippets?: Snippet[] | null
+ componentErrors?: Record
+}
const createBuilderStore = () => {
- const initialState = {
+ const initialState: BuilderStore = {
inBuilder: false,
screen: null,
selectedComponentId: null,
@@ -16,17 +46,13 @@ const createBuilderStore = () => {
navigation: null,
hiddenComponentIds: [],
usedPlugins: null,
- eventResolvers: {},
metadata: null,
snippets: null,
componentErrors: {},
-
- // Legacy - allow the builder to specify a layout
- layout: null,
}
const store = writable(initialState)
const actions = {
- selectComponent: id => {
+ selectComponent: (id: string) => {
if (id === get(store).selectedComponentId) {
return
}
@@ -38,46 +64,59 @@ const createBuilderStore = () => {
devToolsStore.actions.setAllowSelection(false)
eventStore.actions.dispatchEvent("select-component", { id })
},
- updateProp: (prop, value) => {
+ updateProp: (prop: string, value: any) => {
eventStore.actions.dispatchEvent("update-prop", { prop, value })
},
- updateStyles: async (styles, id) => {
+ updateStyles: async (styles: Record, id: string) => {
await eventStore.actions.dispatchEvent("update-styles", {
styles,
id,
})
},
- keyDown: (key, ctrlKey) => {
+ keyDown: (key: string, ctrlKey: boolean) => {
eventStore.actions.dispatchEvent("key-down", { key, ctrlKey })
},
- duplicateComponent: (id, mode = "below", selectComponent = true) => {
+ duplicateComponent: (
+ id: string,
+ mode = DropPosition.BELOW,
+ selectComponent = true
+ ) => {
eventStore.actions.dispatchEvent("duplicate-component", {
id,
mode,
selectComponent,
})
},
- deleteComponent: id => {
+ deleteComponent: (id: string) => {
eventStore.actions.dispatchEvent("delete-component", { id })
},
notifyLoaded: () => {
eventStore.actions.dispatchEvent("preview-loaded")
},
- analyticsPing: async ({ embedded }) => {
+ analyticsPing: async ({ embedded }: { embedded: boolean }) => {
try {
- await API.analyticsPing({ source: "app", embedded })
+ await API.analyticsPing({ source: PingSource.APP, embedded })
} catch (error) {
// Do nothing
}
},
- moveComponent: async (componentId, destinationComponentId, mode) => {
+ moveComponent: async (
+ componentId: string,
+ destinationComponentId: string,
+ mode: DropPosition
+ ) => {
await eventStore.actions.dispatchEvent("move-component", {
componentId,
destinationComponentId,
mode,
})
},
- dropNewComponent: (component, parent, index, props) => {
+ dropNewComponent: (
+ component: string,
+ parent: string,
+ index: number,
+ props: Record
+ ) => {
eventStore.actions.dispatchEvent("drop-new-component", {
component,
parent,
@@ -85,7 +124,7 @@ const createBuilderStore = () => {
props,
})
},
- setEditMode: enabled => {
+ setEditMode: (enabled: boolean) => {
if (enabled === get(store).editMode) {
return
}
@@ -94,18 +133,18 @@ const createBuilderStore = () => {
requestAddComponent: () => {
eventStore.actions.dispatchEvent("request-add-component")
},
- highlightSetting: setting => {
+ highlightSetting: (setting: string) => {
eventStore.actions.dispatchEvent("highlight-setting", { setting })
},
- ejectBlock: (id, definition) => {
+ ejectBlock: (id: string, definition: ComponentDefinition) => {
eventStore.actions.dispatchEvent("eject-block", { id, definition })
},
- updateUsedPlugin: (name, hash) => {
+ updateUsedPlugin: (name: string, hash: string) => {
// Check if we used this plugin
const used = get(store)?.usedPlugins?.find(x => x.name === name)
if (used) {
store.update(state => {
- state.usedPlugins = state.usedPlugins.filter(x => x.name !== name)
+ state.usedPlugins = state.usedPlugins!.filter(x => x.name !== name)
state.usedPlugins.push({
...used,
hash,
@@ -117,13 +156,13 @@ const createBuilderStore = () => {
// Notify the builder so we can reload component definitions
eventStore.actions.dispatchEvent("reload-plugin")
},
- addParentComponent: (componentId, parentType) => {
+ addParentComponent: (componentId: string, parentType: string) => {
eventStore.actions.dispatchEvent("add-parent-component", {
componentId,
parentType,
})
},
- setMetadata: metadata => {
+ setMetadata: (metadata: { componentId: string; step: number }) => {
store.update(state => ({
...state,
metadata,
@@ -132,7 +171,7 @@ const createBuilderStore = () => {
}
return {
...store,
- set: state => store.set({ ...initialState, ...state }),
+ set: (state: BuilderStore) => store.set({ ...initialState, ...state }),
actions,
}
}
diff --git a/packages/client/src/stores/derived/devToolsEnabled.js b/packages/client/src/stores/derived/devToolsEnabled.js
index 525f70e49b..20327c6550 100644
--- a/packages/client/src/stores/derived/devToolsEnabled.js
+++ b/packages/client/src/stores/derived/devToolsEnabled.js
@@ -1,6 +1,6 @@
import { derived } from "svelte/store"
import { appStore } from "../app.js"
-import { builderStore } from "../builder.js"
+import { builderStore } from "../builder"
export const devToolsEnabled = derived(
[appStore, builderStore],
diff --git a/packages/client/src/stores/derived/snippets.js b/packages/client/src/stores/derived/snippets.js
index 74b2797643..8a7b2c3461 100644
--- a/packages/client/src/stores/derived/snippets.js
+++ b/packages/client/src/stores/derived/snippets.js
@@ -1,5 +1,5 @@
import { appStore } from "../app.js"
-import { builderStore } from "../builder.js"
+import { builderStore } from "../builder"
import { derivedMemo } from "@budibase/frontend-core"
export const snippets = derivedMemo(
diff --git a/packages/client/src/stores/routes.ts b/packages/client/src/stores/routes.ts
index e6cb6288d2..5158205b0a 100644
--- a/packages/client/src/stores/routes.ts
+++ b/packages/client/src/stores/routes.ts
@@ -119,7 +119,39 @@ const createRouteStore = () => {
const base = window.location.href.split("#")[0]
return `${base}#${relativeURL}`
}
+ const setTestUrlParams = (route: string, testValue: string) => {
+ if (route === "/") {
+ return
+ }
+ const [pathPart, queryPart] = testValue.split("?")
+ const routeSegments = route.split("/").filter(Boolean)
+
+ // If first segment happens to be a parameter (e.g. /:foo), include it
+ const startIndex = routeSegments[0]?.startsWith(":") ? 0 : 1
+ const segments = routeSegments.slice(startIndex)
+ const testSegments = pathPart.split("/")
+
+ const params: Record = {}
+ segments.forEach((segment, index) => {
+ if (segment.startsWith(":") && index < testSegments.length) {
+ params[segment.slice(1)] = testSegments[index]
+ }
+ })
+
+ const queryParams: Record = {}
+ if (queryPart) {
+ queryPart.split("&").forEach(param => {
+ const [key, value] = param.split("=")
+ if (key && value) {
+ queryParams[key] = value
+ }
+ })
+ }
+
+ setQueryParams({ ...queryParams })
+ store.update(state => ({ ...state, testUrlParams: params }))
+ }
return {
subscribe: store.subscribe,
actions: {
@@ -130,6 +162,7 @@ const createRouteStore = () => {
setQueryParams,
setActiveRoute,
setRouterLoaded,
+ setTestUrlParams,
},
}
}
diff --git a/packages/client/src/stores/screens.js b/packages/client/src/stores/screens.js
index 4163f87e1a..1cc205b382 100644
--- a/packages/client/src/stores/screens.js
+++ b/packages/client/src/stores/screens.js
@@ -36,11 +36,6 @@ const createScreenStore = () => {
activeScreen = Helpers.cloneDeep($builderStore.screen)
screens = [activeScreen]
- // Legacy - allow the builder to specify a layout
- if ($builderStore.layout) {
- activeLayout = $builderStore.layout
- }
-
// Attach meta
const errors = $builderStore.componentErrors || {}
const attachComponentMeta = component => {
diff --git a/packages/server/package.json b/packages/server/package.json
index fd448629e8..9a70ecba9c 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -99,7 +99,7 @@
"jsonschema": "1.4.0",
"jsonwebtoken": "9.0.2",
"knex": "2.4.2",
- "koa": "2.13.4",
+ "koa": "2.15.4",
"koa-body": "4.2.0",
"koa-compress": "4.0.1",
"koa-send": "5.0.1",
diff --git a/packages/server/src/api/controllers/static/index.ts b/packages/server/src/api/controllers/static/index.ts
index 97406d677d..6b8ecda0d9 100644
--- a/packages/server/src/api/controllers/static/index.ts
+++ b/packages/server/src/api/controllers/static/index.ts
@@ -339,10 +339,13 @@ export const getSignedUploadURL = async function (
ctx.throw(400, "bucket and key values are required")
}
try {
+ let endpoint = datasource?.config?.endpoint
+ if (endpoint && !utils.urlHasProtocol(endpoint)) {
+ endpoint = `https://${endpoint}`
+ }
const s3 = new S3({
region: awsRegion,
- endpoint: datasource?.config?.endpoint || undefined,
-
+ endpoint: endpoint,
credentials: {
accessKeyId: datasource?.config?.accessKeyId as string,
secretAccessKey: datasource?.config?.secretAccessKey as string,
@@ -350,8 +353,8 @@ export const getSignedUploadURL = async function (
})
const params = { Bucket: bucket, Key: key }
signedUrl = await getSignedUrl(s3, new PutObjectCommand(params))
- if (datasource?.config?.endpoint) {
- publicUrl = `${datasource.config.endpoint}/${bucket}/${key}`
+ if (endpoint) {
+ publicUrl = `${endpoint}/${bucket}/${key}`
} else {
publicUrl = `https://${bucket}.s3.${awsRegion}.amazonaws.com/${key}`
}
diff --git a/packages/server/src/api/controllers/static/templates/preview.hbs b/packages/server/src/api/controllers/static/templates/preview.hbs
index 750a780897..fc6de9a28c 100644
--- a/packages/server/src/api/controllers/static/templates/preview.hbs
+++ b/packages/server/src/api/controllers/static/templates/preview.hbs
@@ -80,7 +80,6 @@
// Set some flags so the app knows we're in the builder
window["##BUDIBASE_IN_BUILDER##"] = true
window["##BUDIBASE_APP_ID##"] = appId
- window["##BUDIBASE_PREVIEW_LAYOUT##"] = layout
window["##BUDIBASE_PREVIEW_SCREEN##"] = screen
window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId
window["##BUDIBASE_PREVIEW_ID##"] = Math.random()
@@ -90,7 +89,6 @@
window["##BUDIBASE_PREVIEW_NAVIGATION##"] = navigation
window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"] = hiddenComponentIds
window["##BUDIBASE_USED_PLUGINS##"] = usedPlugins
- window["##BUDIBASE_LOCATION##"] = location
window["##BUDIBASE_SNIPPETS##"] = snippets
window['##BUDIBASE_COMPONENT_ERRORS##'] = componentErrors
diff --git a/packages/server/src/integrations/s3.ts b/packages/server/src/integrations/s3.ts
index 488c22835a..88d00724f1 100644
--- a/packages/server/src/integrations/s3.ts
+++ b/packages/server/src/integrations/s3.ts
@@ -7,7 +7,7 @@ import {
ConnectionInfo,
} from "@budibase/types"
-import { S3 } from "@aws-sdk/client-s3"
+import { S3, S3ClientConfig } from "@aws-sdk/client-s3"
import csv from "csvtojson"
import stream from "stream"
@@ -157,13 +157,20 @@ const SCHEMA: Integration = {
}
class S3Integration implements IntegrationBase {
- private readonly config: S3Config
- private client
+ private readonly config: S3ClientConfig
+ private client: S3
constructor(config: S3Config) {
- this.config = config
- if (this.config.endpoint) {
- this.config.s3ForcePathStyle = true
+ this.config = {
+ forcePathStyle: config.s3ForcePathStyle || true,
+ credentials: {
+ accessKeyId: config.accessKeyId,
+ secretAccessKey: config.secretAccessKey,
+ },
+ region: config.region,
+ }
+ if (config.endpoint) {
+ this.config.forcePathStyle = true
} else {
delete this.config.endpoint
}
@@ -176,7 +183,9 @@ class S3Integration implements IntegrationBase {
connected: false,
}
try {
- await this.client.listBuckets()
+ await this.client.listBuckets({
+ MaxBuckets: 1,
+ })
response.connected = true
} catch (e: any) {
response.error = e.message as string
@@ -253,7 +262,7 @@ class S3Integration implements IntegrationBase {
.on("error", () => {
csvError = true
})
- fileStream.on("finish", () => {
+ fileStream.on("end", () => {
resolve(response)
})
}).catch(err => {
diff --git a/packages/server/src/sdk/app/datasources/datasources.ts b/packages/server/src/sdk/app/datasources/datasources.ts
index 84e1601152..d87ef69205 100644
--- a/packages/server/src/sdk/app/datasources/datasources.ts
+++ b/packages/server/src/sdk/app/datasources/datasources.ts
@@ -120,7 +120,7 @@ export function areRESTVariablesValid(datasource: Datasource) {
export function checkDatasourceTypes(schema: Integration, config: any) {
for (let key of Object.keys(config)) {
- if (!schema.datasource[key]) {
+ if (!schema.datasource?.[key]) {
continue
}
const type = schema.datasource[key].type
@@ -149,7 +149,9 @@ async function enrichDatasourceWithValues(
) as Datasource
processed.entities = entities
const definition = await getDefinition(processed.source)
- processed.config = checkDatasourceTypes(definition!, processed.config)
+ if (definition) {
+ processed.config = checkDatasourceTypes(definition, processed.config)
+ }
return {
datasource: processed,
envVars: env as Record,
diff --git a/packages/types/src/sdk/datasources.ts b/packages/types/src/sdk/datasources.ts
index 1f2a4d2127..349e9fbe0e 100644
--- a/packages/types/src/sdk/datasources.ts
+++ b/packages/types/src/sdk/datasources.ts
@@ -157,7 +157,7 @@ export interface Integration {
friendlyName: string
type?: string
iconUrl?: string
- datasource: DatasourceConfig
+ datasource?: DatasourceConfig
query: {
[key: string]: QueryDefinition
}
diff --git a/packages/types/src/ui/stores/misc.ts b/packages/types/src/ui/stores/misc.ts
index 275b388e9f..055ed85288 100644
--- a/packages/types/src/ui/stores/misc.ts
+++ b/packages/types/src/ui/stores/misc.ts
@@ -1,2 +1,8 @@
// type purely to capture structures that the type is unknown, but maybe known later
export type UIObject = Record
+
+export const enum DropPosition {
+ ABOVE = "above",
+ BELOW = "below",
+ INSIDE = "inside",
+}
diff --git a/packages/types/src/ui/stores/preview.ts b/packages/types/src/ui/stores/preview.ts
index 4d09366ff5..d9f5f2ac46 100644
--- a/packages/types/src/ui/stores/preview.ts
+++ b/packages/types/src/ui/stores/preview.ts
@@ -1 +1,2 @@
export type PreviewDevice = "desktop" | "tablet" | "mobile"
+export type ComponentContext = Record
diff --git a/packages/worker/package.json b/packages/worker/package.json
index ff21d88ac0..c5e32d11a0 100644
--- a/packages/worker/package.json
+++ b/packages/worker/package.json
@@ -58,7 +58,7 @@
"joi": "17.6.0",
"jsonwebtoken": "9.0.2",
"knex": "2.4.2",
- "koa": "2.13.4",
+ "koa": "2.15.4",
"koa-body": "4.2.0",
"koa-compress": "4.0.1",
"koa-passport": "4.1.4",
diff --git a/yarn.lock b/yarn.lock
index d9957a488e..efbac62306 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9421,7 +9421,7 @@ cookiejar@^2.1.4:
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b"
integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==
-cookies@0.8.0, cookies@~0.8.0:
+cookies@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90"
integrity sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==
@@ -9429,6 +9429,14 @@ cookies@0.8.0, cookies@~0.8.0:
depd "~2.0.0"
keygrip "~1.1.0"
+cookies@~0.9.0:
+ version "0.9.1"
+ resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.9.1.tgz#3ffed6f60bb4fb5f146feeedba50acc418af67e3"
+ integrity sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==
+ dependencies:
+ depd "~2.0.0"
+ keygrip "~1.1.0"
+
copyfiles@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.4.1.tgz#d2dcff60aaad1015f09d0b66e7f0f1c5cd3c5da5"
@@ -9820,10 +9828,10 @@ dd-trace@5.26.0:
shell-quote "^1.8.1"
tlhunter-sorted-set "^0.1.0"
-debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4:
- version "4.3.7"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
- integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
+debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
+ integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
dependencies:
ms "^2.1.3"
@@ -9841,10 +9849,10 @@ debug@^3.1.0, debug@^3.2.7:
dependencies:
ms "^2.1.1"
-debug@^4.3.5:
- version "4.4.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
- integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
+debug@~4.3.1, debug@~4.3.2, debug@~4.3.4:
+ version "4.3.7"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
+ integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
dependencies:
ms "^2.1.3"
@@ -14732,45 +14740,16 @@ koa2-ratelimit@1.1.1:
resolved "https://registry.yarnpkg.com/koa2-ratelimit/-/koa2-ratelimit-1.1.1.tgz#9c1d8257770e4a0a08063ba2ddcaf690fd457d23"
integrity sha512-IpxGMdZqEhMykW0yYKGVB4vDEacPvSBH4hNpDL38ABj3W2KHNLujAljGEDg7eEjXvrRbXRSWXzANhV3c9v7nyg==
-koa@2.13.4:
- version "2.13.4"
- resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.4.tgz#ee5b0cb39e0b8069c38d115139c774833d32462e"
- integrity sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==
+koa@2.15.4, koa@^2.13.1, koa@^2.13.4:
+ version "2.15.4"
+ resolved "https://registry.yarnpkg.com/koa/-/koa-2.15.4.tgz#7000b3d8354558671adb1ba1b1c09bedb5f8da75"
+ integrity sha512-7fNBIdrU2PEgLljXoPWoyY4r1e+ToWCmzS/wwMPbUNs7X+5MMET1ObhJBlUkF5uZG9B6QhM2zS1TsH6adegkiQ==
dependencies:
accepts "^1.3.5"
cache-content-type "^1.0.0"
content-disposition "~0.5.2"
content-type "^1.0.4"
- cookies "~0.8.0"
- debug "^4.3.2"
- delegates "^1.0.0"
- depd "^2.0.0"
- destroy "^1.0.4"
- encodeurl "^1.0.2"
- escape-html "^1.0.3"
- fresh "~0.5.2"
- http-assert "^1.3.0"
- http-errors "^1.6.3"
- is-generator-function "^1.0.7"
- koa-compose "^4.1.0"
- koa-convert "^2.0.0"
- on-finished "^2.3.0"
- only "~0.0.2"
- parseurl "^1.3.2"
- statuses "^1.5.0"
- type-is "^1.6.16"
- vary "^1.1.2"
-
-koa@^2.13.1, koa@^2.13.4:
- version "2.14.1"
- resolved "https://registry.yarnpkg.com/koa/-/koa-2.14.1.tgz#defb9589297d8eb1859936e777f3feecfc26925c"
- integrity sha512-USJFyZgi2l0wDgqkfD27gL4YGno7TfUkcmOe6UOLFOVuN+J7FwnNu4Dydl4CUQzraM1lBAiGed0M9OVJoT0Kqw==
- dependencies:
- accepts "^1.3.5"
- cache-content-type "^1.0.0"
- content-disposition "~0.5.2"
- content-type "^1.0.4"
- cookies "~0.8.0"
+ cookies "~0.9.0"
debug "^4.3.2"
delegates "^1.0.0"
depd "^2.0.0"