1
0
Fork 0
mirror of synced 2024-06-14 00:14:39 +12:00

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:
Andrew Kingston 2021-11-08 14:35:58 +00:00
parent 2f949bad85
commit aa56d6fd63
12 changed files with 616 additions and 96 deletions

View file

@ -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.
*/
@ -125,9 +144,26 @@ export const getDatasourceForProvider = (asset, component) => {
const getContextBindings = (asset, componentId) => {
// Extract any components which provide data contexts
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
let bindings = []
dataProviders.forEach(component => {
const def = store.actions.components.getDefinition(component._component)
const contexts = Array.isArray(def.context) ? def.context : [def.context]
@ -140,6 +176,7 @@ const getContextBindings = (asset, componentId) => {
let schema
let readablePrefix
let runtimeSuffix = context.suffix
if (context.type === "form") {
// Forms do not need table schemas
@ -169,8 +206,14 @@ const getContextBindings = (asset, componentId) => {
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
const safeComponentId = makePropSafe(component._id)
keys.forEach(key => {
const fieldSchema = schema[key]
@ -182,6 +225,7 @@ const getContextBindings = (asset, componentId) => {
} else if (fieldSchema.type === "attachment") {
runtimeBoundKey = `${key}_first`
}
const runtimeBinding = `${safeComponentId}.${makePropSafe(
runtimeBoundKey
)}`

View file

@ -3,7 +3,8 @@
"name": "Blocks",
"icon": "Article",
"children": [
"tablewithsearch"
"tablewithsearch",
"cardlistwithsearch"
]
},
"section",

View file

@ -12,6 +12,7 @@
export let componentInstance
export let assetInstance
export let bindings
export let componentBindings
const layoutDefinition = []
const screenDefinition = [
@ -21,12 +22,24 @@
{ key: "layoutId", label: "Layout", control: LayoutSelect },
]
$: settings = componentDefinition?.settings ?? []
$: generalSettings = settings.filter(setting => !setting.section)
$: sections = settings.filter(setting => setting.section)
$: sections = getSections(componentDefinition)
$: isLayout = assetInstance && assetInstance.favicon
$: 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 canRenderControl = setting => {
@ -61,53 +74,18 @@
}
</script>
<DetailSummary name="General" collapsible={false}>
{#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)}
{#each sections as section, idx (section.name)}
<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)}
{#if canRenderControl(setting)}
<PropertyControl
@ -117,7 +95,7 @@
key={setting.key}
value={componentInstance[setting.key] ??
componentInstance[setting.key]?.defaultValue}
{componentInstance}
nested={setting.nested}
onChange={val => updateProp(setting.key, val)}
props={{
options: setting.options || [],
@ -126,10 +104,15 @@
max: setting.max || null,
}}
{bindings}
{componentBindings}
{componentInstance}
{componentDefinition}
/>
{/if}
{/each}
{#if idx === 0 && componentDefinition?.component?.endsWith("/fieldgroup")}
<ResetFieldsButton {componentInstance} />
{/if}
{#if section?.info}
<div class="text">
{@html section.info}

View file

@ -6,13 +6,20 @@
import DesignSection from "./DesignSection.svelte"
import CustomStylesSection from "./CustomStylesSection.svelte"
import ConditionalUISection from "./ConditionalUISection.svelte"
import { getBindableProperties } from "builderStore/dataBinding"
import {
getBindableProperties,
getComponentBindableProperties,
} from "builderStore/dataBinding"
$: componentInstance = $selectedComponent
$: componentDefinition = store.actions.components.getDefinition(
$selectedComponent?._component
)
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
$: componentBindings = getComponentBindableProperties(
$currentAsset,
$store.selectedComponentId
)
</script>
<Tabs selected="Settings" noPadding>
@ -28,6 +35,7 @@
{componentInstance}
{componentDefinition}
{bindings}
{componentBindings}
/>
<DesignSection {componentInstance} {componentDefinition} {bindings} />
<CustomStylesSection

View file

@ -17,14 +17,24 @@
export let props = {}
export let onChange = () => {}
export let bindings = []
export let componentBindings = []
export let nested = false
let bindingDrawer
let anchor
let valid
$: safeValue = getSafeValue(value, props.defaultValue, bindings)
$: allBindings = getAllBindings(bindings, componentBindings, nested)
$: safeValue = getSafeValue(value, props.defaultValue, allBindings)
$: 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 = () => {
handleChange(tempValue)
@ -78,7 +88,7 @@
updateOnChange={false}
on:change={handleChange}
onChange={handleChange}
{bindings}
bindings={allBindings}
name={key}
text={label}
{type}
@ -104,7 +114,7 @@
bind:valid
value={safeValue}
on:change={e => (tempValue = e.detail)}
bindableProperties={bindings}
bindableProperties={allBindings}
allowJS
/>
</Drawer>

View file

@ -2599,6 +2599,21 @@
"type": "boolean",
"key": "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,
"name": "Button",
"name": "Title button",
"settings": [
{
"type": "boolean",
"key": "showTitleButton",
"label": "Show title button",
"label": "Show button",
"defaultValue": false
},
{
"type": "text",
"key": "titleButtonText",
"label": "Title button text"
"label": "Button text"
},
{
"type": "event",
"label": "Title button action",
"label": "Button action",
"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": {
"type": "schema"
"type": "schema",
"suffix": "repeater"
}
}
}

View file

@ -6,6 +6,7 @@
export let type
export let props
export let styles
export let context
// ID is only exposed as a prop so that it can be bound to from parent
// block components
@ -16,7 +17,7 @@
// 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
$: id = block.id + rand
$: id = `${block.id}-${context ?? rand}`
$: instance = {
_component: `@budibase/standard-components/${type}`,
_id: id,

View file

@ -1,3 +1,7 @@
<script context="module">
let SettingsDefinitionCache = {}
</script>
<script>
import { getContext, setContext } from "svelte"
import { writable, get } from "svelte/store"
@ -20,13 +24,13 @@
// Any prop overrides that need to be applied due to conditional UI
let conditionalSettings
// Props are hashed when inside the builder preview and used as a key, so that
// components fully remount whenever any props change
let propsHash = 0
// Settings are hashed when inside the builder preview and used as a key,
// so that components fully remount whenever any settings change
let hash = 0
// Latest timestamp that we started a props update.
// 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
// Keep track of stringified representations of context and instance
@ -49,25 +53,59 @@
// Extract component instance info
$: constructor = getComponentConstructor(instance._component)
$: definition = getComponentDefinition(instance._component)
$: settingsDefinition = getSettingsDefinition(definition)
$: children = instance._children || []
$: id = instance._id
$: 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 =
$builderStore.inBuilder &&
($builderStore.previewType === "layout" || insideScreenslot) &&
!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
$: emptyState = empty && definition?.showEmptyState !== false
$: rawProps = getRawProps(instance)
$: instanceKey = JSON.stringify(rawProps)
$: updateComponentProps(rawProps, instanceKey, $context)
$: selected =
$builderStore.inBuilder &&
$builderStore.selectedComponentId === instance._id
$: inSelectedPath = $builderStore.selectedComponentPath?.includes(id)
// Raw props are all props excluding internal props and children
$: rawSettings = getRawSettings(instance)
$: instanceKey = hashString(JSON.stringify(rawSettings))
// Component settings are those which are intended for this component and
// 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)
$: 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
$: componentStore.set({
@ -79,14 +117,14 @@
name,
})
const getRawProps = instance => {
let validProps = {}
const getRawSettings = instance => {
let validSettings = {}
Object.entries(instance)
.filter(([name]) => name === "_conditions" || !name.startsWith("_"))
.forEach(([key, value]) => {
validProps[key] = value
validSettings[key] = value
})
return validProps
return validSettings
}
// Gets the component constructor for the specified component
@ -105,8 +143,47 @@
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
const updateComponentProps = (rawProps, instanceKey, context) => {
const enrichComponentSettings = (rawSettings, instanceKey, context) => {
const instanceSame = instanceKey === lastInstanceKey
const contextSame = context.key === lastContextKey
@ -121,8 +198,8 @@
latestUpdateTime = Date.now()
const enrichmentTime = latestUpdateTime
// Enrich props with context
const enrichedProps = enrichProps(rawProps, context)
// Enrich settings with context
const newEnrichedSettings = enrichProps(rawSettings, context)
// Abandon this update if a newer update has started
if (enrichmentTime !== latestUpdateTime) {
@ -132,7 +209,7 @@
// Update the component props.
// Most props are deeply compared so that svelte will only trigger reactive
// statements on props that have actually changed.
if (!enrichedProps) {
if (!newEnrichedSettings) {
return
}
let propsChanged = false
@ -140,17 +217,17 @@
enrichedSettings = {}
propsChanged = true
}
Object.keys(enrichedProps).forEach(key => {
if (!propsAreSame(enrichedProps[key], enrichedSettings[key])) {
Object.keys(newEnrichedSettings).forEach(key => {
if (!propsAreSame(newEnrichedSettings[key], enrichedSettings[key])) {
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
// component
if (get(builderStore).inBuilder && propsChanged) {
propsHash = hashString(JSON.stringify(enrichedSettings))
hash = hashString(JSON.stringify(enrichedSettings))
}
}
@ -173,14 +250,10 @@
conditionalSettings = result.settingUpdates
visible = nextVisible
}
// Drag and drop helper tags
$: draggable = interactive && !isLayout && !isScreen
$: droppable = interactive && !isLayout && !isScreen
</script>
{#key renderKey}
{#if constructor && componentSettings && (visible || inSelectedPath)}
{#if constructor && settings && (visible || inSelectedPath)}
<!-- The ID is used as a class because getElementsByClassName is O(1) -->
<!-- and the performance matters for the selection indicators -->
<div
@ -192,7 +265,7 @@
data-id={id}
data-name={name}
>
<svelte:component this={constructor} {...componentSettings}>
<svelte:component this={constructor} {...settings}>
{#if children.length}
{#each children as child (child._id)}
<svelte:self instance={child} />

View file

@ -1,6 +1,7 @@
<script>
import "@spectrum-css/card/dist/index-vars.css"
import { getContext } from "svelte"
import { Button } from "@budibase/bbui"
export let title
export let subtitle
@ -8,6 +9,9 @@
export let imageURL
export let linkURL
export let horizontal
export let showButton
export let buttonText
export let buttonOnClick
const { styleable, linkable } = getContext("sdk")
const component = getContext("component")
@ -60,6 +64,11 @@
{description}
</div>
{/if}
{#if showButton}
<div class="spectrum-Card-footer button-container">
<Button on:click={buttonOnClick} secondary>{buttonText}</Button>
</div>
{/if}
</div>
</div>
@ -116,4 +125,8 @@
padding-top: 0;
margin-top: -8px;
}
.button-container {
margin-top: -3px;
}
</style>

View file

@ -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>

View file

@ -1 +1,2 @@
export { default as tablewithsearch } from "./TableWithSearch.svelte"
export { default as cardlistwithsearch } from "./CardListWithSearch.svelte"

View file

@ -1,4 +1,5 @@
import { writable, derived } from "svelte/store"
import { hashString } from "../utils/helpers"
export const createContextStore = oldContext => {
const newContext = writable({})
@ -9,7 +10,7 @@ export const createContextStore = oldContext => {
for (let i = 0; i < $contexts.length - 1; i++) {
key += $contexts[i].key
}
key += JSON.stringify($contexts[$contexts.length - 1])
key = hashString(key + JSON.stringify($contexts[$contexts.length - 1]))
// Reduce global state
const reducer = (total, context) => ({ ...total, ...context })