diff --git a/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte b/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte
index d70929469a..e4b3f972ba 100644
--- a/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte
+++ b/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte
@@ -9,6 +9,7 @@
Heading,
Drawer,
DrawerContent,
+ Icon,
} from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { store, currentAsset } from "builderStore"
@@ -22,6 +23,7 @@
import BindingBuilder from "components/integration/QueryBindingBuilder.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
+ import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
export let value = {}
export let otherSources
@@ -31,9 +33,12 @@
const dispatch = createEventDispatcher()
const arrayTypes = ["attachment", "array"]
+
let anchorRight, dropdownRight
let drawer
let tmpQueryParams
+ let tmpCustomData
+ let customDataValid = true
$: text = value?.label ?? "Choose an option"
$: tables = $tablesStore.list.map(m => ({
@@ -125,6 +130,10 @@
value: `{{ literal ${runtimeBinding} }}`,
}
})
+ $: custom = {
+ type: "custom",
+ label: "Custom",
+ }
const handleSelected = selected => {
dispatch("change", selected)
@@ -151,6 +160,11 @@
drawer.show()
}
+ const openCustomDrawer = () => {
+ tmpCustomData = value.data || ""
+ drawer.show()
+ }
+
const getQueryValue = queries => {
return queries.find(q => q._id === value._id) || value
}
@@ -162,6 +176,14 @@
})
drawer.hide()
}
+
+ const saveCustomData = () => {
+ handleSelected({
+ ...value,
+ data: tmpCustomData,
+ })
+ drawer.hide()
+ }
@@ -172,7 +194,9 @@
on:click={dropdownRight.show}
/>
{#if value?.type === "query"}
-
+
+
+
@@ -198,6 +222,31 @@
{/if}
+ {#if value?.type === "custom"}
+
+
+
+
+
+
+ Provide a JavaScript or JSON array to use as data
+
+ (tmpCustomData = event.detail)}
+ {bindings}
+ allowJS
+ allowHelpers
+ />
+
+ {/if}
@@ -285,17 +334,18 @@
{/each}
{/if}
- {#if otherSources?.length}
-
-
- Other
-
-
+
+
+ Other
+
+
+ - handleSelected(custom)}>{custom.label}
+ {#if otherSources?.length}
{#each otherSources as source}
- handleSelected(source)}>{source.label}
{/each}
-
- {/if}
+ {/if}
+
@@ -340,16 +390,7 @@
background-color: var(--spectrum-global-color-gray-200);
}
- i {
- margin-left: 5px;
- display: flex;
- align-items: center;
- transition: all 0.2s;
- }
-
- i:hover {
- transform: scale(1.1);
- font-weight: 600;
- cursor: pointer;
+ .icon {
+ margin-left: 8px;
}
diff --git a/packages/frontend-core/src/components/grid/lib/utils.js b/packages/frontend-core/src/components/grid/lib/utils.js
index 9383f69f66..d3898a3b18 100644
--- a/packages/frontend-core/src/components/grid/lib/utils.js
+++ b/packages/frontend-core/src/components/grid/lib/utils.js
@@ -1,6 +1,6 @@
export const getColor = (idx, opacity = 0.3) => {
if (idx == null || idx === -1) {
- return null
+ idx = 0
}
return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, ${opacity})`
}
diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js
index e6251a5afa..5b6f4fd7f9 100644
--- a/packages/frontend-core/src/components/grid/stores/rows.js
+++ b/packages/frontend-core/src/components/grid/stores/rows.js
@@ -420,7 +420,7 @@ export const createActions = context => {
// Ensure we have a unique _id.
// This means generating one for non DS+.
if (!newRow._id) {
- newRow._id = `fake-${Helpers.hashString(JSON.stringify(newRow))}`
+ newRow._id = `fake-${Math.random()}`
}
if (!rowCacheMap[newRow._id]) {
diff --git a/packages/frontend-core/src/fetch/CustomFetch.js b/packages/frontend-core/src/fetch/CustomFetch.js
new file mode 100644
index 0000000000..baa7ce3d4d
--- /dev/null
+++ b/packages/frontend-core/src/fetch/CustomFetch.js
@@ -0,0 +1,97 @@
+import DataFetch from "./DataFetch.js"
+
+export default class CustomFetch extends DataFetch {
+ getType(value) {
+ if (value == null) {
+ return "string"
+ }
+ const type = typeof value
+ if (type === "object") {
+ if (Array.isArray(value)) {
+ return "array"
+ }
+ return "json"
+ } else {
+ return type
+ }
+ }
+
+ // Parses the custom data into an array format
+ parseCustomData(data) {
+ if (!data) {
+ return []
+ }
+
+ // Happy path - already an array
+ if (Array.isArray(data)) {
+ return data
+ }
+
+ // Handle string cases
+ if (typeof data === "string") {
+ // Try JSON parsing
+ try {
+ data = JSON.parse(data)
+ if (Array.isArray(data)) {
+ return data
+ }
+ } catch (error) {
+ // Ignore
+ }
+
+ // Try a simple CSV
+ return data.split(",").map(x => x.trim())
+ }
+
+ // Other cases we just assume it's a single object and wrap it
+ return [data]
+ }
+
+ // Enriches the custom data to ensure the structure and format is usable
+ enrichCustomData(data) {
+ if (!data?.length) {
+ return []
+ }
+
+ // Filter out any invalid values
+ data = data.filter(x => x != null && !Array.isArray(x))
+
+ // Ensure all values are packed into objects
+ return data.map(x => (typeof x === "object" ? x : { value: x }))
+ }
+
+ getCustomData(datasource) {
+ return this.enrichCustomData(this.parseCustomData(datasource?.data))
+ }
+
+ async getDefinition(datasource) {
+ // Try and work out the schema from the array provided
+ let schema = {}
+ const data = this.getCustomData(datasource)
+
+ // Go through every object and extract all valid keys
+ for (let datum of data) {
+ for (let key of Object.keys(datum)) {
+ if (key === "_id") {
+ continue
+ }
+ if (!schema[key]) {
+ schema[key] = { type: this.getType(datum[key]) }
+ }
+ }
+ }
+
+ return {
+ schema,
+ }
+ }
+
+ async getData() {
+ const { datasource } = this.options
+ return {
+ rows: this.getCustomData(datasource),
+ hasNextPage: false,
+ cursor: null,
+ }
+ }
+}
diff --git a/packages/frontend-core/src/fetch/index.js b/packages/frontend-core/src/fetch/index.js
index 0edd07762b..d133942bb7 100644
--- a/packages/frontend-core/src/fetch/index.js
+++ b/packages/frontend-core/src/fetch/index.js
@@ -8,6 +8,7 @@ import FieldFetch from "./FieldFetch.js"
import JSONArrayFetch from "./JSONArrayFetch.js"
import UserFetch from "./UserFetch.js"
import GroupUserFetch from "./GroupUserFetch.js"
+import CustomFetch from "./CustomFetch.js"
const DataFetchMap = {
table: TableFetch,
@@ -17,6 +18,7 @@ const DataFetchMap = {
link: RelationshipFetch,
user: UserFetch,
groupUser: GroupUserFetch,
+ custom: CustomFetch,
// Client specific datasource types
provider: NestedProviderFetch,