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}
@@ -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,