Add button to table with search block and support defining multiple settings sections in component manifest entries
This commit is contained in:
parent
7f6b4fb30b
commit
adda702588
|
@ -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) {
|
||||
|
|
|
@ -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 => {
|
||||
settings.forEach(setting => {
|
||||
if (setting.defaultValue !== undefined) {
|
||||
props[setting.key] = setting.defaultValue
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Add any extra properties the component needs
|
||||
let extras = {}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
|
@ -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",
|
||||
"label": "Table Columns",
|
||||
|
@ -2660,5 +2666,32 @@
|
|||
"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 {
|
||||
width: fit-content;
|
||||
width: -moz-fit-content;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.spectrum-Button--overBackground:hover {
|
||||
color: #555;
|
||||
|
|
|
@ -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,13 +80,17 @@
|
|||
<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>
|
||||
<div class="controls">
|
||||
{#if enrichedSearchColumns?.length}
|
||||
<div class="search" class:mobile={$context.device.mobile}>
|
||||
<div
|
||||
class="search"
|
||||
style="--cols:{enrichedSearchColumns?.length}"
|
||||
>
|
||||
{#each enrichedSearchColumns as column}
|
||||
<BlockComponent
|
||||
type={column.componentType}
|
||||
|
@ -97,6 +104,17 @@
|
|||
{/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>
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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,
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue