Update URL settigns to be bindable inputs and update navigate action

This commit is contained in:
Andrew Kingston 2021-02-15 20:03:29 +00:00
parent 47dc6e8598
commit 37b9ba4f6a
16 changed files with 165 additions and 130 deletions

View File

@ -11,21 +11,24 @@ const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
/** /**
* Gets all bindable data context fields and instance fields. * Gets all bindable data context fields and instance fields.
*/ */
export const getBindableProperties = (rootComponent, componentId) => { export const getBindableProperties = (asset, componentId) => {
return getContextBindings(rootComponent, componentId) const contextBindings = getContextBindings(asset, componentId)
const userBindings = getUserBindings()
const urlBindings = getUrlBindings(asset, componentId)
return [...contextBindings, ...userBindings, ...urlBindings]
} }
/** /**
* Gets all data provider components above a component. * Gets all data provider components above a component.
*/ */
export const getDataProviderComponents = (rootComponent, componentId) => { export const getDataProviderComponents = (asset, componentId) => {
if (!rootComponent || !componentId) { if (!asset || !componentId) {
return [] return []
} }
// Get the component tree leading up to this component, ignoring the component // Get the component tree leading up to this component, ignoring the component
// itself // itself
const path = findComponentPath(rootComponent, componentId) const path = findComponentPath(asset.props, componentId)
path.pop() path.pop()
// Filter by only data provider components // Filter by only data provider components
@ -38,18 +41,14 @@ export const getDataProviderComponents = (rootComponent, componentId) => {
/** /**
* Gets all data provider components above a component. * Gets all data provider components above a component.
*/ */
export const getActionProviderComponents = ( export const getActionProviderComponents = (asset, componentId, actionType) => {
rootComponent, if (!asset || !componentId) {
componentId,
actionType
) => {
if (!rootComponent || !componentId) {
return [] return []
} }
// Get the component tree leading up to this component, ignoring the component // Get the component tree leading up to this component, ignoring the component
// itself // itself
const path = findComponentPath(rootComponent, componentId) const path = findComponentPath(asset.props, componentId)
path.pop() path.pop()
// Filter by only data provider components // Filter by only data provider components
@ -92,13 +91,12 @@ export const getDatasourceForProvider = component => {
} }
/** /**
* Gets all bindable data contexts. These are fields of schemas of data contexts * Gets all bindable data properties from component data contexts.
* provided by data provider components, such as lists or row detail components.
*/ */
export const getContextBindings = (rootComponent, componentId) => { const getContextBindings = (asset, componentId) => {
// Extract any components which provide data contexts // Extract any components which provide data contexts
const dataProviders = getDataProviderComponents(rootComponent, componentId) const dataProviders = getDataProviderComponents(asset, componentId)
let contextBindings = [] let bindings = []
// Create bindings for each data provider // Create bindings for each data provider
dataProviders.forEach(component => { dataProviders.forEach(component => {
@ -143,7 +141,7 @@ export const getContextBindings = (rootComponent, componentId) => {
runtimeBoundKey = `${key}_first` runtimeBoundKey = `${key}_first`
} }
contextBindings.push({ bindings.push({
type: "context", type: "context",
runtimeBinding: `${makePropSafe(component._id)}.${makePropSafe( runtimeBinding: `${makePropSafe(component._id)}.${makePropSafe(
runtimeBoundKey runtimeBoundKey
@ -157,7 +155,14 @@ export const getContextBindings = (rootComponent, componentId) => {
}) })
}) })
// Add logged in user bindings return bindings
}
/**
* Gets all bindable properties from the logged in user.
*/
const getUserBindings = () => {
let bindings = []
const tables = get(backendUiStore).tables const tables = get(backendUiStore).tables
const userTable = tables.find(table => table._id === TableNames.USERS) const userTable = tables.find(table => table._id === TableNames.USERS)
const schema = { const schema = {
@ -176,7 +181,7 @@ export const getContextBindings = (rootComponent, componentId) => {
runtimeBoundKey = `${key}_first` runtimeBoundKey = `${key}_first`
} }
contextBindings.push({ bindings.push({
type: "context", type: "context",
runtimeBinding: `user.${runtimeBoundKey}`, runtimeBinding: `user.${runtimeBoundKey}`,
readableBinding: `Current User.${key}`, readableBinding: `Current User.${key}`,
@ -187,7 +192,26 @@ export const getContextBindings = (rootComponent, componentId) => {
}) })
}) })
return contextBindings return bindings
}
/**
* Gets all bindable properties from URL parameters.
*/
const getUrlBindings = asset => {
const url = asset?.routing?.route ?? ""
const split = url.split("/")
let params = []
split.forEach(part => {
if (part.startsWith(":") && part.length > 1) {
params.push(part.replace(/:/g, "").replace(/\?/g, ""))
}
})
return params.map(param => ({
type: "context",
runtimeBinding: `url.${param}`,
readableBinding: `URL.${param}`,
}))
} }
/** /**

View File

@ -0,0 +1,84 @@
<script>
import { Icon, Input, Drawer, Body, Button } from "@budibase/bbui"
import {
readableToRuntimeBinding,
runtimeToReadableBinding,
} from "builderStore/dataBinding"
import BindingPanel from "components/design/PropertiesPanel/BindingPanel.svelte"
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
export let value = ""
export let bindings = []
let bindingDrawer
let tempValue = value
$: readableValue = runtimeToReadableBinding(bindings, value)
const handleClose = () => {
onChange(tempValue)
bindingDrawer.hide()
}
const onChange = value => {
dispatch("change", readableToRuntimeBinding(bindings, value))
}
</script>
<div class="control">
<Input
thin
value={readableValue}
on:change={event => onChange(event.detail)}
placeholder="/screen" />
<div class="icon" on:click={bindingDrawer.show}>
<Icon name="lightning" />
</div>
</div>
<Drawer bind:this={bindingDrawer} title="Bindings">
<div slot="description">
<Body extraSmall grey>
Add the objects on the left to enrich your text.
</Body>
</div>
<heading slot="buttons">
<Button thin blue on:click={handleClose}>Save</Button>
</heading>
<div slot="body">
<BindingPanel
value={readableValue}
close={handleClose}
on:update={event => (tempValue = event.detail)}
bindableProperties={bindings} />
</div>
</Drawer>
<style>
.control {
flex: 1;
margin-left: var(--spacing-l);
position: relative;
}
.icon {
right: 2px;
top: 2px;
bottom: 2px;
position: absolute;
align-items: center;
display: flex;
box-sizing: border-box;
padding-left: 7px;
border-left: 1px solid var(--grey-4);
background-color: var(--grey-2);
border-top-right-radius: var(--border-radius-m);
border-bottom-right-radius: var(--border-radius-m);
color: var(--grey-7);
font-size: 14px;
}
.icon:hover {
color: var(--ink);
cursor: pointer;
}
</style>

View File

@ -24,7 +24,7 @@
$: value && checkValid() $: value && checkValid()
$: bindableProperties = getBindableProperties( $: bindableProperties = getBindableProperties(
$currentAsset.props, $currentAsset,
$store.selectedComponentId $store.selectedComponentId
) )
$: dispatch("update", value) $: dispatch("update", value)

View File

@ -47,7 +47,7 @@
type: "query", type: "query",
})) }))
$: bindableProperties = getBindableProperties( $: bindableProperties = getBindableProperties(
$currentAsset.props, $currentAsset,
$store.selectedComponentId $store.selectedComponentId
) )
$: queryBindableProperties = bindableProperties.map(property => ({ $: queryBindableProperties = bindableProperties.map(property => ({

View File

@ -10,7 +10,7 @@
export let parameters export let parameters
$: dataProviderComponents = getDataProviderComponents( $: dataProviderComponents = getDataProviderComponents(
$currentAsset.props, $currentAsset,
$store.selectedComponentId $store.selectedComponentId
) )
$: { $: {

View File

@ -10,7 +10,7 @@
ds => ds._id === parameters.datasourceId ds => ds._id === parameters.datasourceId
) )
$: bindableProperties = getBindableProperties( $: bindableProperties = getBindableProperties(
$currentAsset.props, $currentAsset,
$store.selectedComponentId $store.selectedComponentId
).map(property => ({ ).map(property => ({
...property, ...property,

View File

@ -1,18 +1,23 @@
<script> <script>
import { DataList, Label } from "@budibase/bbui" import { Label } from "@budibase/bbui"
import { allScreens } from "builderStore" import { getBindableProperties } from "builderStore/dataBinding"
import { currentAsset, store } from "builderStore"
import DrawerBindableInput from "components/common/DrawerBindableInput.svelte"
export let parameters export let parameters
let bindingDrawer
let tempValue = parameters.url
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
</script> </script>
<div class="root"> <div class="root">
<Label size="m" color="dark">Screen</Label> <Label size="m" color="dark">Screen</Label>
<DataList secondary bind:value={parameters.url}> <DrawerBindableInput
<option value="" /> value={parameters.url}
{#each $allScreens as screen} on:change={value => (parameters.url = value.detail)}
<option value={screen.routing.route}>{screen.props._instanceName}</option> {bindings} />
{/each}
</DataList>
</div> </div>
<style> <style>

View File

@ -6,7 +6,7 @@
export let parameters export let parameters
$: dataProviders = getDataProviderComponents( $: dataProviders = getDataProviderComponents(
$currentAsset.props, $currentAsset,
$store.selectedComponentId $store.selectedComponentId
) )
</script> </script>

View File

@ -25,7 +25,7 @@
const emptyField = () => ({ name: "", value: "" }) const emptyField = () => ({ name: "", value: "" })
$: bindableProperties = getBindableProperties( $: bindableProperties = getBindableProperties(
$currentAsset.props, $currentAsset,
$store.selectedComponentId $store.selectedComponentId
) )

View File

@ -11,7 +11,7 @@
export let parameters export let parameters
$: dataProviderComponents = getDataProviderComponents( $: dataProviderComponents = getDataProviderComponents(
$currentAsset.props, $currentAsset,
$store.selectedComponentId $store.selectedComponentId
) )
$: providerComponent = dataProviderComponents.find( $: providerComponent = dataProviderComponents.find(

View File

@ -6,7 +6,7 @@
export let parameters export let parameters
$: actionProviders = getActionProviderComponents( $: actionProviders = getActionProviderComponents(
$currentAsset.props, $currentAsset,
$store.selectedComponentId, $store.selectedComponentId,
"ValidateForm" "ValidateForm"
) )

View File

@ -24,7 +24,7 @@
let valid let valid
$: bindableProperties = getBindableProperties( $: bindableProperties = getBindableProperties(
$currentAsset.props, $currentAsset,
$store.selectedComponentId $store.selectedComponentId
) )
$: safeValue = getSafeValue(value, props.defaultValue, bindableProperties) $: safeValue = getSafeValue(value, props.defaultValue, bindableProperties)

View File

@ -1,80 +0,0 @@
<script>
import { DataList } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { store, allScreens, currentAsset } from "builderStore"
import { getBindableProperties } from "builderStore/dataBinding"
export let value = ""
$: urls = getUrls($allScreens, $currentAsset, $store.selectedComponentId)
// Update value on blur
const dispatch = createEventDispatcher()
const handleBlur = () => dispatch("change", value)
// Get all valid screen URL, as well as detail screens which can be used in
// the current data context
const getUrls = (screens, asset, componentId) => {
// Get all screens which aren't detail screens
let urls = screens
.filter(screen => !screen.props._component.endsWith("/rowdetail"))
.map(screen => ({
name: screen.props._instanceName,
url: screen.routing.route,
sort: screen.props._component,
}))
// Add detail screens enriched with the current data context
const bindableProperties = getBindableProperties(asset.props, componentId)
screens
.filter(screen => screen.props._component.endsWith("/rowdetail"))
.forEach(detailScreen => {
// Find any _id bindings that match the detail screen's table
const binding = bindableProperties.find(p => {
return (
p.type === "context" &&
p.runtimeBinding.endsWith("._id") &&
p.tableId === detailScreen.props.table
)
})
if (binding) {
urls.push({
name: detailScreen.props._instanceName,
url: detailScreen.routing.route.replace(
":id",
`{{ ${binding.runtimeBinding} }}`
),
sort: detailScreen.props._component,
})
}
})
return urls
}
</script>
<div>
<DataList
editable
secondary
extraThin
on:blur={handleBlur}
on:change
bind:value>
<option value="" />
{#each urls as url}
<option value={url.url}>{url.name}</option>
{/each}
</DataList>
</div>
<style>
div {
flex: 1 1 auto;
display: flex;
flex-direction: row;
}
div :global(> div) {
flex: 1 1 auto;
}
</style>

View File

@ -18,7 +18,6 @@
import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte" import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte"
import SchemaSelect from "./PropertyControls/SchemaSelect.svelte" import SchemaSelect from "./PropertyControls/SchemaSelect.svelte"
import EventsEditor from "./PropertyControls/EventsEditor" import EventsEditor from "./PropertyControls/EventsEditor"
import ScreenSelect from "./PropertyControls/ScreenSelect.svelte"
import DetailScreenSelect from "./PropertyControls/DetailScreenSelect.svelte" import DetailScreenSelect from "./PropertyControls/DetailScreenSelect.svelte"
import { IconSelect } from "./PropertyControls/IconSelect" import { IconSelect } from "./PropertyControls/IconSelect"
import ColorPicker from "./PropertyControls/ColorPicker.svelte" import ColorPicker from "./PropertyControls/ColorPicker.svelte"
@ -62,7 +61,6 @@
text: Input, text: Input,
select: OptionSelect, select: OptionSelect,
datasource: DatasourceSelect, datasource: DatasourceSelect,
screen: ScreenSelect,
detailScreen: DetailScreenSelect, detailScreen: DetailScreenSelect,
boolean: Checkbox, boolean: Checkbox,
number: Input, number: Input,

View File

@ -44,7 +44,7 @@ export const enrichProps = async (props, context) => {
let enrichedProps = await enrichDataBindings(validProps, totalContext) let enrichedProps = await enrichDataBindings(validProps, totalContext)
// Enrich button actions if they exist // Enrich button actions if they exist
if (props._component.endsWith("/button") && enrichedProps.onClick) { if (props._component?.endsWith("/button") && enrichedProps.onClick) {
enrichedProps.onClick = enrichButtonActions( enrichedProps.onClick = enrichButtonActions(
enrichedProps.onClick, enrichedProps.onClick,
totalContext totalContext

View File

@ -137,9 +137,10 @@
"key": "subheading" "key": "subheading"
}, },
{ {
"type": "screen", "type": "text",
"label": "Link URL", "label": "Link URL",
"key": "destinationUrl" "key": "destinationUrl",
"placeholder": "/screen"
} }
] ]
}, },
@ -170,9 +171,10 @@
"key": "linkText" "key": "linkText"
}, },
{ {
"type": "screen", "type": "text",
"label": "Link Url", "label": "Link URL",
"key": "linkUrl" "key": "linkUrl",
"placeholder": "/screen"
}, },
{ {
"type": "color", "type": "color",
@ -339,9 +341,10 @@
"key": "text" "key": "text"
}, },
{ {
"type": "screen", "type": "text",
"label": "URL", "label": "URL",
"key": "url" "key": "url",
"placeholder": "/screen"
}, },
{ {
"type": "boolean", "type": "boolean",
@ -412,9 +415,10 @@
"key": "linkText" "key": "linkText"
}, },
{ {
"type": "screen", "type": "text",
"label": "Link URL", "label": "Link URL",
"key": "linkUrl" "key": "linkUrl",
"placeholder": "/screen"
}, },
{ {
"type": "color", "type": "color",