Merge branch 'master' of github.com:Budibase/budibase into fix/relationship-select
This commit is contained in:
commit
241bbf9b36
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||
"version": "3.4.7",
|
||||
"version": "3.4.9",
|
||||
"npmClient": "yarn",
|
||||
"concurrency": 20,
|
||||
"command": {
|
||||
|
|
|
@ -247,3 +247,7 @@ export function hasCircularStructure(json: any) {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function urlHasProtocol(url: string): boolean {
|
||||
return !!url.match(/^.+:\/\/.+$/)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import "@spectrum-css/typography/dist/index-vars.css"
|
||||
|
||||
export let size = "M"
|
||||
export let serif = false
|
||||
export let weight = null
|
||||
export let textAlign = null
|
||||
export let color = null
|
||||
export let size: "XS" | "S" | "M" | "L" | "XL" = "M"
|
||||
export let serif: boolean = false
|
||||
export let weight: string | null = null
|
||||
export let textAlign: string | null = null
|
||||
export let color: string | null = null
|
||||
</script>
|
||||
|
||||
<p
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
<script lang="ts">
|
||||
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()
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if hasUrlParams}
|
||||
<div class="url-test-section">
|
||||
<div class="info">
|
||||
<Label size="M">Set temporary URL variables for design preview</Label>
|
||||
</div>
|
||||
<div class="url-test-container">
|
||||
<div class="base-input">
|
||||
<Input disabled={true} value={baseInput} />
|
||||
</div>
|
||||
<div class="variable-input">
|
||||
<Input value={testValue} on:change={onVariableChange} {placeholder} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.url-test-section {
|
||||
width: 100%;
|
||||
margin-top: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.url-test-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.base-input {
|
||||
width: 98px;
|
||||
margin-right: -1px;
|
||||
}
|
||||
|
||||
.base-input :global(.spectrum-Textfield-input) {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
background-color: var(--spectrum-global-color-gray-200);
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
|
||||
.variable-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.variable-input :global(.spectrum-Textfield-input) {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
</style>
|
|
@ -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
|
||||
|
|
|
@ -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<string, any>
|
||||
|
||||
interface PreviewState {
|
||||
previewDevice: PreviewDevice
|
||||
|
@ -86,6 +85,10 @@ export class PreviewStore extends BudiStore<PreviewState> {
|
|||
this.sendEvent("builder-state", data)
|
||||
}
|
||||
|
||||
setUrlTestData(data: Record<string, any>) {
|
||||
this.sendEvent("builder-url-test-data", data)
|
||||
}
|
||||
|
||||
requestComponentContext() {
|
||||
this.sendEvent("request-context")
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
import UserBindingsProvider from "./context/UserBindingsProvider.svelte"
|
||||
import DeviceBindingsProvider from "./context/DeviceBindingsProvider.svelte"
|
||||
import StateBindingsProvider from "./context/StateBindingsProvider.svelte"
|
||||
import TestUrlBindingsProvider from "./context/TestUrlBindingsProvider.svelte"
|
||||
import RowSelectionProvider from "./context/RowSelectionProvider.svelte"
|
||||
import QueryParamsProvider from "./context/QueryParamsProvider.svelte"
|
||||
import SettingsBar from "./preview/SettingsBar.svelte"
|
||||
|
@ -169,108 +170,110 @@
|
|||
<StateBindingsProvider>
|
||||
<RowSelectionProvider>
|
||||
<QueryParamsProvider>
|
||||
<SnippetsProvider>
|
||||
<!-- Settings bar can be rendered outside of device preview -->
|
||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||
{#key $builderStore.selectedComponentId}
|
||||
{#if $builderStore.inBuilder}
|
||||
<SettingsBar />
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
<!-- Clip boundary for selection indicators -->
|
||||
<div
|
||||
id="clip-root"
|
||||
class:preview={$builderStore.inBuilder}
|
||||
class:tablet-preview={$builderStore.previewDevice ===
|
||||
"tablet"}
|
||||
class:mobile-preview={$builderStore.previewDevice ===
|
||||
"mobile"}
|
||||
>
|
||||
<!-- Actual app -->
|
||||
<div id="app-root">
|
||||
{#if showDevTools}
|
||||
<DevToolsHeader />
|
||||
<TestUrlBindingsProvider>
|
||||
<SnippetsProvider>
|
||||
<!-- Settings bar can be rendered outside of device preview -->
|
||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||
{#key $builderStore.selectedComponentId}
|
||||
{#if $builderStore.inBuilder}
|
||||
<SettingsBar />
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
<div id="app-body">
|
||||
{#if permissionError}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
You don't have permission to use this app
|
||||
</Heading>
|
||||
<Body size="S">
|
||||
Ask your administrator to grant you access
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else if !$screenStore.activeLayout}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
Something went wrong rendering your app
|
||||
</Heading>
|
||||
<Body size="S">
|
||||
Get in touch with support if this issue
|
||||
persists
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else if embedNoScreens}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
This Budibase app is not publicly accessible
|
||||
</Heading>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomThemeWrapper>
|
||||
{#key $screenStore.activeLayout._id}
|
||||
<Component
|
||||
isLayout
|
||||
instance={$screenStore.activeLayout.props}
|
||||
/>
|
||||
{/key}
|
||||
|
||||
<!-- Layers on top of app -->
|
||||
<NotificationDisplay />
|
||||
<ConfirmationDisplay />
|
||||
<PeekScreenDisplay />
|
||||
</CustomThemeWrapper>
|
||||
<!-- Clip boundary for selection indicators -->
|
||||
<div
|
||||
id="clip-root"
|
||||
class:preview={$builderStore.inBuilder}
|
||||
class:tablet-preview={$builderStore.previewDevice ===
|
||||
"tablet"}
|
||||
class:mobile-preview={$builderStore.previewDevice ===
|
||||
"mobile"}
|
||||
>
|
||||
<!-- Actual app -->
|
||||
<div id="app-root">
|
||||
{#if showDevTools}
|
||||
<DevToolsHeader />
|
||||
{/if}
|
||||
|
||||
{#if showDevTools}
|
||||
<DevTools />
|
||||
<div id="app-body">
|
||||
{#if permissionError}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
You don't have permission to use this app
|
||||
</Heading>
|
||||
<Body size="S">
|
||||
Ask your administrator to grant you access
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else if !$screenStore.activeLayout}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
Something went wrong rendering your app
|
||||
</Heading>
|
||||
<Body size="S">
|
||||
Get in touch with support if this issue
|
||||
persists
|
||||
</Body>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else if embedNoScreens}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
This Budibase app is not publicly accessible
|
||||
</Heading>
|
||||
</Layout>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomThemeWrapper>
|
||||
{#key $screenStore.activeLayout._id}
|
||||
<Component
|
||||
isLayout
|
||||
instance={$screenStore.activeLayout.props}
|
||||
/>
|
||||
{/key}
|
||||
|
||||
<!-- Layers on top of app -->
|
||||
<NotificationDisplay />
|
||||
<ConfirmationDisplay />
|
||||
<PeekScreenDisplay />
|
||||
</CustomThemeWrapper>
|
||||
{/if}
|
||||
|
||||
{#if showDevTools}
|
||||
<DevTools />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !$builderStore.inBuilder && $featuresStore.logoEnabled}
|
||||
<FreeFooter />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !$builderStore.inBuilder && $featuresStore.logoEnabled}
|
||||
<FreeFooter />
|
||||
<!-- Preview and dev tools utilities -->
|
||||
{#if $appStore.isDevApp}
|
||||
<SelectionIndicator />
|
||||
{/if}
|
||||
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
||||
<HoverIndicator />
|
||||
{/if}
|
||||
{#if $builderStore.inBuilder}
|
||||
<DNDHandler />
|
||||
<GridDNDHandler />
|
||||
<DNDSelectionIndicators />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Preview and dev tools utilities -->
|
||||
{#if $appStore.isDevApp}
|
||||
<SelectionIndicator />
|
||||
{/if}
|
||||
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
|
||||
<HoverIndicator />
|
||||
{/if}
|
||||
{#if $builderStore.inBuilder}
|
||||
<DNDHandler />
|
||||
<GridDNDHandler />
|
||||
<DNDSelectionIndicators />
|
||||
{/if}
|
||||
</div>
|
||||
</SnippetsProvider>
|
||||
</SnippetsProvider>
|
||||
</TestUrlBindingsProvider>
|
||||
</QueryParamsProvider>
|
||||
</RowSelectionProvider>
|
||||
</StateBindingsProvider>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<script>
|
||||
import Provider from "./Provider.svelte"
|
||||
import { routeStore } from "@/stores"
|
||||
</script>
|
||||
|
||||
<Provider key="url" data={$routeStore.testUrlParams}>
|
||||
<slot />
|
||||
</Provider>
|
|
@ -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
|
|
@ -10,6 +10,7 @@ import {
|
|||
eventStore,
|
||||
hoverStore,
|
||||
stateStore,
|
||||
routeStore,
|
||||
} from "@/stores"
|
||||
import { get } from "svelte/store"
|
||||
import { initWebsocket } from "@/websocket"
|
||||
|
@ -188,6 +189,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<string, string> = {}
|
||||
segments.forEach((segment, index) => {
|
||||
if (segment.startsWith(":") && index < testSegments.length) {
|
||||
params[segment.slice(1)] = testSegments[index]
|
||||
}
|
||||
})
|
||||
|
||||
const queryParams: Record<string, string> = {}
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}`
|
||||
}
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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<string, string>,
|
||||
|
|
|
@ -157,7 +157,7 @@ export interface Integration {
|
|||
friendlyName: string
|
||||
type?: string
|
||||
iconUrl?: string
|
||||
datasource: DatasourceConfig
|
||||
datasource?: DatasourceConfig
|
||||
query: {
|
||||
[key: string]: QueryDefinition
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export type PreviewDevice = "desktop" | "tablet" | "mobile"
|
||||
export type ComponentContext = Record<string, any>
|
||||
|
|
Loading…
Reference in New Issue