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"