Add card list with search block. Add concept of nested settings which can consume their own contexts and are not enriched at the top level
This commit is contained in:
parent
2f949bad85
commit
aa56d6fd63
|
@ -39,6 +39,25 @@ export const getBindableProperties = (asset, componentId) => {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the bindable properties exposed by a certain component.
|
||||||
|
*/
|
||||||
|
export const getComponentBindableProperties = (asset, componentId) => {
|
||||||
|
if (!asset || !componentId) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the component exists and exposes context
|
||||||
|
const component = findComponent(asset.props, componentId)
|
||||||
|
const def = store.actions.components.getDefinition(component?._component)
|
||||||
|
if (!def?.context) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the bindings for the component
|
||||||
|
return getProviderContextBindings(asset, component)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all data provider components above a component.
|
* Gets all data provider components above a component.
|
||||||
*/
|
*/
|
||||||
|
@ -125,9 +144,26 @@ export const getDatasourceForProvider = (asset, component) => {
|
||||||
const getContextBindings = (asset, componentId) => {
|
const getContextBindings = (asset, componentId) => {
|
||||||
// Extract any components which provide data contexts
|
// Extract any components which provide data contexts
|
||||||
const dataProviders = getDataProviderComponents(asset, componentId)
|
const dataProviders = getDataProviderComponents(asset, componentId)
|
||||||
let bindings = []
|
|
||||||
|
// Generate bindings for all matching components
|
||||||
|
return getProviderContextBindings(asset, dataProviders)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the context bindings exposed by a set of data provider components.
|
||||||
|
*/
|
||||||
|
const getProviderContextBindings = (asset, dataProviders) => {
|
||||||
|
if (!asset || !dataProviders) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure providers is an array
|
||||||
|
if (!Array.isArray(dataProviders)) {
|
||||||
|
dataProviders = [dataProviders]
|
||||||
|
}
|
||||||
|
|
||||||
// Create bindings for each data provider
|
// Create bindings for each data provider
|
||||||
|
let bindings = []
|
||||||
dataProviders.forEach(component => {
|
dataProviders.forEach(component => {
|
||||||
const def = store.actions.components.getDefinition(component._component)
|
const def = store.actions.components.getDefinition(component._component)
|
||||||
const contexts = Array.isArray(def.context) ? def.context : [def.context]
|
const contexts = Array.isArray(def.context) ? def.context : [def.context]
|
||||||
|
@ -140,6 +176,7 @@ const getContextBindings = (asset, componentId) => {
|
||||||
|
|
||||||
let schema
|
let schema
|
||||||
let readablePrefix
|
let readablePrefix
|
||||||
|
let runtimeSuffix = context.suffix
|
||||||
|
|
||||||
if (context.type === "form") {
|
if (context.type === "form") {
|
||||||
// Forms do not need table schemas
|
// Forms do not need table schemas
|
||||||
|
@ -169,8 +206,14 @@ const getContextBindings = (asset, componentId) => {
|
||||||
|
|
||||||
const keys = Object.keys(schema).sort()
|
const keys = Object.keys(schema).sort()
|
||||||
|
|
||||||
|
// Generate safe unique runtime prefix
|
||||||
|
let runtimeId = component._id
|
||||||
|
if (runtimeSuffix) {
|
||||||
|
runtimeId += `-${runtimeSuffix}`
|
||||||
|
}
|
||||||
|
const safeComponentId = makePropSafe(runtimeId)
|
||||||
|
|
||||||
// Create bindable properties for each schema field
|
// Create bindable properties for each schema field
|
||||||
const safeComponentId = makePropSafe(component._id)
|
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
const fieldSchema = schema[key]
|
const fieldSchema = schema[key]
|
||||||
|
|
||||||
|
@ -182,6 +225,7 @@ const getContextBindings = (asset, componentId) => {
|
||||||
} else if (fieldSchema.type === "attachment") {
|
} else if (fieldSchema.type === "attachment") {
|
||||||
runtimeBoundKey = `${key}_first`
|
runtimeBoundKey = `${key}_first`
|
||||||
}
|
}
|
||||||
|
|
||||||
const runtimeBinding = `${safeComponentId}.${makePropSafe(
|
const runtimeBinding = `${safeComponentId}.${makePropSafe(
|
||||||
runtimeBoundKey
|
runtimeBoundKey
|
||||||
)}`
|
)}`
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
"name": "Blocks",
|
"name": "Blocks",
|
||||||
"icon": "Article",
|
"icon": "Article",
|
||||||
"children": [
|
"children": [
|
||||||
"tablewithsearch"
|
"tablewithsearch",
|
||||||
|
"cardlistwithsearch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"section",
|
"section",
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let assetInstance
|
export let assetInstance
|
||||||
export let bindings
|
export let bindings
|
||||||
|
export let componentBindings
|
||||||
|
|
||||||
const layoutDefinition = []
|
const layoutDefinition = []
|
||||||
const screenDefinition = [
|
const screenDefinition = [
|
||||||
|
@ -21,12 +22,24 @@
|
||||||
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
||||||
]
|
]
|
||||||
|
|
||||||
$: settings = componentDefinition?.settings ?? []
|
$: sections = getSections(componentDefinition)
|
||||||
$: 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
|
||||||
|
|
||||||
|
const getSections = definition => {
|
||||||
|
const settings = definition?.settings ?? []
|
||||||
|
const generalSettings = settings.filter(setting => !setting.section)
|
||||||
|
const customSections = settings.filter(setting => setting.section)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: "General",
|
||||||
|
info: componentDefinition?.info,
|
||||||
|
settings: generalSettings,
|
||||||
|
},
|
||||||
|
...(customSections || []),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
const updateProp = store.actions.components.updateProp
|
const updateProp = store.actions.components.updateProp
|
||||||
|
|
||||||
const canRenderControl = setting => {
|
const canRenderControl = setting => {
|
||||||
|
@ -61,53 +74,18 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DetailSummary name="General" collapsible={false}>
|
{#each sections as section, idx (section.name)}
|
||||||
{#if !componentInstance._component.endsWith("/layout")}
|
|
||||||
<PropertyControl
|
|
||||||
bindable={false}
|
|
||||||
control={Input}
|
|
||||||
label="Name"
|
|
||||||
key="_instanceName"
|
|
||||||
value={componentInstance._instanceName}
|
|
||||||
onChange={val => updateProp("_instanceName", val)}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if generalSettings.length}
|
|
||||||
{#each generalSettings 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}
|
|
||||||
{#if componentDefinition?.component?.endsWith("/fieldgroup")}
|
|
||||||
<ResetFieldsButton {componentInstance} />
|
|
||||||
{/if}
|
|
||||||
{#if componentDefinition?.info}
|
|
||||||
<div class="text">
|
|
||||||
{@html componentDefinition.info}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</DetailSummary>
|
|
||||||
|
|
||||||
{#each sections as section (section.name)}
|
|
||||||
<DetailSummary name={section.name} collapsible={false}>
|
<DetailSummary name={section.name} collapsible={false}>
|
||||||
|
{#if idx === 0 && !componentInstance._component.endsWith("/layout")}
|
||||||
|
<PropertyControl
|
||||||
|
bindable={false}
|
||||||
|
control={Input}
|
||||||
|
label="Name"
|
||||||
|
key="_instanceName"
|
||||||
|
value={componentInstance._instanceName}
|
||||||
|
onChange={val => updateProp("_instanceName", val)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{#each section.settings as setting (setting.key)}
|
{#each section.settings as setting (setting.key)}
|
||||||
{#if canRenderControl(setting)}
|
{#if canRenderControl(setting)}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
|
@ -117,7 +95,7 @@
|
||||||
key={setting.key}
|
key={setting.key}
|
||||||
value={componentInstance[setting.key] ??
|
value={componentInstance[setting.key] ??
|
||||||
componentInstance[setting.key]?.defaultValue}
|
componentInstance[setting.key]?.defaultValue}
|
||||||
{componentInstance}
|
nested={setting.nested}
|
||||||
onChange={val => updateProp(setting.key, val)}
|
onChange={val => updateProp(setting.key, val)}
|
||||||
props={{
|
props={{
|
||||||
options: setting.options || [],
|
options: setting.options || [],
|
||||||
|
@ -126,10 +104,15 @@
|
||||||
max: setting.max || null,
|
max: setting.max || null,
|
||||||
}}
|
}}
|
||||||
{bindings}
|
{bindings}
|
||||||
|
{componentBindings}
|
||||||
|
{componentInstance}
|
||||||
{componentDefinition}
|
{componentDefinition}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if idx === 0 && componentDefinition?.component?.endsWith("/fieldgroup")}
|
||||||
|
<ResetFieldsButton {componentInstance} />
|
||||||
|
{/if}
|
||||||
{#if section?.info}
|
{#if section?.info}
|
||||||
<div class="text">
|
<div class="text">
|
||||||
{@html section.info}
|
{@html section.info}
|
||||||
|
|
|
@ -6,13 +6,20 @@
|
||||||
import DesignSection from "./DesignSection.svelte"
|
import DesignSection from "./DesignSection.svelte"
|
||||||
import CustomStylesSection from "./CustomStylesSection.svelte"
|
import CustomStylesSection from "./CustomStylesSection.svelte"
|
||||||
import ConditionalUISection from "./ConditionalUISection.svelte"
|
import ConditionalUISection from "./ConditionalUISection.svelte"
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
import {
|
||||||
|
getBindableProperties,
|
||||||
|
getComponentBindableProperties,
|
||||||
|
} from "builderStore/dataBinding"
|
||||||
|
|
||||||
$: componentInstance = $selectedComponent
|
$: componentInstance = $selectedComponent
|
||||||
$: componentDefinition = store.actions.components.getDefinition(
|
$: componentDefinition = store.actions.components.getDefinition(
|
||||||
$selectedComponent?._component
|
$selectedComponent?._component
|
||||||
)
|
)
|
||||||
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
||||||
|
$: componentBindings = getComponentBindableProperties(
|
||||||
|
$currentAsset,
|
||||||
|
$store.selectedComponentId
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Tabs selected="Settings" noPadding>
|
<Tabs selected="Settings" noPadding>
|
||||||
|
@ -28,6 +35,7 @@
|
||||||
{componentInstance}
|
{componentInstance}
|
||||||
{componentDefinition}
|
{componentDefinition}
|
||||||
{bindings}
|
{bindings}
|
||||||
|
{componentBindings}
|
||||||
/>
|
/>
|
||||||
<DesignSection {componentInstance} {componentDefinition} {bindings} />
|
<DesignSection {componentInstance} {componentDefinition} {bindings} />
|
||||||
<CustomStylesSection
|
<CustomStylesSection
|
||||||
|
|
|
@ -17,14 +17,24 @@
|
||||||
export let props = {}
|
export let props = {}
|
||||||
export let onChange = () => {}
|
export let onChange = () => {}
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
|
export let componentBindings = []
|
||||||
|
export let nested = false
|
||||||
|
|
||||||
let bindingDrawer
|
let bindingDrawer
|
||||||
let anchor
|
let anchor
|
||||||
let valid
|
let valid
|
||||||
|
|
||||||
$: safeValue = getSafeValue(value, props.defaultValue, bindings)
|
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
||||||
|
$: safeValue = getSafeValue(value, props.defaultValue, allBindings)
|
||||||
$: tempValue = safeValue
|
$: tempValue = safeValue
|
||||||
$: replaceBindings = val => readableToRuntimeBinding(bindings, val)
|
$: replaceBindings = val => readableToRuntimeBinding(allBindings, val)
|
||||||
|
|
||||||
|
const getAllBindings = (bindings, componentBindings, nested) => {
|
||||||
|
if (!nested) {
|
||||||
|
return bindings
|
||||||
|
}
|
||||||
|
return [...(bindings || []), ...(componentBindings || [])]
|
||||||
|
}
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
handleChange(tempValue)
|
handleChange(tempValue)
|
||||||
|
@ -78,7 +88,7 @@
|
||||||
updateOnChange={false}
|
updateOnChange={false}
|
||||||
on:change={handleChange}
|
on:change={handleChange}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
{bindings}
|
bindings={allBindings}
|
||||||
name={key}
|
name={key}
|
||||||
text={label}
|
text={label}
|
||||||
{type}
|
{type}
|
||||||
|
@ -104,7 +114,7 @@
|
||||||
bind:valid
|
bind:valid
|
||||||
value={safeValue}
|
value={safeValue}
|
||||||
on:change={e => (tempValue = e.detail)}
|
on:change={e => (tempValue = e.detail)}
|
||||||
bindableProperties={bindings}
|
bindableProperties={allBindings}
|
||||||
allowJS
|
allowJS
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
@ -2599,6 +2599,21 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"key": "horizontal",
|
"key": "horizontal",
|
||||||
"label": "Horizontal"
|
"label": "Horizontal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Show button",
|
||||||
|
"key": "showButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "buttonText",
|
||||||
|
"label": "Button text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "Button action",
|
||||||
|
"key": "buttonOnClick"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -2710,22 +2725,22 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"section": true,
|
"section": true,
|
||||||
"name": "Button",
|
"name": "Title button",
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"key": "showTitleButton",
|
"key": "showTitleButton",
|
||||||
"label": "Show title button",
|
"label": "Show button",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"key": "titleButtonText",
|
"key": "titleButtonText",
|
||||||
"label": "Title button text"
|
"label": "Button text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "Title button action",
|
"label": "Button action",
|
||||||
"key": "titleButtonOnClick"
|
"key": "titleButtonOnClick"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -2742,9 +2757,139 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cardlistwithsearch": {
|
||||||
|
"block": true,
|
||||||
|
"name": "Card list with search",
|
||||||
|
"icon": "Table",
|
||||||
|
"styles": ["size"],
|
||||||
|
"info": "Only the first 3 search columns will be used.",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Title",
|
||||||
|
"key": "title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dataSource",
|
||||||
|
"label": "Data",
|
||||||
|
"key": "dataSource"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "multifield",
|
||||||
|
"label": "Search Columns",
|
||||||
|
"key": "searchColumns",
|
||||||
|
"placeholder": "Choose search columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "filter",
|
||||||
|
"label": "Filtering",
|
||||||
|
"key": "filter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Sort Column",
|
||||||
|
"key": "sortColumn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Sort Order",
|
||||||
|
"key": "sortOrder",
|
||||||
|
"options": ["Ascending", "Descending"],
|
||||||
|
"defaultValue": "Descending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Limit",
|
||||||
|
"key": "limit",
|
||||||
|
"defaultValue": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Paginate",
|
||||||
|
"key": "paginate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Cards",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "cardTitle",
|
||||||
|
"label": "Title",
|
||||||
|
"nested": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "cardSubtitle",
|
||||||
|
"label": "Subtitle",
|
||||||
|
"nested": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "cardDescription",
|
||||||
|
"label": "Description",
|
||||||
|
"nested": true
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "cardImageURL",
|
||||||
|
"label": "Image URL",
|
||||||
|
"nested": true
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "cardHorizontal",
|
||||||
|
"label": "Horizontal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Show button",
|
||||||
|
"key": "showCardButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "cardButtonText",
|
||||||
|
"label": "Button text",
|
||||||
|
"nested": true
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "Button action",
|
||||||
|
"key": "cardButtonOnClick",
|
||||||
|
"nested": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Title button",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "showTitleButton",
|
||||||
|
"label": "Show button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "titleButtonText",
|
||||||
|
"label": "Button text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "Button action",
|
||||||
|
"key": "titleButtonOnClick"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"context": {
|
"context": {
|
||||||
"type": "schema"
|
"type": "schema",
|
||||||
|
"suffix": "repeater"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
export let type
|
export let type
|
||||||
export let props
|
export let props
|
||||||
export let styles
|
export let styles
|
||||||
|
export let context
|
||||||
|
|
||||||
// ID is only exposed as a prop so that it can be bound to from parent
|
// ID is only exposed as a prop so that it can be bound to from parent
|
||||||
// block components
|
// block components
|
||||||
|
@ -16,7 +17,7 @@
|
||||||
|
|
||||||
// Create a fake component instance so that we can use the core Component
|
// Create a fake component instance so that we can use the core Component
|
||||||
// to render this part of the block, taking advantage of binding enrichment
|
// to render this part of the block, taking advantage of binding enrichment
|
||||||
$: id = block.id + rand
|
$: id = `${block.id}-${context ?? rand}`
|
||||||
$: instance = {
|
$: instance = {
|
||||||
_component: `@budibase/standard-components/${type}`,
|
_component: `@budibase/standard-components/${type}`,
|
||||||
_id: id,
|
_id: id,
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
<script context="module">
|
||||||
|
let SettingsDefinitionCache = {}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getContext, setContext } from "svelte"
|
import { getContext, setContext } from "svelte"
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
|
@ -20,13 +24,13 @@
|
||||||
// Any prop overrides that need to be applied due to conditional UI
|
// Any prop overrides that need to be applied due to conditional UI
|
||||||
let conditionalSettings
|
let conditionalSettings
|
||||||
|
|
||||||
// Props are hashed when inside the builder preview and used as a key, so that
|
// Settings are hashed when inside the builder preview and used as a key,
|
||||||
// components fully remount whenever any props change
|
// so that components fully remount whenever any settings change
|
||||||
let propsHash = 0
|
let hash = 0
|
||||||
|
|
||||||
// Latest timestamp that we started a props update.
|
// Latest timestamp that we started a props update.
|
||||||
// Due to enrichment now being async, we need to avoid overwriting newer
|
// Due to enrichment now being async, we need to avoid overwriting newer
|
||||||
// props with old ones, depending on how long enrichment takes.
|
// settings with old ones, depending on how long enrichment takes.
|
||||||
let latestUpdateTime
|
let latestUpdateTime
|
||||||
|
|
||||||
// Keep track of stringified representations of context and instance
|
// Keep track of stringified representations of context and instance
|
||||||
|
@ -49,25 +53,59 @@
|
||||||
// Extract component instance info
|
// Extract component instance info
|
||||||
$: constructor = getComponentConstructor(instance._component)
|
$: constructor = getComponentConstructor(instance._component)
|
||||||
$: definition = getComponentDefinition(instance._component)
|
$: definition = getComponentDefinition(instance._component)
|
||||||
|
$: settingsDefinition = getSettingsDefinition(definition)
|
||||||
$: children = instance._children || []
|
$: children = instance._children || []
|
||||||
$: id = instance._id
|
$: id = instance._id
|
||||||
$: name = instance._instanceName
|
$: name = instance._instanceName
|
||||||
|
|
||||||
|
// Determine if the component is selected or is part of the critical path
|
||||||
|
// leading to the selected component
|
||||||
|
$: selected =
|
||||||
|
$builderStore.inBuilder && $builderStore.selectedComponentId === id
|
||||||
|
$: inSelectedPath = $builderStore.selectedComponentPath?.includes(id)
|
||||||
|
|
||||||
|
// Interactive components can be selected, dragged and highlighted inside
|
||||||
|
// the builder preview
|
||||||
$: interactive =
|
$: interactive =
|
||||||
$builderStore.inBuilder &&
|
$builderStore.inBuilder &&
|
||||||
($builderStore.previewType === "layout" || insideScreenslot) &&
|
($builderStore.previewType === "layout" || insideScreenslot) &&
|
||||||
!insideBlock
|
!insideBlock
|
||||||
|
$: draggable = interactive && !isLayout && !isScreen
|
||||||
|
$: droppable = interactive && !isLayout && !isScreen
|
||||||
|
|
||||||
|
// Empty components are those which accept children but do not have any.
|
||||||
|
// Empty states can be shown for these components, but can be disabled
|
||||||
|
// in the component manifest.
|
||||||
$: empty = interactive && !children.length && definition?.hasChildren
|
$: empty = interactive && !children.length && definition?.hasChildren
|
||||||
$: emptyState = empty && definition?.showEmptyState !== false
|
$: emptyState = empty && definition?.showEmptyState !== false
|
||||||
$: rawProps = getRawProps(instance)
|
|
||||||
$: instanceKey = JSON.stringify(rawProps)
|
// Raw props are all props excluding internal props and children
|
||||||
$: updateComponentProps(rawProps, instanceKey, $context)
|
$: rawSettings = getRawSettings(instance)
|
||||||
$: selected =
|
$: instanceKey = hashString(JSON.stringify(rawSettings))
|
||||||
$builderStore.inBuilder &&
|
|
||||||
$builderStore.selectedComponentId === instance._id
|
// Component settings are those which are intended for this component and
|
||||||
$: inSelectedPath = $builderStore.selectedComponentPath?.includes(id)
|
// which need to be enriched
|
||||||
|
$: componentSettings = getComponentSettings(rawSettings, settingsDefinition)
|
||||||
|
$: enrichComponentSettings(rawSettings, instanceKey, $context)
|
||||||
|
|
||||||
|
// Nested settings are those which are intended for child components inside
|
||||||
|
// blocks and which should not be enriched at this level
|
||||||
|
$: nestedSettings = getNestedSettings(rawSettings, settingsDefinition)
|
||||||
|
|
||||||
|
// Evaluate conditional UI settings and store any component setting changes
|
||||||
|
// which need to be made
|
||||||
$: evaluateConditions(enrichedSettings?._conditions)
|
$: evaluateConditions(enrichedSettings?._conditions)
|
||||||
$: componentSettings = { ...enrichedSettings, ...conditionalSettings }
|
|
||||||
$: renderKey = `${propsHash}-${emptyState}`
|
// Build up the final settings object to be passed to the component
|
||||||
|
$: settings = {
|
||||||
|
...enrichedSettings,
|
||||||
|
...nestedSettings,
|
||||||
|
...conditionalSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render key is used when in the builder preview to fully remount
|
||||||
|
// components when settings are changed
|
||||||
|
$: renderKey = `${hash}-${emptyState}`
|
||||||
|
|
||||||
// Update component context
|
// Update component context
|
||||||
$: componentStore.set({
|
$: componentStore.set({
|
||||||
|
@ -79,14 +117,14 @@
|
||||||
name,
|
name,
|
||||||
})
|
})
|
||||||
|
|
||||||
const getRawProps = instance => {
|
const getRawSettings = instance => {
|
||||||
let validProps = {}
|
let validSettings = {}
|
||||||
Object.entries(instance)
|
Object.entries(instance)
|
||||||
.filter(([name]) => name === "_conditions" || !name.startsWith("_"))
|
.filter(([name]) => name === "_conditions" || !name.startsWith("_"))
|
||||||
.forEach(([key, value]) => {
|
.forEach(([key, value]) => {
|
||||||
validProps[key] = value
|
validSettings[key] = value
|
||||||
})
|
})
|
||||||
return validProps
|
return validSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the component constructor for the specified component
|
// Gets the component constructor for the specified component
|
||||||
|
@ -105,8 +143,47 @@
|
||||||
return type ? Manifest[type] : null
|
return type ? Manifest[type] : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getSettingsDefinition = definition => {
|
||||||
|
if (!definition) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (SettingsDefinitionCache[definition.name]) {
|
||||||
|
return SettingsDefinitionCache[definition.name]
|
||||||
|
}
|
||||||
|
let settings = []
|
||||||
|
definition.settings?.forEach(setting => {
|
||||||
|
if (setting.section) {
|
||||||
|
settings = settings.concat(setting.settings || [])
|
||||||
|
} else {
|
||||||
|
settings.push(setting)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
SettingsDefinitionCache[definition] = settings
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
|
const getComponentSettings = (rawSettings, settingsDefinition) => {
|
||||||
|
let clone = { ...rawSettings }
|
||||||
|
settingsDefinition?.forEach(setting => {
|
||||||
|
if (setting.nested) {
|
||||||
|
delete clone[setting.key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNestedSettings = (rawSettings, settingsDefinition) => {
|
||||||
|
let clone = { ...rawSettings }
|
||||||
|
settingsDefinition?.forEach(setting => {
|
||||||
|
if (!setting.nested) {
|
||||||
|
delete clone[setting.key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
// Enriches any string component props using handlebars
|
// Enriches any string component props using handlebars
|
||||||
const updateComponentProps = (rawProps, instanceKey, context) => {
|
const enrichComponentSettings = (rawSettings, instanceKey, context) => {
|
||||||
const instanceSame = instanceKey === lastInstanceKey
|
const instanceSame = instanceKey === lastInstanceKey
|
||||||
const contextSame = context.key === lastContextKey
|
const contextSame = context.key === lastContextKey
|
||||||
|
|
||||||
|
@ -121,8 +198,8 @@
|
||||||
latestUpdateTime = Date.now()
|
latestUpdateTime = Date.now()
|
||||||
const enrichmentTime = latestUpdateTime
|
const enrichmentTime = latestUpdateTime
|
||||||
|
|
||||||
// Enrich props with context
|
// Enrich settings with context
|
||||||
const enrichedProps = enrichProps(rawProps, context)
|
const newEnrichedSettings = enrichProps(rawSettings, context)
|
||||||
|
|
||||||
// Abandon this update if a newer update has started
|
// Abandon this update if a newer update has started
|
||||||
if (enrichmentTime !== latestUpdateTime) {
|
if (enrichmentTime !== latestUpdateTime) {
|
||||||
|
@ -132,7 +209,7 @@
|
||||||
// Update the component props.
|
// Update the component props.
|
||||||
// Most props are deeply compared so that svelte will only trigger reactive
|
// Most props are deeply compared so that svelte will only trigger reactive
|
||||||
// statements on props that have actually changed.
|
// statements on props that have actually changed.
|
||||||
if (!enrichedProps) {
|
if (!newEnrichedSettings) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let propsChanged = false
|
let propsChanged = false
|
||||||
|
@ -140,17 +217,17 @@
|
||||||
enrichedSettings = {}
|
enrichedSettings = {}
|
||||||
propsChanged = true
|
propsChanged = true
|
||||||
}
|
}
|
||||||
Object.keys(enrichedProps).forEach(key => {
|
Object.keys(newEnrichedSettings).forEach(key => {
|
||||||
if (!propsAreSame(enrichedProps[key], enrichedSettings[key])) {
|
if (!propsAreSame(newEnrichedSettings[key], enrichedSettings[key])) {
|
||||||
propsChanged = true
|
propsChanged = true
|
||||||
enrichedSettings[key] = enrichedProps[key]
|
enrichedSettings[key] = newEnrichedSettings[key]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update the hash if we're in the builder so we can fully remount this
|
// Update the hash if we're in the builder so we can fully remount this
|
||||||
// component
|
// component
|
||||||
if (get(builderStore).inBuilder && propsChanged) {
|
if (get(builderStore).inBuilder && propsChanged) {
|
||||||
propsHash = hashString(JSON.stringify(enrichedSettings))
|
hash = hashString(JSON.stringify(enrichedSettings))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,14 +250,10 @@
|
||||||
conditionalSettings = result.settingUpdates
|
conditionalSettings = result.settingUpdates
|
||||||
visible = nextVisible
|
visible = nextVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag and drop helper tags
|
|
||||||
$: draggable = interactive && !isLayout && !isScreen
|
|
||||||
$: droppable = interactive && !isLayout && !isScreen
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key renderKey}
|
{#key renderKey}
|
||||||
{#if constructor && componentSettings && (visible || inSelectedPath)}
|
{#if constructor && settings && (visible || inSelectedPath)}
|
||||||
<!-- The ID is used as a class because getElementsByClassName is O(1) -->
|
<!-- The ID is used as a class because getElementsByClassName is O(1) -->
|
||||||
<!-- and the performance matters for the selection indicators -->
|
<!-- and the performance matters for the selection indicators -->
|
||||||
<div
|
<div
|
||||||
|
@ -192,7 +265,7 @@
|
||||||
data-id={id}
|
data-id={id}
|
||||||
data-name={name}
|
data-name={name}
|
||||||
>
|
>
|
||||||
<svelte:component this={constructor} {...componentSettings}>
|
<svelte:component this={constructor} {...settings}>
|
||||||
{#if children.length}
|
{#if children.length}
|
||||||
{#each children as child (child._id)}
|
{#each children as child (child._id)}
|
||||||
<svelte:self instance={child} />
|
<svelte:self instance={child} />
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/card/dist/index-vars.css"
|
import "@spectrum-css/card/dist/index-vars.css"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
import { Button } from "@budibase/bbui"
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let subtitle
|
export let subtitle
|
||||||
|
@ -8,6 +9,9 @@
|
||||||
export let imageURL
|
export let imageURL
|
||||||
export let linkURL
|
export let linkURL
|
||||||
export let horizontal
|
export let horizontal
|
||||||
|
export let showButton
|
||||||
|
export let buttonText
|
||||||
|
export let buttonOnClick
|
||||||
|
|
||||||
const { styleable, linkable } = getContext("sdk")
|
const { styleable, linkable } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
@ -60,6 +64,11 @@
|
||||||
{description}
|
{description}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if showButton}
|
||||||
|
<div class="spectrum-Card-footer button-container">
|
||||||
|
<Button on:click={buttonOnClick} secondary>{buttonText}</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -116,4 +125,8 @@
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
margin-top: -8px;
|
margin-top: -8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
margin-top: -3px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
<script>
|
||||||
|
import { onMount, getContext } from "svelte"
|
||||||
|
import Block from "components/Block.svelte"
|
||||||
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
|
import { Heading } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let title
|
||||||
|
export let dataSource
|
||||||
|
export let searchColumns
|
||||||
|
export let filter
|
||||||
|
export let sortColumn
|
||||||
|
export let sortOrder
|
||||||
|
export let paginate
|
||||||
|
export let limit
|
||||||
|
export let showTitleButton
|
||||||
|
export let titleButtonText
|
||||||
|
export let titleButtonOnClick
|
||||||
|
export let cardTitle
|
||||||
|
export let cardSubtitle
|
||||||
|
export let cardDescription
|
||||||
|
export let cardImageURL
|
||||||
|
export let cardHorizontal
|
||||||
|
export let showCardButton
|
||||||
|
export let cardButtonText
|
||||||
|
export let cardButtonOnClick
|
||||||
|
|
||||||
|
const { API, styleable } = getContext("sdk")
|
||||||
|
const context = getContext("context")
|
||||||
|
const component = getContext("component")
|
||||||
|
const schemaComponentMap = {
|
||||||
|
string: "stringfield",
|
||||||
|
options: "optionsfield",
|
||||||
|
number: "numberfield",
|
||||||
|
datetime: "datetimefield",
|
||||||
|
boolean: "booleanfield",
|
||||||
|
}
|
||||||
|
|
||||||
|
let formId
|
||||||
|
let dataProviderId
|
||||||
|
let schema
|
||||||
|
|
||||||
|
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||||
|
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
||||||
|
$: cardWidth = cardHorizontal ? 420 : 240
|
||||||
|
|
||||||
|
// Enrich the default filter with the specified search fields
|
||||||
|
const enrichFilter = (filter, columns, formId) => {
|
||||||
|
let enrichedFilter = [...(filter || [])]
|
||||||
|
columns?.forEach(column => {
|
||||||
|
enrichedFilter.push({
|
||||||
|
field: column.name,
|
||||||
|
operator: "equal",
|
||||||
|
type: "string",
|
||||||
|
valueType: "Binding",
|
||||||
|
value: `{{ [${formId}].[${column.name}] }}`,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return enrichedFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine data types for search fields and only use those that are valid
|
||||||
|
const enrichSearchColumns = (searchColumns, schema) => {
|
||||||
|
let enrichedColumns = []
|
||||||
|
searchColumns?.forEach(column => {
|
||||||
|
const schemaType = schema?.[column]?.type
|
||||||
|
const componentType = schemaComponentMap[schemaType]
|
||||||
|
if (componentType) {
|
||||||
|
enrichedColumns.push({
|
||||||
|
name: column,
|
||||||
|
componentType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return enrichedColumns.slice(0, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the datasource schema on mount so we can determine column types
|
||||||
|
onMount(async () => {
|
||||||
|
if (dataSource) {
|
||||||
|
schema = await API.fetchDatasourceSchema(dataSource)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Block>
|
||||||
|
<div class="card-list" use:styleable={$component.styles}>
|
||||||
|
<BlockComponent type="form" bind:id={formId} props={{ dataSource }}>
|
||||||
|
{#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"
|
||||||
|
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
|
||||||
|
type="dataprovider"
|
||||||
|
bind:id={dataProviderId}
|
||||||
|
props={{
|
||||||
|
dataSource,
|
||||||
|
filter: enrichedFilter,
|
||||||
|
sortColumn,
|
||||||
|
sortOrder,
|
||||||
|
paginate,
|
||||||
|
limit,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BlockComponent
|
||||||
|
type="repeater"
|
||||||
|
context="repeater"
|
||||||
|
props={{
|
||||||
|
dataProvider: `{{ literal [${dataProviderId}] }}`,
|
||||||
|
direction: "row",
|
||||||
|
hAlign: "left",
|
||||||
|
vAlign: "top",
|
||||||
|
gap: "M",
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
display: "grid",
|
||||||
|
"grid-template-columns": `repeat(auto-fill, minmax(${cardWidth}px, 1fr))`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BlockComponent
|
||||||
|
type="spectrumcard"
|
||||||
|
props={{
|
||||||
|
title: cardTitle,
|
||||||
|
subtitle: cardSubtitle,
|
||||||
|
description: cardDescription,
|
||||||
|
imageURL: cardImageURL,
|
||||||
|
horizontal: cardHorizontal,
|
||||||
|
showButton: showCardButton,
|
||||||
|
buttonText: cardButtonText,
|
||||||
|
buttonOnClick: cardButtonOnClick,
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
width: "auto",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</BlockComponent>
|
||||||
|
</BlockComponent>
|
||||||
|
</BlockComponent>
|
||||||
|
</div>
|
||||||
|
</Block>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.card-list :global(.spectrum-Card) {
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.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-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%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(var(--cols), minmax(120px, 200px));
|
||||||
|
}
|
||||||
|
.search :global(.spectrum-InputGroup) {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1 +1,2 @@
|
||||||
export { default as tablewithsearch } from "./TableWithSearch.svelte"
|
export { default as tablewithsearch } from "./TableWithSearch.svelte"
|
||||||
|
export { default as cardlistwithsearch } from "./CardListWithSearch.svelte"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { writable, derived } from "svelte/store"
|
import { writable, derived } from "svelte/store"
|
||||||
|
import { hashString } from "../utils/helpers"
|
||||||
|
|
||||||
export const createContextStore = oldContext => {
|
export const createContextStore = oldContext => {
|
||||||
const newContext = writable({})
|
const newContext = writable({})
|
||||||
|
@ -9,7 +10,7 @@ export const createContextStore = oldContext => {
|
||||||
for (let i = 0; i < $contexts.length - 1; i++) {
|
for (let i = 0; i < $contexts.length - 1; i++) {
|
||||||
key += $contexts[i].key
|
key += $contexts[i].key
|
||||||
}
|
}
|
||||||
key += JSON.stringify($contexts[$contexts.length - 1])
|
key = hashString(key + JSON.stringify($contexts[$contexts.length - 1]))
|
||||||
|
|
||||||
// Reduce global state
|
// Reduce global state
|
||||||
const reducer = (total, context) => ({ ...total, ...context })
|
const reducer = (total, context) => ({ ...total, ...context })
|
||||||
|
|
Loading…
Reference in New Issue