Add new data bindings for context bindings and most work for component bindings
This commit is contained in:
parent
bd237b3e7c
commit
5211d316f8
|
@ -1,4 +1,4 @@
|
|||
packages/builder/src/userInterface/CurrentItemPreview.svelte
|
||||
packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte
|
||||
public
|
||||
dist
|
||||
packages/server/builder
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import { get } from "svelte/store"
|
||||
import { backendUiStore, store } from "builderStore"
|
||||
import { findAllMatchingComponents, findComponentPath } from "./storeUtils"
|
||||
|
||||
/**
|
||||
* Gets all bindable data context fields and instance fields.
|
||||
*/
|
||||
export const getBindableProperties = (rootComponent, componentId) => {
|
||||
const bindableContexts = getBindableContexts(rootComponent, componentId)
|
||||
const bindableComponents = getBindableComponents(rootComponent)
|
||||
return [...bindableContexts, ...bindableComponents]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all bindable data contexts. These are fields of schemas of data contexts
|
||||
* provided by data provider components, such as lists or row detail components.
|
||||
*/
|
||||
export const getBindableContexts = (rootComponent, componentId) => {
|
||||
if (!rootComponent || !componentId) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Get the component tree leading up to this component
|
||||
const path = findComponentPath(rootComponent, componentId)
|
||||
path.pop()
|
||||
|
||||
// Extract any components which provide data contexts
|
||||
const dataProviders = path.filter(component => {
|
||||
const def = store.actions.components.getDefinition(component._component)
|
||||
return def?.dataProvider
|
||||
})
|
||||
|
||||
let contexts = []
|
||||
dataProviders.forEach(provider => {
|
||||
if (!provider.datasource) {
|
||||
return
|
||||
}
|
||||
const { schema, table } = getSchemaForDatasource(provider.datasource)
|
||||
Object.entries(schema).forEach(([key, schema]) => {
|
||||
// Replace certain bindings with a new property to help display components
|
||||
let runtimeBoundKey = key
|
||||
if (schema.type === "link") {
|
||||
runtimeBoundKey = `${key}_count`
|
||||
} else if (schema.type === "attachment") {
|
||||
runtimeBoundKey = `${key}_first`
|
||||
}
|
||||
|
||||
contexts.push({
|
||||
type: "context",
|
||||
runtimeBinding: `${provider._id}.${runtimeBoundKey}`,
|
||||
readableBinding: `${provider._instanceName}.${table.name}.${key}`,
|
||||
fieldSchema: schema,
|
||||
providerId: provider._id,
|
||||
})
|
||||
})
|
||||
})
|
||||
return contexts
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all bindable components. These are form components which allow their
|
||||
* values to be bound to.
|
||||
*/
|
||||
export const getBindableComponents = rootComponent => {
|
||||
if (!rootComponent) {
|
||||
return []
|
||||
}
|
||||
const componentSelector = component => {
|
||||
const type = component._component
|
||||
const definition = store.actions.components.getDefinition(type)
|
||||
return definition.bindable
|
||||
}
|
||||
const components = findAllMatchingComponents(rootComponent, componentSelector)
|
||||
console.log(components)
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a schema for a datasource object.
|
||||
*/
|
||||
const getSchemaForDatasource = datasource => {
|
||||
const tables = get(backendUiStore).tables
|
||||
const { type } = datasource
|
||||
const table = tables.find(table => table._id === datasource.tableId)
|
||||
let schema = {}
|
||||
if (table) {
|
||||
if (type === "table") {
|
||||
schema = table.schema ?? {}
|
||||
} else if (type === "view") {
|
||||
schema = table.views?.[datasource.name]?.schema ?? {}
|
||||
} else if (type === "link") {
|
||||
schema = table.schema ?? {}
|
||||
}
|
||||
}
|
||||
return { schema, table }
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
import { walkProps } from "./storeUtils"
|
||||
import { get_capitalised_name } from "../helpers"
|
||||
import { get } from "svelte/store"
|
||||
import { allScreens } from "builderStore"
|
||||
import { FrontendTypes } from "../constants"
|
||||
import { currentAsset } from "."
|
||||
|
||||
export default function(component, state) {
|
||||
const capitalised = get_capitalised_name(
|
||||
component.name || component._component
|
||||
)
|
||||
|
||||
const matchingComponents = []
|
||||
|
||||
const findMatches = props => {
|
||||
walkProps(props, c => {
|
||||
const thisInstanceName = get_capitalised_name(c._instanceName)
|
||||
if ((thisInstanceName || "").startsWith(capitalised)) {
|
||||
matchingComponents.push(thisInstanceName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// check layouts first
|
||||
for (let layout of state.layouts) {
|
||||
findMatches(layout.props)
|
||||
}
|
||||
|
||||
// if viewing screen, check current screen for duplicate
|
||||
if (state.currentFrontEndType === FrontendTypes.SCREEN) {
|
||||
findMatches(get(currentAsset).props)
|
||||
} else {
|
||||
// viewing a layout - need to find against all screens
|
||||
for (let screen of get(allScreens)) {
|
||||
findMatches(screen.props)
|
||||
}
|
||||
}
|
||||
|
||||
let index = 1
|
||||
let name
|
||||
while (!name) {
|
||||
const tryName = `${capitalised || "Copy"} ${index}`
|
||||
if (!matchingComponents.includes(tryName)) name = tryName
|
||||
index++
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
|
@ -30,9 +30,10 @@ export function runtimeToReadableBinding(bindableProperties, textWithBindings) {
|
|||
const binding = bindableProperties.find(({ runtimeBinding }) => {
|
||||
return v === `{{ ${runtimeBinding} }}`
|
||||
})
|
||||
if (binding) {
|
||||
temp = temp.replace(v, `{{ ${binding.readableBinding} }}`)
|
||||
}
|
||||
temp = temp.replace(
|
||||
v,
|
||||
`{{ ${binding?.readableBinding ?? "Invalid binding"} }}`
|
||||
)
|
||||
})
|
||||
|
||||
return temp
|
||||
|
|
|
@ -497,7 +497,10 @@ export const getFrontendStore = () => {
|
|||
const path = findComponentPath(selectedAsset.props, component._id) || []
|
||||
|
||||
// Remove root entry since it's the screen or layout
|
||||
return path.slice(1).join("/")
|
||||
return path
|
||||
.slice(1)
|
||||
.map(component => component._id)
|
||||
.join("/")
|
||||
},
|
||||
links: {
|
||||
save: async (url, title) => {
|
||||
|
|
|
@ -40,22 +40,45 @@ export const findComponentParent = (rootComponent, id, parentComponent) => {
|
|||
*/
|
||||
export const findComponentPath = (rootComponent, id, path = []) => {
|
||||
if (!rootComponent || !id) {
|
||||
return null
|
||||
return []
|
||||
}
|
||||
if (rootComponent._id === id) {
|
||||
return [...path, id]
|
||||
return [...path, rootComponent]
|
||||
}
|
||||
if (!rootComponent._children) {
|
||||
return null
|
||||
return []
|
||||
}
|
||||
for (const child of rootComponent._children) {
|
||||
const newPath = [...path, rootComponent._id]
|
||||
const newPath = [...path, rootComponent]
|
||||
const childResult = findComponentPath(child, id, newPath)
|
||||
if (childResult != null) {
|
||||
if (childResult?.length) {
|
||||
return childResult
|
||||
}
|
||||
}
|
||||
return null
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurses through the component tree and finds all components of a certain
|
||||
* type.
|
||||
*/
|
||||
export const findAllMatchingComponents = (rootComponent, selector) => {
|
||||
if (!rootComponent || !selector) {
|
||||
return []
|
||||
}
|
||||
let components = []
|
||||
if (rootComponent._children) {
|
||||
rootComponent._children.forEach(child => {
|
||||
components = [
|
||||
...components,
|
||||
...findAllMatchingComponents(child, selector),
|
||||
]
|
||||
})
|
||||
}
|
||||
if (selector(rootComponent)) {
|
||||
components.push(rootComponent)
|
||||
}
|
||||
return components.reverse()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,12 +1,32 @@
|
|||
[
|
||||
"container",
|
||||
"grid",
|
||||
"list",
|
||||
{
|
||||
"name": "Category",
|
||||
"name": "Cards",
|
||||
"icon": "ri-file-edit-line",
|
||||
"children": [
|
||||
"container"
|
||||
"card",
|
||||
"stackedlist"
|
||||
]
|
||||
},
|
||||
"grid",
|
||||
"screenslot",
|
||||
"button"
|
||||
{
|
||||
"name": "Forms",
|
||||
"icon": "ri-file-edit-line",
|
||||
"children": [
|
||||
"form",
|
||||
"input",
|
||||
"richtext",
|
||||
"datepicker"
|
||||
]
|
||||
},
|
||||
"button",
|
||||
"text",
|
||||
{
|
||||
"name": "Other",
|
||||
"icon": "ri-file-edit-line",
|
||||
"children": [
|
||||
"screenslot"
|
||||
]
|
||||
}
|
||||
]
|
|
@ -77,18 +77,16 @@ export default function() {
|
|||
|
||||
// Stop if the target and source are the same
|
||||
if (state.targetComponent === state.dragged) {
|
||||
console.log("same component")
|
||||
return
|
||||
}
|
||||
// Stop if the target or source are null
|
||||
if (!state.targetComponent || !state.dragged) {
|
||||
console.log("null component")
|
||||
return
|
||||
}
|
||||
// Stop if the target is a child of source
|
||||
const path = findComponentPath(state.dragged, state.targetComponent._id)
|
||||
if (path?.includes(state.targetComponent._id)) {
|
||||
console.log("target is child of course")
|
||||
const ids = path.map(component => component._id)
|
||||
if (ids.includes(state.targetComponent._id)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script>
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import Input from "./Input.svelte"
|
||||
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||
import { store, currentAsset } from "builderStore"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
import {
|
||||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
|
@ -15,67 +15,48 @@
|
|||
export let componentInstance = {}
|
||||
export let control = null
|
||||
export let key = ""
|
||||
export let value
|
||||
export let type = ""
|
||||
export let value = null
|
||||
export let props = {}
|
||||
export let onChange = () => {}
|
||||
|
||||
let temporaryBindableValue = value
|
||||
let bindableProperties = []
|
||||
let anchor
|
||||
let dropdown
|
||||
|
||||
function handleClose() {
|
||||
handleChange(key, temporaryBindableValue)
|
||||
}
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset.props,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
$: safeValue = getSafeValue(value, props.defaultValue, bindableProperties)
|
||||
$: replaceBindings = val => readableToRuntimeBinding(bindableProperties, val)
|
||||
|
||||
function getBindableProperties() {
|
||||
// Get all bindableProperties
|
||||
bindableProperties = fetchBindableProperties({
|
||||
componentInstanceId: $store.selectedComponentId,
|
||||
components: $store.components,
|
||||
screen: $currentAsset,
|
||||
tables: $backendUiStore.tables,
|
||||
})
|
||||
}
|
||||
|
||||
function replaceBindings(textWithBindings) {
|
||||
getBindableProperties()
|
||||
textWithBindings = readableToRuntimeBinding(
|
||||
bindableProperties,
|
||||
textWithBindings
|
||||
)
|
||||
onChange(key, textWithBindings)
|
||||
}
|
||||
|
||||
function handleChange(key, v) {
|
||||
let innerVal = v
|
||||
if (typeof v === "object") {
|
||||
if ("detail" in v) {
|
||||
innerVal = v.detail
|
||||
} else if ("target" in v) {
|
||||
innerVal = props.valueKey ? v.target[props.valueKey] : v.target.value
|
||||
// Handle a value change of any type
|
||||
// String values have any bindings handled
|
||||
const handleChange = value => {
|
||||
let innerVal = value
|
||||
if (value && typeof value === "object") {
|
||||
if ("detail" in value) {
|
||||
innerVal = value.detail
|
||||
} else if ("target" in value) {
|
||||
innerVal = value.target.value
|
||||
}
|
||||
}
|
||||
if (typeof innerVal === "string") {
|
||||
replaceBindings(innerVal)
|
||||
onChange(replaceBindings(innerVal))
|
||||
} else {
|
||||
onChange(key, innerVal)
|
||||
onChange(innerVal)
|
||||
}
|
||||
}
|
||||
|
||||
const safeValue = () => {
|
||||
getBindableProperties()
|
||||
|
||||
let temp = runtimeToReadableBinding(bindableProperties, value)
|
||||
|
||||
return value == null && props.initialValue !== undefined
|
||||
? props.initialValue
|
||||
: temp
|
||||
// The "safe" value is the value with eny bindings made readable
|
||||
// If there is no value set, any default value is used
|
||||
const getSafeValue = (value, defaultValue, bindableProperties) => {
|
||||
const enriched = runtimeToReadableBinding(bindableProperties, value)
|
||||
return enriched == null && defaultValue !== undefined
|
||||
? defaultValue
|
||||
: enriched
|
||||
}
|
||||
|
||||
// Incase the component has a different value key name
|
||||
const handlevalueKey = value =>
|
||||
props.valueKey ? { [props.valueKey]: safeValue() } : { value: safeValue() }
|
||||
</script>
|
||||
|
||||
<div class="property-control" bind:this={anchor}>
|
||||
|
@ -84,13 +65,13 @@
|
|||
<svelte:component
|
||||
this={control}
|
||||
{componentInstance}
|
||||
{...handlevalueKey(value)}
|
||||
on:change={val => handleChange(key, val)}
|
||||
onChange={val => handleChange(key, val)}
|
||||
value={safeValue}
|
||||
on:change={handleChange}
|
||||
onChange={handleChange}
|
||||
{...props}
|
||||
name={key} />
|
||||
</div>
|
||||
{#if bindable && !key.startsWith('_') && control === Input}
|
||||
{#if bindable && !key.startsWith('_') && type === 'text'}
|
||||
<div
|
||||
class="icon"
|
||||
data-cy={`${key}-binding-button`}
|
||||
|
@ -99,14 +80,14 @@
|
|||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if control == Input}
|
||||
{#if type === 'text'}
|
||||
<DropdownMenu
|
||||
on:close={handleClose}
|
||||
on:close={() => handleChange(temporaryBindableValue)}
|
||||
bind:this={dropdown}
|
||||
{anchor}
|
||||
align="right">
|
||||
<BindingDropdown
|
||||
{...handlevalueKey(value)}
|
||||
value={safeValue}
|
||||
close={dropdown.hide}
|
||||
on:update={e => (temporaryBindableValue = e.detail)}
|
||||
{bindableProperties} />
|
||||
|
|
|
@ -22,11 +22,12 @@
|
|||
<div>
|
||||
{#each properties as prop}
|
||||
<PropertyControl
|
||||
bindable={false}
|
||||
label={`${prop.label}${hasPropChanged(style, prop) ? ' *' : ''}`}
|
||||
control={prop.control}
|
||||
key={prop.key}
|
||||
value={style[prop.key]}
|
||||
onChange={(key, value) => onStyleChanged(styleCategory, key, value)}
|
||||
onChange={value => onStyleChanged(styleCategory, prop.key, value)}
|
||||
props={{ options: prop.options, placeholder: prop.placeholder }} />
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { Icon, DropdownMenu, Heading } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let anchorRight, dropdownRight
|
||||
|
@ -31,18 +31,17 @@
|
|||
return [...acc, ...viewsArr]
|
||||
}, [])
|
||||
|
||||
$: bindableProperties = fetchBindableProperties({
|
||||
componentInstanceId: $store.selectedComponentId,
|
||||
components: $store.components,
|
||||
screen: $currentAsset,
|
||||
tables: $backendUiStore.tables,
|
||||
})
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset.props,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
$: console.log(bindableProperties)
|
||||
|
||||
$: links = bindableProperties
|
||||
.filter(x => x.fieldSchema?.type === "link")
|
||||
.map(property => {
|
||||
return {
|
||||
providerId: property.instance._id,
|
||||
providerId: property.providerId,
|
||||
label: property.readableBinding,
|
||||
fieldName: property.fieldSchema.name,
|
||||
name: `all_${property.fieldSchema.tableId}`,
|
||||
|
|
|
@ -25,6 +25,13 @@
|
|||
export let onScreenPropChange = () => {}
|
||||
export let showDisplayName = false
|
||||
|
||||
const layoutDefinition = []
|
||||
const screenDefinition = [
|
||||
{ key: "description", label: "Description", control: Input },
|
||||
{ key: "routing.route", label: "Route", control: Input },
|
||||
{ key: "routing.roleId", label: "Access", control: RoleSelect },
|
||||
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
||||
]
|
||||
const assetProps = [
|
||||
"title",
|
||||
"description",
|
||||
|
@ -34,12 +41,15 @@
|
|||
]
|
||||
|
||||
$: settings = componentDefinition?.settings ?? []
|
||||
$: isLayout = assetInstance && assetInstance.favicon
|
||||
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition
|
||||
|
||||
const controlMap = {
|
||||
text: Input,
|
||||
select: OptionSelect,
|
||||
datasource: TableViewSelect,
|
||||
detailURL: DetailScreenSelect,
|
||||
screen: ScreenSelect,
|
||||
detailScreen: DetailScreenSelect,
|
||||
boolean: Checkbox,
|
||||
number: Input,
|
||||
event: EventsEditor,
|
||||
|
@ -48,17 +58,6 @@
|
|||
return controlMap[type]
|
||||
}
|
||||
|
||||
const propExistsOnComponentDef = prop =>
|
||||
assetProps.includes(prop) || prop in (componentDefinition?.props ?? {})
|
||||
|
||||
const layoutDefinition = []
|
||||
const screenDefinition = [
|
||||
{ key: "description", label: "Description", control: Input },
|
||||
{ key: "routing.route", label: "Route", control: Input },
|
||||
{ key: "routing.roleId", label: "Access", control: RoleSelect },
|
||||
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
||||
]
|
||||
|
||||
const canRenderControl = setting => {
|
||||
const control = getControl(setting?.type)
|
||||
if (!control) {
|
||||
|
@ -70,29 +69,27 @@
|
|||
return true
|
||||
}
|
||||
|
||||
$: isLayout = assetInstance && assetInstance.favicon
|
||||
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition
|
||||
|
||||
const onInstanceNameChange = (_, name) => {
|
||||
const onInstanceNameChange = name => {
|
||||
onChange("_instanceName", name)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="settings-view-container">
|
||||
{#if assetInstance}
|
||||
{#each assetDefinition as def}
|
||||
{#each assetDefinition as def (`${componentInstance._id}-${def.key}`)}
|
||||
<PropertyControl
|
||||
bindable={false}
|
||||
control={def.control}
|
||||
label={def.label}
|
||||
key={def.key}
|
||||
value={get(assetInstance, def.key)}
|
||||
onChange={onScreenPropChange} />
|
||||
onChange={val => onScreenPropChange(def.key, val)} />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if showDisplayName}
|
||||
<PropertyControl
|
||||
bindable={false}
|
||||
control={Input}
|
||||
label="Name"
|
||||
key="_instanceName"
|
||||
|
@ -101,15 +98,16 @@
|
|||
{/if}
|
||||
|
||||
{#if settings && settings.length > 0}
|
||||
{#each settings as setting}
|
||||
{#each settings as setting (`${componentInstance._id}-${setting.key}`)}
|
||||
{#if canRenderControl(setting)}
|
||||
<PropertyControl
|
||||
type={setting.type}
|
||||
control={getControl(setting.type)}
|
||||
label={setting.label}
|
||||
key={setting.key}
|
||||
value={componentInstance[setting.key] ?? componentInstance[setting.key]?.defaultValue}
|
||||
{componentInstance}
|
||||
{onChange}
|
||||
onChange={val => onChange(setting.key, val)}
|
||||
props={{ options: setting.options }} />
|
||||
{/if}
|
||||
{/each}
|
||||
|
|
|
@ -1,225 +1,10 @@
|
|||
import Input from "./PropertiesPanel/PropertyControls/Input.svelte"
|
||||
import OptionSelect from "./PropertiesPanel/PropertyControls/OptionSelect.svelte"
|
||||
import MultiTableViewFieldSelect from "./PropertiesPanel/PropertyControls/MultiTableViewFieldSelect.svelte"
|
||||
import Checkbox from "../common/Checkbox.svelte"
|
||||
import TableSelect from "components/userInterface/PropertyControls/TableSelect.svelte"
|
||||
import TableViewSelect from "components/userInterface/PropertyControls/TableViewSelect.svelte"
|
||||
import TableViewFieldSelect from "components/userInterface/PropertyControls/TableViewFieldSelect.svelte"
|
||||
import Event from "components/userInterface/PropertyControls/EventsEditor/EventPropertyControl.svelte"
|
||||
import ScreenSelect from "components/userInterface/PropertyControls/ScreenSelect.svelte"
|
||||
import DetailScreenSelect from "components/userInterface/PropertyControls/DetailScreenSelect.svelte"
|
||||
import { IconSelect } from "components/userInterface/PropertyControls/IconSelect"
|
||||
import Colorpicker from "@budibase/colorpicker"
|
||||
|
||||
import { all } from "./propertyCategories.js"
|
||||
/*
|
||||
{ label: "N/A ", value: "N/A" },
|
||||
{ label: "Flex", value: "flex" },
|
||||
{ label: "Inline Flex", value: "inline-flex" },
|
||||
*/
|
||||
|
||||
export default {
|
||||
categories: [
|
||||
{
|
||||
name: "Repeater",
|
||||
_component: "@budibase/standard-components/list",
|
||||
description: "Renders all children once per row, of a given table",
|
||||
icon: "ri-list-check-2",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
{
|
||||
label: "Data",
|
||||
key: "datasource",
|
||||
control: TableViewSelect,
|
||||
},
|
||||
],
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Form",
|
||||
icon: "ri-file-edit-line",
|
||||
isCategory: true,
|
||||
children: [
|
||||
{
|
||||
_component: "@budibase/standard-components/form",
|
||||
name: "Form",
|
||||
icon: "ri-file-edit-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "@budibase/standard-components/textfield",
|
||||
name: "Text Field",
|
||||
description:
|
||||
"A textfield component that allows the user to input text.",
|
||||
icon: "ri-edit-box-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
{ label: "Label", key: "label", control: Input },
|
||||
{
|
||||
label: "Type",
|
||||
key: "type",
|
||||
control: OptionSelect,
|
||||
options: ["text", "password"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// {
|
||||
// _component: "@budibase/standard-components/richtext",
|
||||
// name: "Rich Text",
|
||||
// description:
|
||||
// "A component that allows the user to enter long form text.",
|
||||
// icon: "ri-edit-box-line",
|
||||
// properties: {
|
||||
// design: { ...all },
|
||||
// settings: [],
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// _component: "@budibase/standard-components/datepicker",
|
||||
// name: "Date Picker",
|
||||
// description: "A basic date picker component",
|
||||
// icon: "ri-calendar-line",
|
||||
// children: [],
|
||||
// properties: {
|
||||
// design: { ...all },
|
||||
// settings: [
|
||||
// { label: "Placeholder", key: "placeholder", control: Input },
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Card",
|
||||
icon: "ri-archive-drawer-line",
|
||||
isCategory: true,
|
||||
children: [
|
||||
{
|
||||
_component: "@budibase/standard-components/stackedlist",
|
||||
name: "Stacked List",
|
||||
icon: "ri-archive-drawer-line",
|
||||
description:
|
||||
"A basic card component that can contain content and actions.",
|
||||
children: [],
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
{
|
||||
label: "Image",
|
||||
key: "imageUrl",
|
||||
control: Input,
|
||||
placeholder: "{{{context.Image}}}",
|
||||
},
|
||||
{
|
||||
label: "Heading",
|
||||
key: "heading",
|
||||
control: Input,
|
||||
placeholder: "{{context.Heading}}",
|
||||
},
|
||||
{
|
||||
label: "Text 1",
|
||||
key: "text1",
|
||||
control: Input,
|
||||
placeholder: "{{context.Text 1}}",
|
||||
},
|
||||
{
|
||||
label: "Text 2",
|
||||
key: "text2",
|
||||
control: Input,
|
||||
placeholder: "{{context.Text 2}}",
|
||||
},
|
||||
{
|
||||
label: "Text 3",
|
||||
key: "text3",
|
||||
control: Input,
|
||||
placeholder: "{{context.Text 3}}",
|
||||
},
|
||||
{
|
||||
label: "Link URL",
|
||||
key: "destinationUrl",
|
||||
control: ScreenSelect,
|
||||
placeholder: "/table/_id",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "@budibase/standard-components/card",
|
||||
name: "Vertical",
|
||||
description:
|
||||
"A basic card component that can contain content and actions.",
|
||||
icon: "ri-layout-column-line",
|
||||
children: [],
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
{
|
||||
label: "Image",
|
||||
key: "imageUrl",
|
||||
control: Input,
|
||||
placeholder: "Image",
|
||||
},
|
||||
{
|
||||
label: "Heading",
|
||||
key: "heading",
|
||||
control: Input,
|
||||
placeholder: "Heading",
|
||||
},
|
||||
{
|
||||
label: "Description",
|
||||
key: "description",
|
||||
control: Input,
|
||||
placeholder: "Description",
|
||||
},
|
||||
{
|
||||
label: "Link Text",
|
||||
key: "linkText",
|
||||
control: Input,
|
||||
placeholder: "Link Text",
|
||||
},
|
||||
{
|
||||
label: "Link Url",
|
||||
key: "linkUrl",
|
||||
control: ScreenSelect,
|
||||
placeholder: "Link URL",
|
||||
},
|
||||
{
|
||||
label: "Link Color",
|
||||
key: "linkColor",
|
||||
control: Colorpicker,
|
||||
defaultValue: "#000",
|
||||
},
|
||||
{
|
||||
label: "Hover Color",
|
||||
key: "linkHoverColor",
|
||||
control: Colorpicker,
|
||||
defaultValue: "#222",
|
||||
},
|
||||
{
|
||||
label: "Image Height",
|
||||
key: "imageHeight",
|
||||
control: OptionSelect,
|
||||
options: ["12rem", "16rem", "20rem", "24rem"],
|
||||
placeholder: "Image Height",
|
||||
},
|
||||
{
|
||||
label: "Card Width",
|
||||
key: "cardWidth",
|
||||
control: OptionSelect,
|
||||
options: ["16rem", "20rem", "24rem"],
|
||||
placeholder: "Card Width",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "@budibase/standard-components/cardhorizontal",
|
||||
name: "Horizontal",
|
||||
|
@ -957,40 +742,7 @@ export default {
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "@budibase/standard-components/text",
|
||||
name: "Paragraph",
|
||||
description: "A component for displaying paragraph text.",
|
||||
icon: "ri-paragraph",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
{
|
||||
label: "Text",
|
||||
key: "text",
|
||||
control: Input,
|
||||
},
|
||||
{
|
||||
label: "Type",
|
||||
key: "type",
|
||||
control: OptionSelect,
|
||||
options: [
|
||||
"none",
|
||||
"bold",
|
||||
"strong",
|
||||
"italic",
|
||||
"emphasis",
|
||||
"mark",
|
||||
"small",
|
||||
"del",
|
||||
"ins",
|
||||
"sub",
|
||||
"sup",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
_component: "@budibase/standard-components/image",
|
||||
name: "Image",
|
||||
|
|
|
@ -36,57 +36,6 @@
|
|||
"logon"
|
||||
]
|
||||
},
|
||||
"input": {
|
||||
"name": "Textfield",
|
||||
"bindable": "value",
|
||||
"description": "An HTML input",
|
||||
"props": {
|
||||
"type": {
|
||||
"type": "options",
|
||||
"options": [
|
||||
"text",
|
||||
"password",
|
||||
"checkbox",
|
||||
"color",
|
||||
"date",
|
||||
"datetime-local",
|
||||
"email",
|
||||
"file",
|
||||
"hidden",
|
||||
"image",
|
||||
"month",
|
||||
"number",
|
||||
"radio",
|
||||
"range",
|
||||
"reset",
|
||||
"search",
|
||||
"submit",
|
||||
"tel",
|
||||
"time",
|
||||
"week"
|
||||
],
|
||||
"default": "text"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"form"
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"name": "Text",
|
||||
"description": "stylable block of text",
|
||||
"props": {
|
||||
"text": "string",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"default": "none"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"div",
|
||||
"container"
|
||||
]
|
||||
},
|
||||
"richtext": {
|
||||
"name": "Rich Text",
|
||||
"description": "A component that allows the user to enter long form text.",
|
||||
|
@ -109,40 +58,7 @@
|
|||
}
|
||||
},
|
||||
|
||||
"dataform": {
|
||||
"name": "Form",
|
||||
"description": "an HTML table that fetches data from a table or view and displays it.",
|
||||
"data": true,
|
||||
"props": {}
|
||||
},
|
||||
"dataformwide": {
|
||||
"name": "Form Wide",
|
||||
"description": "an HTML table that fetches data from a table or view and displays it.",
|
||||
"data": true,
|
||||
"props": {}
|
||||
},
|
||||
"list": {
|
||||
"name": "Repeater",
|
||||
"description": "A configurable data list that attaches to your backend tables.",
|
||||
"context": "datasource",
|
||||
"children": true,
|
||||
"data": true,
|
||||
"props": {
|
||||
"datasource": "tables"
|
||||
}
|
||||
},
|
||||
"stackedlist": {
|
||||
"name": "Stacked List",
|
||||
"description": "A stacked list component for displaying information",
|
||||
"props": {
|
||||
"imageUrl": "string",
|
||||
"heading": "string",
|
||||
"text1": "string",
|
||||
"text2": "string",
|
||||
"text3": "string",
|
||||
"destinationUrl": "string"
|
||||
}
|
||||
},
|
||||
|
||||
"rowdetail": {
|
||||
"name": "Row Detail",
|
||||
"description": "Loads a row, using an ID in the url",
|
||||
|
@ -165,43 +81,6 @@
|
|||
"table": "tables"
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"name": "Card",
|
||||
"props": {
|
||||
"imageUrl": "string",
|
||||
"heading": "string",
|
||||
"description": "string",
|
||||
"linkText": "string",
|
||||
"linkUrl": "string",
|
||||
"linkColor": {
|
||||
"type": "string",
|
||||
"default": "#000"
|
||||
},
|
||||
"linkHoverColor": {
|
||||
"type": "string",
|
||||
"default": "#000"
|
||||
},
|
||||
"imageHeight": {
|
||||
"type": "options",
|
||||
"default": "20rem",
|
||||
"options": [
|
||||
"12rem",
|
||||
"16rem",
|
||||
"20rem",
|
||||
"24rem"
|
||||
]
|
||||
},
|
||||
"cardWidth": {
|
||||
"type": "options",
|
||||
"default": "20rem",
|
||||
"options": [
|
||||
"16rem",
|
||||
"20rem",
|
||||
"24rem"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"cardstat": {
|
||||
"name": "Stat Card",
|
||||
"props": {
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
"key": "datasource"
|
||||
},
|
||||
{
|
||||
"type": "detailURL",
|
||||
"type": "detailScreen",
|
||||
"label": "Detail URL",
|
||||
"key": "detailUrl"
|
||||
},
|
||||
|
@ -90,8 +90,7 @@
|
|||
{
|
||||
"type": "boolean",
|
||||
"label": "Disabled",
|
||||
"key": "disabled",
|
||||
"valueKey": "checked"
|
||||
"key": "disabled"
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
|
@ -99,5 +98,176 @@
|
|||
"key": "onClick"
|
||||
}
|
||||
]
|
||||
},
|
||||
"list": {
|
||||
"name": "Repeater",
|
||||
"description": "A configurable data list that attaches to your backend tables.",
|
||||
"icon": "ri-list-check-2",
|
||||
"styleable": true,
|
||||
"hasChildren": true,
|
||||
"dataProvider": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "datasource",
|
||||
"label": "Data",
|
||||
"key": "datasource"
|
||||
}
|
||||
]
|
||||
},
|
||||
"form": {
|
||||
"name": "Form",
|
||||
"icon": "ri-file-edit-line",
|
||||
"styleable": true
|
||||
},
|
||||
"input": {
|
||||
"name": "Text Field",
|
||||
"description": "A textfield component that allows the user to input text.",
|
||||
"icon": "ri-edit-box-line",
|
||||
"styleable": true,
|
||||
"bindable": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Label",
|
||||
"key": "label"
|
||||
},
|
||||
{
|
||||
"label": "Type",
|
||||
"key": "type",
|
||||
"defaultValue": "text",
|
||||
"options": ["text", "password"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"richtext": {
|
||||
"name": "Rich Text",
|
||||
"description": "A component that allows the user to enter long form text.",
|
||||
"icon": "ri-edit-box-line",
|
||||
"styleable": true,
|
||||
"bindable": true
|
||||
},
|
||||
"datepicker": {
|
||||
"name": "Date Picker",
|
||||
"description": "A basic date picker component",
|
||||
"icon": "ri-calendar-line",
|
||||
"styleable": true,
|
||||
"bindable": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Placeholder",
|
||||
"key": "placeholder"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stackedlist": {
|
||||
"name": "Stacked List",
|
||||
"icon": "ri-archive-drawer-line",
|
||||
"description": "A basic card component that can contain content and actions.",
|
||||
"styleable": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Image",
|
||||
"key": "imageUrl"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Heading",
|
||||
"key": "heading"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Text 1",
|
||||
"key": "text1"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Text 2",
|
||||
"key": "text2"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Text 3",
|
||||
"key": "text3"
|
||||
},
|
||||
{
|
||||
"type": "screen",
|
||||
"label": "Link URL",
|
||||
"key": "destinationUrl"
|
||||
}
|
||||
]
|
||||
},
|
||||
"card": {
|
||||
"name": "Vertical Card",
|
||||
"description": "A basic card component that can contain content and actions.",
|
||||
"icon": "ri-layout-column-line",
|
||||
"styleable": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Image",
|
||||
"key": "imageUrl"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Heading",
|
||||
"key": "heading"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Description",
|
||||
"key": "description"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Link Text",
|
||||
"key": "linkText"
|
||||
},
|
||||
{
|
||||
"type": "screen",
|
||||
"label": "Link Url",
|
||||
"key": "linkUrl"
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"label": "Link Color",
|
||||
"key": "linkColor",
|
||||
"defaultValue": "#000"
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"label": "Hover Color",
|
||||
"key": "linkHoverColor",
|
||||
"defaultValue": "#222"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Image Height",
|
||||
"key": "imageHeight",
|
||||
"options": ["auto", "12rem", "16rem", "20rem", "24rem"],
|
||||
"defaultValue": "auto"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Card Width",
|
||||
"key": "cardWidth",
|
||||
"options": ["16rem", "20rem", "24rem"],
|
||||
"defaultValue": "20rem"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"name": "Paragraph",
|
||||
"description": "A component for displaying paragraph text.",
|
||||
"icon": "ri-paragraph",
|
||||
"styleable": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Text",
|
||||
"key": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import { cssVars } from "./helpers"
|
||||
|
||||
const { styleable } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
@ -16,22 +15,21 @@
|
|||
export let imageHeight
|
||||
export let cardWidth
|
||||
|
||||
$: cssVariables = {
|
||||
imageHeight,
|
||||
cardWidth,
|
||||
$: cardStyles = {
|
||||
...$component.styles,
|
||||
normal: {
|
||||
...$component.styles.normal,
|
||||
width: cardWidth,
|
||||
},
|
||||
}
|
||||
|
||||
$: showImage = !!imageUrl
|
||||
</script>
|
||||
|
||||
<div
|
||||
use:cssVars={cssVariables}
|
||||
class="container"
|
||||
use:styleable={$component.styles}
|
||||
style="--cardWidth: {cardWidth}">
|
||||
<div class="container" use:styleable={cardStyles}>
|
||||
{#if showImage}
|
||||
<img
|
||||
style="--imageWidth: {imageWidth}; --imageHeight: {imageHeight}"
|
||||
style="--imageHeight: {imageHeight}"
|
||||
class="image"
|
||||
src={imageUrl}
|
||||
alt="" />
|
||||
|
|
|
@ -9,23 +9,24 @@ export { default as container } from "./Container.svelte"
|
|||
export { default as grid } from "./grid/Component.svelte"
|
||||
export { default as screenslot } from "./ScreenSlot.svelte"
|
||||
export { default as button } from "./Button.svelte"
|
||||
export { default as input } from "./Input.svelte"
|
||||
export { default as richtext } from "./RichText.svelte"
|
||||
export { default as list } from "./List.svelte"
|
||||
export { default as stackedlist } from "./StackedList.svelte"
|
||||
export { default as card } from "./Card.svelte"
|
||||
export { default as form } from "./DataFormWide.svelte"
|
||||
export { default as datepicker } from "./DatePicker.svelte"
|
||||
export { default as text } from "./Text.svelte"
|
||||
|
||||
// export { default as text } from "./Text.svelte"
|
||||
// export { default as heading } from "./Heading.svelte"
|
||||
// export { default as input } from "./Input.svelte"
|
||||
// export { default as richtext } from "./RichText.svelte"
|
||||
// export { default as login } from "./Login.svelte"
|
||||
// export { default as link } from "./Link.svelte"
|
||||
// export { default as image } from "./Image.svelte"
|
||||
// export { default as navigation } from "./Navigation.svelte"
|
||||
// export { default as list } from "./List.svelte"
|
||||
// export { default as embed } from "./Embed.svelte"
|
||||
// export { default as stackedlist } from "./StackedList.svelte"
|
||||
// export { default as card } from "./Card.svelte"
|
||||
// export { default as cardhorizontal } from "./CardHorizontal.svelte"
|
||||
// export { default as cardstat } from "./CardStat.svelte"
|
||||
// export { default as rowdetail } from "./RowDetail.svelte"
|
||||
// export { default as newrow } from "./NewRow.svelte"
|
||||
// export { default as datepicker } from "./DatePicker.svelte"
|
||||
// export { default as icon } from "./Icon.svelte"
|
||||
// export * from "./charts"
|
||||
|
|
Loading…
Reference in New Issue