Add button to table with search block and support defining multiple settings sections in component manifest entries

This commit is contained in:
Andrew Kingston 2021-11-04 11:30:43 +00:00
parent 7f6b4fb30b
commit adda702588
9 changed files with 218 additions and 85 deletions

View File

@ -4,6 +4,7 @@ import {
findAllMatchingComponents,
findComponent,
findComponentPath,
getComponentSettings,
} from "./storeUtils"
import { store } from "builderStore"
import { queries as queriesStores, tables as tablesStore } from "stores/backend"
@ -82,13 +83,10 @@ export const getActionProviderComponents = (asset, componentId, actionType) => {
* Gets a datasource object for a certain data provider component
*/
export const getDatasourceForProvider = (asset, component) => {
const def = store.actions.components.getDefinition(component?._component)
if (!def) {
return null
}
const settings = getComponentSettings(component._component)
// If this component has a dataProvider setting, go up the stack and use it
const dataProviderSetting = def.settings.find(setting => {
const dataProviderSetting = settings.find(setting => {
return setting.type === "dataProvider"
})
if (dataProviderSetting) {
@ -100,7 +98,7 @@ export const getDatasourceForProvider = (asset, component) => {
// Extract datasource from component instance
const validSettingTypes = ["dataSource", "table", "schema"]
const datasourceSetting = def.settings.find(setting => {
const datasourceSetting = settings.find(setting => {
return validSettingTypes.includes(setting.type)
})
if (!datasourceSetting) {
@ -374,8 +372,8 @@ const buildFormSchema = component => {
if (!component) {
return schema
}
const def = store.actions.components.getDefinition(component._component)
const fieldSetting = def?.settings?.find(
const settings = getComponentSettings(component._component)
const fieldSetting = settings.find(
setting => setting.key === "field" && setting.type.startsWith("field/")
)
if (fieldSetting && component.field) {

View File

@ -25,6 +25,7 @@ import {
findClosestMatchingComponent,
findAllMatchingComponents,
findComponent,
getComponentSettings,
} from "../storeUtils"
import { uuid } from "../uuid"
import { removeBindings } from "../dataBinding"
@ -368,14 +369,13 @@ export const getFrontendStore = () => {
}
// Generate default props
const settings = getComponentSettings(componentName)
let props = { ...presetProps }
if (definition.settings) {
definition.settings.forEach(setting => {
if (setting.defaultValue !== undefined) {
props[setting.key] = setting.defaultValue
}
})
}
settings.forEach(setting => {
if (setting.defaultValue !== undefined) {
props[setting.key] = setting.defaultValue
}
})
// Add any extra properties the component needs
let extras = {}

View File

@ -1,3 +1,5 @@
import { store } from "./index"
/**
* Recursively searches for a specific component ID
*/
@ -123,3 +125,20 @@ const searchComponentTree = (rootComponent, matchComponent) => {
}
return null
}
/**
* Searches a component's definition for a setting matching a certin predicate.
*/
export const getComponentSettings = componentType => {
const def = store.actions.components.getDefinition(componentType)
if (!def) {
return []
}
let settings = def.settings?.filter(setting => !setting.section) ?? []
def.settings
?.filter(setting => setting.section)
.forEach(section => {
settings = settings.concat(section.settings || [])
})
return settings
}

View File

@ -22,6 +22,8 @@
]
$: settings = componentDefinition?.settings ?? []
$: generalSettings = settings.filter(setting => !setting.section)
$: sections = settings.filter(setting => setting.section)
$: isLayout = assetInstance && assetInstance.favicon
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition
@ -70,8 +72,8 @@
onChange={val => updateProp("_instanceName", val)}
/>
{/if}
{#if settings && settings.length > 0}
{#each settings as setting (setting.key)}
{#if generalSettings.length}
{#each generalSettings as setting (setting.key)}
{#if canRenderControl(setting)}
<PropertyControl
type={setting.type}
@ -99,11 +101,43 @@
{/if}
{#if componentDefinition?.info}
<div class="text">
{@html componentDefinition?.info}
{@html componentDefinition.info}
</div>
{/if}
</DetailSummary>
{#each sections as section (section.name)}
<DetailSummary name={section.name} collapsible={false}>
{#each section.settings as setting (setting.key)}
{#if canRenderControl(setting)}
<PropertyControl
type={setting.type}
control={getComponentForSettingType(setting.type)}
label={setting.label}
key={setting.key}
value={componentInstance[setting.key] ??
componentInstance[setting.key]?.defaultValue}
{componentInstance}
onChange={val => updateProp(setting.key, val)}
props={{
options: setting.options || [],
placeholder: setting.placeholder || null,
min: setting.min || null,
max: setting.max || null,
}}
{bindings}
{componentDefinition}
/>
{/if}
{/each}
{#if section?.info}
<div class="text">
{@html section.info}
</div>
{/if}
</DetailSummary>
{/each}
<style>
.text {
font-size: var(--spectrum-global-dimension-font-size-75);

View File

@ -2576,9 +2576,11 @@
]
},
"tablewithsearch": {
"block": true,
"name": "Table with search",
"icon": "Table",
"styles": ["size"],
"info": "Only the first 3 search columns will be used.",
"settings": [
{
"type": "text",
@ -2613,18 +2615,6 @@
"options": ["Ascending", "Descending"],
"defaultValue": "Descending"
},
{
"type": "boolean",
"label": "Paginate",
"key": "paginate",
"defaultValue": true
},
{
"type": "number",
"label": "Row Count",
"key": "rowCount",
"defaultValue": 8
},
{
"type": "select",
"label": "Size",
@ -2642,23 +2632,66 @@
]
},
{
"type": "multifield",
"label": "Table Columns",
"key": "tableColumns",
"dependsOn": "dataSource",
"placeholder": "All columns"
"type": "boolean",
"label": "Paginate",
"key": "paginate",
"defaultValue": true
},
{
"type": "boolean",
"label": "Quiet table variant",
"key": "quiet"
"section": true,
"name": "Table",
"settings": [
{
"type": "number",
"label": "Row Count",
"key": "rowCount",
"defaultValue": 8
},
{
"type": "multifield",
"label": "Table Columns",
"key": "tableColumns",
"dependsOn": "dataSource",
"placeholder": "All columns"
},
{
"type": "boolean",
"label": "Quiet table variant",
"key": "quiet"
},
{
"type": "boolean",
"label": "Show auto columns",
"key": "showAutoColumns",
"defaultValue": false
}
]
},
{
"type": "boolean",
"label": "Show auto columns",
"key": "showAutoColumns",
"defaultValue": false
"section": true,
"name": "Button",
"settings": [
{
"type": "boolean",
"key": "showTitleButton",
"label": "Show title button",
"defaultValue": false
},
{
"type": "text",
"key": "titleButtonText",
"label": "Title button text"
},
{
"type": "event",
"label": "Title Button Action",
"key": "titleButtonOnClick"
}
]
}
]
],
"context": {
"type": "schema"
}
}
}

View File

@ -27,6 +27,9 @@
button {
width: fit-content;
width: -moz-fit-content;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.spectrum-Button--overBackground:hover {
color: #555;

View File

@ -16,6 +16,9 @@
export let rowCount
export let quiet
export let size
export let showTitleButton
export let titleButtonText
export let titleButtonOnClick
const { API, styleable } = getContext("sdk")
const context = getContext("context")
@ -63,7 +66,7 @@
})
}
})
return enrichedColumns
return enrichedColumns.slice(0, 3)
}
// Load the datasource schema on mount so we can determine column types
@ -77,26 +80,41 @@
<Block>
<div class={size} use:styleable={$component.styles}>
<BlockComponent type="form" bind:id={formId} props={{ dataSource }}>
{#if title || enrichedSearchColumns?.length}
<div class="header">
{#if title || enrichedSearchColumns?.length || showTitleButton}
<div class="header" class:mobile={$context.device.mobile}>
<div class="title">
<Heading>{title || ""}</Heading>
</div>
{#if enrichedSearchColumns?.length}
<div class="search" class:mobile={$context.device.mobile}>
{#each enrichedSearchColumns as column}
<BlockComponent
type={column.componentType}
props={{
field: column.name,
placeholder: column.name,
text: column.name,
autoWidth: true,
}}
/>
{/each}
</div>
{/if}
<div class="controls">
{#if enrichedSearchColumns?.length}
<div
class="search"
style="--cols:{enrichedSearchColumns?.length}"
>
{#each enrichedSearchColumns as column}
<BlockComponent
type={column.componentType}
props={{
field: column.name,
placeholder: column.name,
text: column.name,
autoWidth: true,
}}
/>
{/each}
</div>
{/if}
{#if showTitleButton}
<BlockComponent
type="button"
props={{
onClick: titleButtonOnClick,
text: titleButtonText,
type: "cta",
}}
/>
{/if}
</div>
</div>
{/if}
<BlockComponent
@ -134,39 +152,59 @@
justify-content: space-between;
align-items: center;
gap: 20px;
flex-wrap: wrap;
margin-bottom: 20px;
}
.title {
flex: 1 1 auto;
overflow: hidden;
}
.search {
flex: 0 0 auto;
.title :global(.spectrum-Heading) {
flex: 1 1 auto;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.controls {
flex: 0 1 auto;
display: flex;
flex-direction: row;
justify-content: flex-start;
justify-content: flex-end;
align-items: center;
gap: 20px;
}
.controls :global(.spectrum-InputGroup .spectrum-InputGroup-input) {
width: 100%;
}
.search {
flex: 0 1 auto;
gap: 10px;
max-width: 100%;
flex-wrap: wrap;
}
.search:not(.mobile) :global(.spectrum-Form-itemField > .spectrum-Picker) {
width: 200px;
}
.search :global(.spectrum-InputGroup .spectrum-InputGroup-input) {
width: 100%;
display: grid;
grid-template-columns: repeat(var(--cols), minmax(120px, 200px));
}
.search :global(.spectrum-InputGroup) {
min-width: 0;
}
.search.mobile {
/* Mobile styles */
.mobile {
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
.mobile .controls {
flex-direction: column-reverse;
justify-content: flex-start;
align-items: stretch;
}
.mobile .search {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
position: relative;
width: 100%;
}
.search.mobile :global(.component > *) {
width: 100%;
}
</style>

View File

@ -161,9 +161,15 @@ const confirmTextMap = {
*/
export const enrichButtonActions = (actions, context) => {
// Prevent button actions in the builder preview
if (get(builderStore).inBuilder) {
if (!actions || get(builderStore).inBuilder) {
return () => {}
}
// If this is a function then it has already been enriched
if (typeof actions === "function") {
return actions
}
const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]])
return async () => {
for (let i = 0; i < handlers.length; i++) {

View File

@ -36,17 +36,19 @@ export const enrichProps = (props, context) => {
let enrichedProps = enrichDataBindings(props, totalContext)
// Enrich click actions if they exist
if (enrichedProps.onClick) {
enrichedProps.onClick = enrichButtonActions(
enrichedProps.onClick,
totalContext
)
}
Object.keys(enrichedProps).forEach(prop => {
if (prop?.toLowerCase().includes("onclick")) {
enrichedProps[prop] = enrichButtonActions(
enrichedProps[prop],
totalContext
)
}
})
// Enrich any click actions in conditions
if (enrichedProps._conditions) {
enrichedProps._conditions.forEach(condition => {
if (condition.setting === "onClick") {
if (condition.setting?.toLowerCase().includes("onclick")) {
condition.settingValue = enrichButtonActions(
condition.settingValue,
totalContext