Add button to table with search block and support defining multiple settings sections in component manifest entries
This commit is contained in:
parent
df9f061d66
commit
dc9b1a2a8c
|
@ -4,6 +4,7 @@ import {
|
||||||
findAllMatchingComponents,
|
findAllMatchingComponents,
|
||||||
findComponent,
|
findComponent,
|
||||||
findComponentPath,
|
findComponentPath,
|
||||||
|
getComponentSettings,
|
||||||
} from "./storeUtils"
|
} from "./storeUtils"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { queries as queriesStores, tables as tablesStore } from "stores/backend"
|
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
|
* Gets a datasource object for a certain data provider component
|
||||||
*/
|
*/
|
||||||
export const getDatasourceForProvider = (asset, component) => {
|
export const getDatasourceForProvider = (asset, component) => {
|
||||||
const def = store.actions.components.getDefinition(component?._component)
|
const settings = getComponentSettings(component._component)
|
||||||
if (!def) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this component has a dataProvider setting, go up the stack and use it
|
// 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"
|
return setting.type === "dataProvider"
|
||||||
})
|
})
|
||||||
if (dataProviderSetting) {
|
if (dataProviderSetting) {
|
||||||
|
@ -100,7 +98,7 @@ export const getDatasourceForProvider = (asset, component) => {
|
||||||
|
|
||||||
// Extract datasource from component instance
|
// Extract datasource from component instance
|
||||||
const validSettingTypes = ["dataSource", "table", "schema"]
|
const validSettingTypes = ["dataSource", "table", "schema"]
|
||||||
const datasourceSetting = def.settings.find(setting => {
|
const datasourceSetting = settings.find(setting => {
|
||||||
return validSettingTypes.includes(setting.type)
|
return validSettingTypes.includes(setting.type)
|
||||||
})
|
})
|
||||||
if (!datasourceSetting) {
|
if (!datasourceSetting) {
|
||||||
|
@ -374,8 +372,8 @@ const buildFormSchema = component => {
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
const def = store.actions.components.getDefinition(component._component)
|
const settings = getComponentSettings(component._component)
|
||||||
const fieldSetting = def?.settings?.find(
|
const fieldSetting = settings.find(
|
||||||
setting => setting.key === "field" && setting.type.startsWith("field/")
|
setting => setting.key === "field" && setting.type.startsWith("field/")
|
||||||
)
|
)
|
||||||
if (fieldSetting && component.field) {
|
if (fieldSetting && component.field) {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
findClosestMatchingComponent,
|
findClosestMatchingComponent,
|
||||||
findAllMatchingComponents,
|
findAllMatchingComponents,
|
||||||
findComponent,
|
findComponent,
|
||||||
|
getComponentSettings,
|
||||||
} from "../storeUtils"
|
} from "../storeUtils"
|
||||||
import { uuid } from "../uuid"
|
import { uuid } from "../uuid"
|
||||||
import { removeBindings } from "../dataBinding"
|
import { removeBindings } from "../dataBinding"
|
||||||
|
@ -368,14 +369,13 @@ export const getFrontendStore = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate default props
|
// Generate default props
|
||||||
|
const settings = getComponentSettings(componentName)
|
||||||
let props = { ...presetProps }
|
let props = { ...presetProps }
|
||||||
if (definition.settings) {
|
settings.forEach(setting => {
|
||||||
definition.settings.forEach(setting => {
|
|
||||||
if (setting.defaultValue !== undefined) {
|
if (setting.defaultValue !== undefined) {
|
||||||
props[setting.key] = setting.defaultValue
|
props[setting.key] = setting.defaultValue
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// Add any extra properties the component needs
|
// Add any extra properties the component needs
|
||||||
let extras = {}
|
let extras = {}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { store } from "./index"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively searches for a specific component ID
|
* Recursively searches for a specific component ID
|
||||||
*/
|
*/
|
||||||
|
@ -123,3 +125,20 @@ const searchComponentTree = (rootComponent, matchComponent) => {
|
||||||
}
|
}
|
||||||
return null
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
]
|
]
|
||||||
|
|
||||||
$: settings = componentDefinition?.settings ?? []
|
$: settings = componentDefinition?.settings ?? []
|
||||||
|
$: generalSettings = settings.filter(setting => !setting.section)
|
||||||
|
$: sections = settings.filter(setting => setting.section)
|
||||||
$: isLayout = assetInstance && assetInstance.favicon
|
$: isLayout = assetInstance && assetInstance.favicon
|
||||||
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition
|
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition
|
||||||
|
|
||||||
|
@ -70,8 +72,8 @@
|
||||||
onChange={val => updateProp("_instanceName", val)}
|
onChange={val => updateProp("_instanceName", val)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if settings && settings.length > 0}
|
{#if generalSettings.length}
|
||||||
{#each settings as setting (setting.key)}
|
{#each generalSettings as setting (setting.key)}
|
||||||
{#if canRenderControl(setting)}
|
{#if canRenderControl(setting)}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
type={setting.type}
|
type={setting.type}
|
||||||
|
@ -99,11 +101,43 @@
|
||||||
{/if}
|
{/if}
|
||||||
{#if componentDefinition?.info}
|
{#if componentDefinition?.info}
|
||||||
<div class="text">
|
<div class="text">
|
||||||
{@html componentDefinition?.info}
|
{@html componentDefinition.info}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</DetailSummary>
|
</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>
|
<style>
|
||||||
.text {
|
.text {
|
||||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||||
|
|
|
@ -2576,9 +2576,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"tablewithsearch": {
|
"tablewithsearch": {
|
||||||
|
"block": true,
|
||||||
"name": "Table with search",
|
"name": "Table with search",
|
||||||
"icon": "Table",
|
"icon": "Table",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
|
"info": "Only the first 3 search columns will be used.",
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -2613,18 +2615,6 @@
|
||||||
"options": ["Ascending", "Descending"],
|
"options": ["Ascending", "Descending"],
|
||||||
"defaultValue": "Descending"
|
"defaultValue": "Descending"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"label": "Paginate",
|
|
||||||
"key": "paginate",
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "number",
|
|
||||||
"label": "Row Count",
|
|
||||||
"key": "rowCount",
|
|
||||||
"defaultValue": 8
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Size",
|
"label": "Size",
|
||||||
|
@ -2641,6 +2631,22 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Paginate",
|
||||||
|
"key": "paginate",
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Table",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Row Count",
|
||||||
|
"key": "rowCount",
|
||||||
|
"defaultValue": 8
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "multifield",
|
"type": "multifield",
|
||||||
"label": "Table Columns",
|
"label": "Table Columns",
|
||||||
|
@ -2660,5 +2666,32 @@
|
||||||
"defaultValue": false
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,9 @@
|
||||||
button {
|
button {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
width: -moz-fit-content;
|
width: -moz-fit-content;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.spectrum-Button--overBackground:hover {
|
.spectrum-Button--overBackground:hover {
|
||||||
color: #555;
|
color: #555;
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
export let rowCount
|
export let rowCount
|
||||||
export let quiet
|
export let quiet
|
||||||
export let size
|
export let size
|
||||||
|
export let showTitleButton
|
||||||
|
export let titleButtonText
|
||||||
|
export let titleButtonOnClick
|
||||||
|
|
||||||
const { API, styleable } = getContext("sdk")
|
const { API, styleable } = getContext("sdk")
|
||||||
const context = getContext("context")
|
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
|
// Load the datasource schema on mount so we can determine column types
|
||||||
|
@ -77,13 +80,17 @@
|
||||||
<Block>
|
<Block>
|
||||||
<div class={size} use:styleable={$component.styles}>
|
<div class={size} use:styleable={$component.styles}>
|
||||||
<BlockComponent type="form" bind:id={formId} props={{ dataSource }}>
|
<BlockComponent type="form" bind:id={formId} props={{ dataSource }}>
|
||||||
{#if title || enrichedSearchColumns?.length}
|
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||||
<div class="header">
|
<div class="header" class:mobile={$context.device.mobile}>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<Heading>{title || ""}</Heading>
|
<Heading>{title || ""}</Heading>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="controls">
|
||||||
{#if enrichedSearchColumns?.length}
|
{#if enrichedSearchColumns?.length}
|
||||||
<div class="search" class:mobile={$context.device.mobile}>
|
<div
|
||||||
|
class="search"
|
||||||
|
style="--cols:{enrichedSearchColumns?.length}"
|
||||||
|
>
|
||||||
{#each enrichedSearchColumns as column}
|
{#each enrichedSearchColumns as column}
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type={column.componentType}
|
type={column.componentType}
|
||||||
|
@ -97,6 +104,17 @@
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if showTitleButton}
|
||||||
|
<BlockComponent
|
||||||
|
type="button"
|
||||||
|
props={{
|
||||||
|
onClick: titleButtonOnClick,
|
||||||
|
text: titleButtonText,
|
||||||
|
type: "cta",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
|
@ -134,39 +152,59 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
flex: 1 1 auto;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.search {
|
.title :global(.spectrum-Heading) {
|
||||||
flex: 0 0 auto;
|
flex: 1 1 auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
flex: 0 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.controls :global(.spectrum-InputGroup .spectrum-InputGroup-input) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
flex: 0 1 auto;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
flex-wrap: wrap;
|
display: grid;
|
||||||
}
|
grid-template-columns: repeat(var(--cols), minmax(120px, 200px));
|
||||||
.search:not(.mobile) :global(.spectrum-Form-itemField > .spectrum-Picker) {
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
.search :global(.spectrum-InputGroup .spectrum-InputGroup-input) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
.search :global(.spectrum-InputGroup) {
|
.search :global(.spectrum-InputGroup) {
|
||||||
min-width: 0;
|
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;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.search.mobile :global(.component > *) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -161,9 +161,15 @@ const confirmTextMap = {
|
||||||
*/
|
*/
|
||||||
export const enrichButtonActions = (actions, context) => {
|
export const enrichButtonActions = (actions, context) => {
|
||||||
// Prevent button actions in the builder preview
|
// Prevent button actions in the builder preview
|
||||||
if (get(builderStore).inBuilder) {
|
if (!actions || get(builderStore).inBuilder) {
|
||||||
return () => {}
|
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"]])
|
const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]])
|
||||||
return async () => {
|
return async () => {
|
||||||
for (let i = 0; i < handlers.length; i++) {
|
for (let i = 0; i < handlers.length; i++) {
|
||||||
|
|
|
@ -36,17 +36,19 @@ export const enrichProps = (props, context) => {
|
||||||
let enrichedProps = enrichDataBindings(props, totalContext)
|
let enrichedProps = enrichDataBindings(props, totalContext)
|
||||||
|
|
||||||
// Enrich click actions if they exist
|
// Enrich click actions if they exist
|
||||||
if (enrichedProps.onClick) {
|
Object.keys(enrichedProps).forEach(prop => {
|
||||||
enrichedProps.onClick = enrichButtonActions(
|
if (prop?.toLowerCase().includes("onclick")) {
|
||||||
enrichedProps.onClick,
|
enrichedProps[prop] = enrichButtonActions(
|
||||||
|
enrichedProps[prop],
|
||||||
totalContext
|
totalContext
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Enrich any click actions in conditions
|
// Enrich any click actions in conditions
|
||||||
if (enrichedProps._conditions) {
|
if (enrichedProps._conditions) {
|
||||||
enrichedProps._conditions.forEach(condition => {
|
enrichedProps._conditions.forEach(condition => {
|
||||||
if (condition.setting === "onClick") {
|
if (condition.setting?.toLowerCase().includes("onclick")) {
|
||||||
condition.settingValue = enrichButtonActions(
|
condition.settingValue = enrichButtonActions(
|
||||||
condition.settingValue,
|
condition.settingValue,
|
||||||
totalContext
|
totalContext
|
||||||
|
|
Loading…
Reference in New Issue