commit
4af29d15de
|
@ -115,7 +115,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.spectrum-Modal {
|
.spectrum-Modal {
|
||||||
background: var(--background);
|
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
max-height: none;
|
max-height: none;
|
||||||
margin: 40px 0;
|
margin: 40px 0;
|
||||||
|
|
|
@ -88,7 +88,6 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
position: relative;
|
position: relative;
|
||||||
gap: var(--spacing-xl);
|
gap: var(--spacing-xl);
|
||||||
color: var(--ink);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.spectrum-Dialog-content {
|
.spectrum-Dialog-content {
|
||||||
|
@ -106,13 +105,8 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 15px;
|
top: 15px;
|
||||||
right: 15px;
|
right: 15px;
|
||||||
color: var(--ink);
|
|
||||||
font-size: var(--font-size-m);
|
font-size: var(--font-size-m);
|
||||||
}
|
}
|
||||||
.close-icon:hover {
|
|
||||||
color: var(--grey-6);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.close-icon :global(svg) {
|
.close-icon :global(svg) {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -530,6 +530,11 @@ export const getFrontendStore = () => {
|
||||||
selected._styles = { normal: {}, hover: {}, active: {} }
|
selected._styles = { normal: {}, hover: {}, active: {} }
|
||||||
await store.actions.preview.saveSelected()
|
await store.actions.preview.saveSelected()
|
||||||
},
|
},
|
||||||
|
updateConditions: async conditions => {
|
||||||
|
const selected = get(selectedComponent)
|
||||||
|
selected._conditions = conditions
|
||||||
|
await store.actions.preview.saveSelected()
|
||||||
|
},
|
||||||
updateProp: async (name, value) => {
|
updateProp: async (name, value) => {
|
||||||
let component = get(selectedComponent)
|
let component = get(selectedComponent)
|
||||||
if (!name || !component) {
|
if (!name || !component) {
|
||||||
|
|
|
@ -1,32 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { isEmpty } from "lodash/fp"
|
import { isEmpty } from "lodash/fp"
|
||||||
import { Checkbox, Input, Select, DetailSummary } from "@budibase/bbui"
|
import { Input, DetailSummary } from "@budibase/bbui"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import PropertyControl from "./PropertyControls/PropertyControl.svelte"
|
import PropertyControl from "./PropertyControls/PropertyControl.svelte"
|
||||||
import LayoutSelect from "./PropertyControls/LayoutSelect.svelte"
|
import LayoutSelect from "./PropertyControls/LayoutSelect.svelte"
|
||||||
import RoleSelect from "./PropertyControls/RoleSelect.svelte"
|
import RoleSelect from "./PropertyControls/RoleSelect.svelte"
|
||||||
import TableSelect from "./PropertyControls/TableSelect.svelte"
|
|
||||||
import DataSourceSelect from "./PropertyControls/DataSourceSelect.svelte"
|
|
||||||
import DataProviderSelect from "./PropertyControls/DataProviderSelect.svelte"
|
|
||||||
import FieldSelect from "./PropertyControls/FieldSelect.svelte"
|
|
||||||
import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte"
|
|
||||||
import SchemaSelect from "./PropertyControls/SchemaSelect.svelte"
|
|
||||||
import SectionSelect from "./PropertyControls/SectionSelect.svelte"
|
|
||||||
import NavigationEditor from "./PropertyControls/NavigationEditor/NavigationEditor.svelte"
|
|
||||||
import EventsEditor from "./PropertyControls/EventsEditor"
|
|
||||||
import FilterEditor from "./PropertyControls/FilterEditor/FilterEditor.svelte"
|
|
||||||
import { IconSelect } from "./PropertyControls/IconSelect"
|
|
||||||
import StringFieldSelect from "./PropertyControls/StringFieldSelect.svelte"
|
|
||||||
import NumberFieldSelect from "./PropertyControls/NumberFieldSelect.svelte"
|
|
||||||
import OptionsFieldSelect from "./PropertyControls/OptionsFieldSelect.svelte"
|
|
||||||
import BooleanFieldSelect from "./PropertyControls/BooleanFieldSelect.svelte"
|
|
||||||
import LongFormFieldSelect from "./PropertyControls/LongFormFieldSelect.svelte"
|
|
||||||
import DateTimeFieldSelect from "./PropertyControls/DateTimeFieldSelect.svelte"
|
|
||||||
import AttachmentFieldSelect from "./PropertyControls/AttachmentFieldSelect.svelte"
|
|
||||||
import RelationshipFieldSelect from "./PropertyControls/RelationshipFieldSelect.svelte"
|
|
||||||
import ResetFieldsButton from "./PropertyControls/ResetFieldsButton.svelte"
|
import ResetFieldsButton from "./PropertyControls/ResetFieldsButton.svelte"
|
||||||
import ColorPicker from "./PropertyControls/ColorPicker.svelte"
|
import { getComponentForSettingType } from "./PropertyControls/componentSettings"
|
||||||
import URLSelect from "./PropertyControls/URLSelect.svelte"
|
|
||||||
|
|
||||||
export let componentDefinition
|
export let componentDefinition
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
|
@ -45,40 +25,9 @@
|
||||||
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition
|
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition
|
||||||
|
|
||||||
const updateProp = store.actions.components.updateProp
|
const updateProp = store.actions.components.updateProp
|
||||||
const controlMap = {
|
|
||||||
text: Input,
|
|
||||||
select: Select,
|
|
||||||
dataSource: DataSourceSelect,
|
|
||||||
dataProvider: DataProviderSelect,
|
|
||||||
boolean: Checkbox,
|
|
||||||
number: Input,
|
|
||||||
event: EventsEditor,
|
|
||||||
table: TableSelect,
|
|
||||||
color: ColorPicker,
|
|
||||||
icon: IconSelect,
|
|
||||||
field: FieldSelect,
|
|
||||||
multifield: MultiFieldSelect,
|
|
||||||
schema: SchemaSelect,
|
|
||||||
section: SectionSelect,
|
|
||||||
navigation: NavigationEditor,
|
|
||||||
filter: FilterEditor,
|
|
||||||
url: URLSelect,
|
|
||||||
"field/string": StringFieldSelect,
|
|
||||||
"field/number": NumberFieldSelect,
|
|
||||||
"field/options": OptionsFieldSelect,
|
|
||||||
"field/boolean": BooleanFieldSelect,
|
|
||||||
"field/longform": LongFormFieldSelect,
|
|
||||||
"field/datetime": DateTimeFieldSelect,
|
|
||||||
"field/attachment": AttachmentFieldSelect,
|
|
||||||
"field/link": RelationshipFieldSelect,
|
|
||||||
}
|
|
||||||
|
|
||||||
const getControl = type => {
|
|
||||||
return controlMap[type]
|
|
||||||
}
|
|
||||||
|
|
||||||
const canRenderControl = setting => {
|
const canRenderControl = setting => {
|
||||||
const control = getControl(setting?.type)
|
const control = getComponentForSettingType(setting?.type)
|
||||||
if (!control) {
|
if (!control) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -101,11 +50,11 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if settings && settings.length > 0}
|
{#if settings && settings.length > 0}
|
||||||
{#each settings as setting (`${componentInstance._id}-${setting.key}`)}
|
{#each settings as setting}
|
||||||
{#if canRenderControl(setting)}
|
{#if canRenderControl(setting)}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
type={setting.type}
|
type={setting.type}
|
||||||
control={getControl(setting.type)}
|
control={getComponentForSettingType(setting.type)}
|
||||||
label={setting.label}
|
label={setting.label}
|
||||||
key={setting.key}
|
key={setting.key}
|
||||||
value={componentInstance[setting.key] ??
|
value={componentInstance[setting.key] ??
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<script>
|
||||||
|
import { DetailSummary, ActionButton, Drawer, Button } from "@budibase/bbui"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import ConditionalUIDrawer from "./PropertyControls/ConditionalUIDrawer.svelte"
|
||||||
|
|
||||||
|
export let componentInstance
|
||||||
|
|
||||||
|
let tempValue
|
||||||
|
let drawer
|
||||||
|
|
||||||
|
const openDrawer = () => {
|
||||||
|
tempValue = JSON.parse(JSON.stringify(componentInstance?._conditions ?? []))
|
||||||
|
drawer.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
store.actions.components.updateConditions(tempValue)
|
||||||
|
drawer.hide()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DetailSummary
|
||||||
|
name={`Conditions${componentInstance?._conditions ? " *" : ""}`}
|
||||||
|
collapsible={false}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<ActionButton on:click={openDrawer}>Configure conditions</ActionButton>
|
||||||
|
</div>
|
||||||
|
</DetailSummary>
|
||||||
|
<Drawer bind:this={drawer} title="Conditions">
|
||||||
|
<svelte:fragment slot="description">
|
||||||
|
Show, hide and update components in response to conditions being met.
|
||||||
|
</svelte:fragment>
|
||||||
|
<Button cta slot="buttons" on:click={() => save()}>Save</Button>
|
||||||
|
<ConditionalUIDrawer slot="body" bind:conditions={tempValue} />
|
||||||
|
</Drawer>
|
|
@ -5,6 +5,7 @@
|
||||||
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
||||||
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"
|
||||||
|
|
||||||
$: componentInstance = $selectedComponent
|
$: componentInstance = $selectedComponent
|
||||||
$: componentDefinition = store.actions.components.getDefinition(
|
$: componentDefinition = store.actions.components.getDefinition(
|
||||||
|
@ -15,10 +16,13 @@
|
||||||
<Tabs selected="Settings" noPadding>
|
<Tabs selected="Settings" noPadding>
|
||||||
<Tab title="Settings">
|
<Tab title="Settings">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
{#key componentInstance?._id}
|
||||||
<ScreenSettingsSection {componentInstance} {componentDefinition} />
|
<ScreenSettingsSection {componentInstance} {componentDefinition} />
|
||||||
<ComponentSettingsSection {componentInstance} {componentDefinition} />
|
<ComponentSettingsSection {componentInstance} {componentDefinition} />
|
||||||
<DesignSection {componentInstance} {componentDefinition} />
|
<DesignSection {componentInstance} {componentDefinition} />
|
||||||
<CustomStylesSection {componentInstance} {componentDefinition} />
|
<CustomStylesSection {componentInstance} {componentDefinition} />
|
||||||
|
<ConditionalUISection {componentInstance} {componentDefinition} />
|
||||||
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
|
@ -0,0 +1,290 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Body,
|
||||||
|
Icon,
|
||||||
|
DrawerContent,
|
||||||
|
Layout,
|
||||||
|
Select,
|
||||||
|
DatePicker,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { flip } from "svelte/animate"
|
||||||
|
import { dndzone } from "svelte-dnd-action"
|
||||||
|
import { generate } from "shortid"
|
||||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
|
||||||
|
import { getBindableProperties } from "builderStore/dataBinding"
|
||||||
|
import { currentAsset, selectedComponent, store } from "builderStore"
|
||||||
|
import { getComponentForSettingType } from "./componentSettings"
|
||||||
|
import PropertyControl from "./PropertyControl.svelte"
|
||||||
|
|
||||||
|
export let conditions = []
|
||||||
|
|
||||||
|
const flipDurationMs = 150
|
||||||
|
const actionOptions = [
|
||||||
|
{
|
||||||
|
label: "Hide component",
|
||||||
|
value: "hide",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Show component",
|
||||||
|
value: "show",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Update setting",
|
||||||
|
value: "update",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const valueTypeOptions = [
|
||||||
|
{
|
||||||
|
value: "string",
|
||||||
|
label: "Binding",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "number",
|
||||||
|
label: "Number",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "datetime",
|
||||||
|
label: "Date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "boolean",
|
||||||
|
label: "Boolean",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let dragDisabled = true
|
||||||
|
$: definition = store.actions.components.getDefinition(
|
||||||
|
$selectedComponent?._component
|
||||||
|
)
|
||||||
|
$: settings = (definition?.settings ?? []).map(setting => {
|
||||||
|
return {
|
||||||
|
label: setting.label,
|
||||||
|
value: setting.key,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$: bindableProperties = getBindableProperties(
|
||||||
|
$currentAsset,
|
||||||
|
$store.selectedComponentId
|
||||||
|
)
|
||||||
|
$: conditions.forEach(link => {
|
||||||
|
if (!link.id) {
|
||||||
|
link.id = generate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const getSettingDefinition = key => {
|
||||||
|
return definition?.settings?.find(setting => {
|
||||||
|
return setting.key === key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getComponentForSetting = key => {
|
||||||
|
const settingDefinition = getSettingDefinition(key)
|
||||||
|
return getComponentForSettingType(settingDefinition?.type || "text")
|
||||||
|
}
|
||||||
|
|
||||||
|
const addCondition = () => {
|
||||||
|
conditions = [
|
||||||
|
...conditions,
|
||||||
|
{
|
||||||
|
valueType: "string",
|
||||||
|
id: generate(),
|
||||||
|
action: "hide",
|
||||||
|
operator: OperatorOptions.Equals.value,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeCondition = id => {
|
||||||
|
conditions = conditions.filter(link => link.id !== id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFinalize = e => {
|
||||||
|
updateConditions(e)
|
||||||
|
dragDisabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateConditions = e => {
|
||||||
|
conditions = e.detail.items
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOperatorOptions = condition => {
|
||||||
|
return getValidOperatorsForType(condition.valueType)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onOperatorChange = (condition, newOperator) => {
|
||||||
|
const noValueOptions = [
|
||||||
|
OperatorOptions.Empty.value,
|
||||||
|
OperatorOptions.NotEmpty.value,
|
||||||
|
]
|
||||||
|
condition.noValue = noValueOptions.includes(newOperator)
|
||||||
|
if (condition.noValue) {
|
||||||
|
condition.referenceValue = null
|
||||||
|
condition.valueType = "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onValueTypeChange = (condition, newType) => {
|
||||||
|
condition.referenceValue = null
|
||||||
|
|
||||||
|
// Ensure a valid operator is set
|
||||||
|
const validOperators = getValidOperatorsForType(newType).map(x => x.value)
|
||||||
|
if (!validOperators.includes(condition.operator)) {
|
||||||
|
condition.operator = validOperators[0] ?? OperatorOptions.Equals.value
|
||||||
|
onOperatorChange(condition, condition.operator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerContent>
|
||||||
|
<div class="container">
|
||||||
|
<Layout noPadding>
|
||||||
|
{#if conditions?.length}
|
||||||
|
<div
|
||||||
|
class="conditions"
|
||||||
|
use:dndzone={{
|
||||||
|
items: conditions,
|
||||||
|
flipDurationMs,
|
||||||
|
dropTargetStyle: { outline: "none" },
|
||||||
|
dragDisabled,
|
||||||
|
}}
|
||||||
|
on:finalize={handleFinalize}
|
||||||
|
on:consider={updateConditions}
|
||||||
|
>
|
||||||
|
{#each conditions as condition (condition.id)}
|
||||||
|
<div
|
||||||
|
class="condition"
|
||||||
|
class:update={condition.action === "update"}
|
||||||
|
animate:flip={{ duration: flipDurationMs }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="handle"
|
||||||
|
aria-label="drag-handle"
|
||||||
|
style={dragDisabled ? "cursor: grab" : "cursor: grabbing"}
|
||||||
|
on:mousedown={() => (dragDisabled = false)}
|
||||||
|
>
|
||||||
|
<Icon name="DragHandle" size="XL" />
|
||||||
|
</div>
|
||||||
|
<Select
|
||||||
|
placeholder={null}
|
||||||
|
options={actionOptions}
|
||||||
|
bind:value={condition.action}
|
||||||
|
/>
|
||||||
|
{#if condition.action === "update"}
|
||||||
|
<Select options={settings} bind:value={condition.setting} />
|
||||||
|
<div>TO</div>
|
||||||
|
{#if getSettingDefinition(condition.setting)}
|
||||||
|
<PropertyControl
|
||||||
|
type={getSettingDefinition(condition.setting).type}
|
||||||
|
control={getComponentForSetting(condition.setting)}
|
||||||
|
key={getSettingDefinition(condition.setting).key}
|
||||||
|
value={condition.settingValue}
|
||||||
|
componentInstance={$selectedComponent}
|
||||||
|
onChange={val => (condition.settingValue = val)}
|
||||||
|
props={{
|
||||||
|
options: getSettingDefinition(condition.setting).options,
|
||||||
|
placeholder: getSettingDefinition(condition.setting)
|
||||||
|
.placeholder,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<Select disabled placeholder=" " />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
<div>IF</div>
|
||||||
|
<DrawerBindableInput
|
||||||
|
bindings={bindableProperties}
|
||||||
|
placeholder="Value"
|
||||||
|
value={condition.newValue}
|
||||||
|
on:change={e => (condition.newValue = e.detail)}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
placeholder={null}
|
||||||
|
options={getOperatorOptions(condition)}
|
||||||
|
bind:value={condition.operator}
|
||||||
|
on:change={e => onOperatorChange(condition, e.detail)}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
disabled={condition.noValue}
|
||||||
|
options={valueTypeOptions}
|
||||||
|
bind:value={condition.valueType}
|
||||||
|
placeholder={null}
|
||||||
|
on:change={e => onValueTypeChange(condition, e.detail)}
|
||||||
|
/>
|
||||||
|
{#if ["string", "number"].includes(condition.valueType)}
|
||||||
|
<DrawerBindableInput
|
||||||
|
disabled={condition.noValue}
|
||||||
|
bindings={bindableProperties}
|
||||||
|
placeholder="Value"
|
||||||
|
value={condition.referenceValue}
|
||||||
|
on:change={e => (condition.referenceValue = e.detail)}
|
||||||
|
/>
|
||||||
|
{:else if condition.valueType === "datetime"}
|
||||||
|
<DatePicker
|
||||||
|
placeholder="Value"
|
||||||
|
disabled={condition.noValue}
|
||||||
|
bind:value={condition.referenceValue}
|
||||||
|
/>
|
||||||
|
{:else if condition.valueType === "boolean"}
|
||||||
|
<Select
|
||||||
|
placeholder="Value"
|
||||||
|
disabled={condition.noValue}
|
||||||
|
options={["True", "False"]}
|
||||||
|
bind:value={condition.referenceValue}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<Icon
|
||||||
|
name="Close"
|
||||||
|
hoverable
|
||||||
|
size="S"
|
||||||
|
on:click={() => removeCondition(condition.id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<Body size="S">Add your first condition to get started.</Body>
|
||||||
|
{/if}
|
||||||
|
<div>
|
||||||
|
<Button secondary icon="Add" on:click={addCondition}>
|
||||||
|
Add condition
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.conditions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.condition {
|
||||||
|
gap: var(--spacing-l);
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: auto 1fr auto 1fr 1fr 1fr 1fr auto;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
transition: background-color ease-in-out 130ms;
|
||||||
|
}
|
||||||
|
.condition.update {
|
||||||
|
grid-template-columns: auto 1fr 1fr auto 1fr auto 1fr 1fr 1fr 1fr auto;
|
||||||
|
}
|
||||||
|
.condition:hover {
|
||||||
|
background-color: var(--spectrum-global-color-gray-100);
|
||||||
|
}
|
||||||
|
.handle {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -11,44 +11,11 @@
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
import { getBindableProperties } from "builderStore/dataBinding"
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
|
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
|
||||||
|
|
||||||
export let schemaFields
|
export let schemaFields
|
||||||
export let value
|
export let value
|
||||||
|
|
||||||
const OperatorOptions = {
|
|
||||||
Equals: {
|
|
||||||
value: "equal",
|
|
||||||
label: "Equals",
|
|
||||||
},
|
|
||||||
NotEquals: {
|
|
||||||
value: "notEqual",
|
|
||||||
label: "Not equals",
|
|
||||||
},
|
|
||||||
Empty: {
|
|
||||||
value: "empty",
|
|
||||||
label: "Is empty",
|
|
||||||
},
|
|
||||||
NotEmpty: {
|
|
||||||
value: "notEmpty",
|
|
||||||
label: "Is not empty",
|
|
||||||
},
|
|
||||||
StartsWith: {
|
|
||||||
value: "string",
|
|
||||||
label: "Starts with",
|
|
||||||
},
|
|
||||||
Like: {
|
|
||||||
value: "fuzzy",
|
|
||||||
label: "Like",
|
|
||||||
},
|
|
||||||
MoreThan: {
|
|
||||||
value: "rangeLow",
|
|
||||||
label: "More than",
|
|
||||||
},
|
|
||||||
LessThan: {
|
|
||||||
value: "rangeHigh",
|
|
||||||
label: "Less than",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
const BannedTypes = ["link", "attachment"]
|
const BannedTypes = ["link", "attachment"]
|
||||||
$: bindableProperties = getBindableProperties(
|
$: bindableProperties = getBindableProperties(
|
||||||
$currentAsset,
|
$currentAsset,
|
||||||
|
@ -75,61 +42,16 @@
|
||||||
value = value.filter(field => field.id !== id)
|
value = value.filter(field => field.id !== id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getValidOperatorsForType = type => {
|
|
||||||
const Op = OperatorOptions
|
|
||||||
if (type === "string") {
|
|
||||||
return [
|
|
||||||
Op.Equals,
|
|
||||||
Op.NotEquals,
|
|
||||||
Op.StartsWith,
|
|
||||||
Op.Like,
|
|
||||||
Op.Empty,
|
|
||||||
Op.NotEmpty,
|
|
||||||
]
|
|
||||||
} else if (type === "number") {
|
|
||||||
return [
|
|
||||||
Op.Equals,
|
|
||||||
Op.NotEquals,
|
|
||||||
Op.MoreThan,
|
|
||||||
Op.LessThan,
|
|
||||||
Op.Empty,
|
|
||||||
Op.NotEmpty,
|
|
||||||
]
|
|
||||||
} else if (type === "options") {
|
|
||||||
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
|
||||||
} else if (type === "boolean") {
|
|
||||||
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
|
||||||
} else if (type === "longform") {
|
|
||||||
return [
|
|
||||||
Op.Equals,
|
|
||||||
Op.NotEquals,
|
|
||||||
Op.StartsWith,
|
|
||||||
Op.Like,
|
|
||||||
Op.Empty,
|
|
||||||
Op.NotEmpty,
|
|
||||||
]
|
|
||||||
} else if (type === "datetime") {
|
|
||||||
return [
|
|
||||||
Op.Equals,
|
|
||||||
Op.NotEquals,
|
|
||||||
Op.MoreThan,
|
|
||||||
Op.LessThan,
|
|
||||||
Op.Empty,
|
|
||||||
Op.NotEmpty,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const onFieldChange = (expression, field) => {
|
const onFieldChange = (expression, field) => {
|
||||||
// Update the field type
|
// Update the field type
|
||||||
expression.type = schemaFields.find(x => x.name === field)?.type
|
expression.type = schemaFields.find(x => x.name === field)?.type
|
||||||
|
|
||||||
// Ensure a valid operator is set
|
// Ensure a valid operator is set
|
||||||
const validOperators = getValidOperatorsForType(expression.type)
|
const validOperators = getValidOperatorsForType(expression.type).map(
|
||||||
|
x => x.value
|
||||||
|
)
|
||||||
if (!validOperators.includes(expression.operator)) {
|
if (!validOperators.includes(expression.operator)) {
|
||||||
expression.operator =
|
expression.operator = validOperators[0] ?? OperatorOptions.Equals.value
|
||||||
validOperators[0]?.value ?? OperatorOptions.Equals.value
|
|
||||||
onOperatorChange(expression, expression.operator)
|
onOperatorChange(expression, expression.operator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,13 @@
|
||||||
export let links = []
|
export let links = []
|
||||||
|
|
||||||
const flipDurationMs = 150
|
const flipDurationMs = 150
|
||||||
|
let dragDisabled = true
|
||||||
|
|
||||||
$: links.forEach(link => {
|
$: links.forEach(link => {
|
||||||
if (!link.id) {
|
if (!link.id) {
|
||||||
link.id = generate()
|
link.id = generate()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$: urlOptions = $store.screens
|
$: urlOptions = $store.screens
|
||||||
.map(screen => screen.routing?.route)
|
.map(screen => screen.routing?.route)
|
||||||
.filter(x => x != null)
|
.filter(x => x != null)
|
||||||
|
@ -37,6 +37,11 @@
|
||||||
const updateLinks = e => {
|
const updateLinks = e => {
|
||||||
links = e.detail.items
|
links = e.detail.items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFinalize = e => {
|
||||||
|
updateLinks(e)
|
||||||
|
dragDisabled = true
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
|
@ -49,13 +54,21 @@
|
||||||
items: links,
|
items: links,
|
||||||
flipDurationMs,
|
flipDurationMs,
|
||||||
dropTargetStyle: { outline: "none" },
|
dropTargetStyle: { outline: "none" },
|
||||||
|
dragDisabled,
|
||||||
}}
|
}}
|
||||||
on:finalize={updateLinks}
|
on:finalize={handleFinalize}
|
||||||
on:consider={updateLinks}
|
on:consider={updateLinks}
|
||||||
>
|
>
|
||||||
{#each links as link (link.id)}
|
{#each links as link (link.id)}
|
||||||
<div class="link" animate:flip={{ duration: flipDurationMs }}>
|
<div class="link" animate:flip={{ duration: flipDurationMs }}>
|
||||||
|
<div
|
||||||
|
class="handle"
|
||||||
|
aria-label="drag-handle"
|
||||||
|
style={dragDisabled ? "cursor: grab" : "cursor: grabbing"}
|
||||||
|
on:mousedown={() => (dragDisabled = false)}
|
||||||
|
>
|
||||||
<Icon name="DragHandle" size="XL" />
|
<Icon name="DragHandle" size="XL" />
|
||||||
|
</div>
|
||||||
<Input bind:value={link.text} placeholder="Text" />
|
<Input bind:value={link.text} placeholder="Text" />
|
||||||
<Combobox
|
<Combobox
|
||||||
bind:value={link.url}
|
bind:value={link.url}
|
||||||
|
@ -90,7 +103,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
.link {
|
.link {
|
||||||
gap: var(--spacing-l);
|
gap: var(--spacing-l);
|
||||||
|
@ -108,4 +121,8 @@
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
.handle {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="property-control" bind:this={anchor} data-cy={`setting-${key}`}>
|
<div class="property-control" bind:this={anchor} data-cy={`setting-${key}`}>
|
||||||
{#if type !== "boolean"}
|
{#if type !== "boolean" && label}
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<Label>{label}</Label>
|
<Label>{label}</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { Checkbox, Input, Select } from "@budibase/bbui"
|
||||||
|
import DataSourceSelect from "./DataSourceSelect.svelte"
|
||||||
|
import DataProviderSelect from "./DataProviderSelect.svelte"
|
||||||
|
import EventsEditor from "./EventsEditor"
|
||||||
|
import TableSelect from "./TableSelect.svelte"
|
||||||
|
import ColorPicker from "./ColorPicker.svelte"
|
||||||
|
import { IconSelect } from "./IconSelect"
|
||||||
|
import FieldSelect from "./FieldSelect.svelte"
|
||||||
|
import MultiFieldSelect from "./MultiFieldSelect.svelte"
|
||||||
|
import SchemaSelect from "./SchemaSelect.svelte"
|
||||||
|
import SectionSelect from "./SectionSelect.svelte"
|
||||||
|
import NavigationEditor from "./NavigationEditor/NavigationEditor.svelte"
|
||||||
|
import FilterEditor from "./FilterEditor/FilterEditor.svelte"
|
||||||
|
import URLSelect from "./URLSelect.svelte"
|
||||||
|
import StringFieldSelect from "./StringFieldSelect.svelte"
|
||||||
|
import NumberFieldSelect from "./NumberFieldSelect.svelte"
|
||||||
|
import OptionsFieldSelect from "./OptionsFieldSelect.svelte"
|
||||||
|
import BooleanFieldSelect from "./BooleanFieldSelect.svelte"
|
||||||
|
import LongFormFieldSelect from "./LongFormFieldSelect.svelte"
|
||||||
|
import DateTimeFieldSelect from "./DateTimeFieldSelect.svelte"
|
||||||
|
import AttachmentFieldSelect from "./AttachmentFieldSelect.svelte"
|
||||||
|
import RelationshipFieldSelect from "./RelationshipFieldSelect.svelte"
|
||||||
|
|
||||||
|
const componentMap = {
|
||||||
|
text: Input,
|
||||||
|
select: Select,
|
||||||
|
dataSource: DataSourceSelect,
|
||||||
|
dataProvider: DataProviderSelect,
|
||||||
|
boolean: Checkbox,
|
||||||
|
number: Input,
|
||||||
|
event: EventsEditor,
|
||||||
|
table: TableSelect,
|
||||||
|
color: ColorPicker,
|
||||||
|
icon: IconSelect,
|
||||||
|
field: FieldSelect,
|
||||||
|
multifield: MultiFieldSelect,
|
||||||
|
schema: SchemaSelect,
|
||||||
|
section: SectionSelect,
|
||||||
|
navigation: NavigationEditor,
|
||||||
|
filter: FilterEditor,
|
||||||
|
url: URLSelect,
|
||||||
|
"field/string": StringFieldSelect,
|
||||||
|
"field/number": NumberFieldSelect,
|
||||||
|
"field/options": OptionsFieldSelect,
|
||||||
|
"field/boolean": BooleanFieldSelect,
|
||||||
|
"field/longform": LongFormFieldSelect,
|
||||||
|
"field/datetime": DateTimeFieldSelect,
|
||||||
|
"field/attachment": AttachmentFieldSelect,
|
||||||
|
"field/link": RelationshipFieldSelect,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getComponentForSettingType = type => {
|
||||||
|
return componentMap[type]
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
export const OperatorOptions = {
|
||||||
|
Equals: {
|
||||||
|
value: "equal",
|
||||||
|
label: "Equals",
|
||||||
|
},
|
||||||
|
NotEquals: {
|
||||||
|
value: "notEqual",
|
||||||
|
label: "Not equals",
|
||||||
|
},
|
||||||
|
Empty: {
|
||||||
|
value: "empty",
|
||||||
|
label: "Is empty",
|
||||||
|
},
|
||||||
|
NotEmpty: {
|
||||||
|
value: "notEmpty",
|
||||||
|
label: "Is not empty",
|
||||||
|
},
|
||||||
|
StartsWith: {
|
||||||
|
value: "string",
|
||||||
|
label: "Starts with",
|
||||||
|
},
|
||||||
|
Like: {
|
||||||
|
value: "fuzzy",
|
||||||
|
label: "Like",
|
||||||
|
},
|
||||||
|
MoreThan: {
|
||||||
|
value: "rangeLow",
|
||||||
|
label: "More than",
|
||||||
|
},
|
||||||
|
LessThan: {
|
||||||
|
value: "rangeHigh",
|
||||||
|
label: "Less than",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getValidOperatorsForType = type => {
|
||||||
|
const Op = OperatorOptions
|
||||||
|
if (type === "string") {
|
||||||
|
return [
|
||||||
|
Op.Equals,
|
||||||
|
Op.NotEquals,
|
||||||
|
Op.StartsWith,
|
||||||
|
Op.Like,
|
||||||
|
Op.Empty,
|
||||||
|
Op.NotEmpty,
|
||||||
|
]
|
||||||
|
} else if (type === "number") {
|
||||||
|
return [
|
||||||
|
Op.Equals,
|
||||||
|
Op.NotEquals,
|
||||||
|
Op.MoreThan,
|
||||||
|
Op.LessThan,
|
||||||
|
Op.Empty,
|
||||||
|
Op.NotEmpty,
|
||||||
|
]
|
||||||
|
} else if (type === "options") {
|
||||||
|
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
||||||
|
} else if (type === "boolean") {
|
||||||
|
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
||||||
|
} else if (type === "longform") {
|
||||||
|
return [
|
||||||
|
Op.Equals,
|
||||||
|
Op.NotEquals,
|
||||||
|
Op.StartsWith,
|
||||||
|
Op.Like,
|
||||||
|
Op.Empty,
|
||||||
|
Op.NotEmpty,
|
||||||
|
]
|
||||||
|
} else if (type === "datetime") {
|
||||||
|
return [
|
||||||
|
Op.Equals,
|
||||||
|
Op.NotEquals,
|
||||||
|
Op.MoreThan,
|
||||||
|
Op.LessThan,
|
||||||
|
Op.Empty,
|
||||||
|
Op.NotEmpty,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
|
@ -8,11 +8,18 @@
|
||||||
import { hashString } from "../utils/hash"
|
import { hashString } from "../utils/hash"
|
||||||
import Manifest from "@budibase/standard-components/manifest.json"
|
import Manifest from "@budibase/standard-components/manifest.json"
|
||||||
import { Placeholder } from "@budibase/standard-components"
|
import { Placeholder } from "@budibase/standard-components"
|
||||||
|
import {
|
||||||
|
getActiveConditions,
|
||||||
|
reduceConditionActions,
|
||||||
|
} from "../utils/conditions"
|
||||||
|
|
||||||
export let instance = {}
|
export let instance = {}
|
||||||
|
|
||||||
// Props that will be passed to the component instance
|
// The enriched component settings
|
||||||
let componentProps
|
let enrichedSettings
|
||||||
|
|
||||||
|
// 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
|
// Props are hashed when inside the builder preview and used as a key, so that
|
||||||
// components fully remount whenever any props change
|
// components fully remount whenever any props change
|
||||||
|
@ -28,6 +35,9 @@
|
||||||
let lastContextKey
|
let lastContextKey
|
||||||
let lastInstanceKey
|
let lastInstanceKey
|
||||||
|
|
||||||
|
// Visibility flag used by conditional UI
|
||||||
|
let visible = true
|
||||||
|
|
||||||
// Get contexts
|
// Get contexts
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
const insideScreenslot = !!getContext("screenslot")
|
const insideScreenslot = !!getContext("screenslot")
|
||||||
|
@ -54,6 +64,8 @@
|
||||||
$builderStore.inBuilder &&
|
$builderStore.inBuilder &&
|
||||||
$builderStore.selectedComponentId === instance._id
|
$builderStore.selectedComponentId === instance._id
|
||||||
$: interactive = $builderStore.previewType === "layout" || insideScreenslot
|
$: interactive = $builderStore.previewType === "layout" || insideScreenslot
|
||||||
|
$: evaluateConditions(enrichedSettings?._conditions)
|
||||||
|
$: componentSettings = { ...enrichedSettings, ...conditionalSettings }
|
||||||
|
|
||||||
// Update component context
|
// Update component context
|
||||||
$: componentStore.set({
|
$: componentStore.set({
|
||||||
|
@ -62,14 +74,14 @@
|
||||||
styles: { ...instance._styles, id, empty, interactive },
|
styles: { ...instance._styles, id, empty, interactive },
|
||||||
empty,
|
empty,
|
||||||
selected,
|
selected,
|
||||||
props: componentProps,
|
props: componentSettings,
|
||||||
name,
|
name,
|
||||||
})
|
})
|
||||||
|
|
||||||
const getRawProps = instance => {
|
const getRawProps = instance => {
|
||||||
let validProps = {}
|
let validProps = {}
|
||||||
Object.entries(instance)
|
Object.entries(instance)
|
||||||
.filter(([name]) => !name.startsWith("_"))
|
.filter(([name]) => name === "_conditions" || !name.startsWith("_"))
|
||||||
.forEach(([key, value]) => {
|
.forEach(([key, value]) => {
|
||||||
validProps[key] = value
|
validProps[key] = value
|
||||||
})
|
})
|
||||||
|
@ -123,34 +135,55 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let propsChanged = false
|
let propsChanged = false
|
||||||
if (!componentProps) {
|
if (!enrichedSettings) {
|
||||||
componentProps = {}
|
enrichedSettings = {}
|
||||||
propsChanged = true
|
propsChanged = true
|
||||||
}
|
}
|
||||||
Object.keys(enrichedProps).forEach(key => {
|
Object.keys(enrichedProps).forEach(key => {
|
||||||
if (!propsAreSame(enrichedProps[key], componentProps[key])) {
|
if (!propsAreSame(enrichedProps[key], enrichedSettings[key])) {
|
||||||
propsChanged = true
|
propsChanged = true
|
||||||
componentProps[key] = enrichedProps[key]
|
enrichedSettings[key] = enrichedProps[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(componentProps))
|
propsHash = hashString(JSON.stringify(enrichedSettings))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const evaluateConditions = conditions => {
|
||||||
|
if (!conditions?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default visible to false if there is a show condition
|
||||||
|
let nextVisible = !conditions.find(condition => condition.action === "show")
|
||||||
|
|
||||||
|
// Execute conditions and determine settings and visibility changes
|
||||||
|
const activeConditions = getActiveConditions(conditions)
|
||||||
|
const result = reduceConditionActions(activeConditions)
|
||||||
|
if (result.visible != null) {
|
||||||
|
nextVisible = result.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update state from condition results
|
||||||
|
conditionalSettings = result.settingUpdates
|
||||||
|
visible = nextVisible
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#key propsHash}
|
||||||
|
{#if constructor && componentSettings && visible}
|
||||||
<div
|
<div
|
||||||
class={`component ${id}`}
|
class={`component ${id}`}
|
||||||
data-type={interactive ? "component" : ""}
|
data-type={interactive ? "component" : ""}
|
||||||
data-id={id}
|
data-id={id}
|
||||||
data-name={name}
|
data-name={name}
|
||||||
|
class:hidden={!visible}
|
||||||
>
|
>
|
||||||
{#key propsHash}
|
<svelte:component this={constructor} {...componentSettings}>
|
||||||
{#if constructor && componentProps}
|
|
||||||
<svelte:component this={constructor} {...componentProps}>
|
|
||||||
{#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} />
|
||||||
|
@ -159,9 +192,9 @@
|
||||||
<Placeholder />
|
<Placeholder />
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:component>
|
</svelte:component>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.component {
|
.component {
|
||||||
|
|
|
@ -43,5 +43,17 @@ export const enrichProps = (props, context) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enrich any click actions in conditions
|
||||||
|
if (enrichedProps._conditions) {
|
||||||
|
enrichedProps._conditions.forEach(condition => {
|
||||||
|
if (condition.setting === "onClick") {
|
||||||
|
condition.settingValue = enrichButtonActions(
|
||||||
|
condition.settingValue,
|
||||||
|
totalContext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return enrichedProps
|
return enrichedProps
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
import {
|
||||||
|
buildLuceneQuery,
|
||||||
|
luceneQuery,
|
||||||
|
} from "../../../standard-components/src/lucene"
|
||||||
|
|
||||||
|
export const getActiveConditions = conditions => {
|
||||||
|
if (!conditions?.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return conditions.filter(condition => {
|
||||||
|
// Parse values into correct types
|
||||||
|
if (condition.valueType === "number") {
|
||||||
|
condition.referenceValue = parseFloat(condition.referenceValue)
|
||||||
|
condition.newValue = parseFloat(condition.newValue)
|
||||||
|
} else if (condition.valueType === "datetime") {
|
||||||
|
if (condition.referenceValue) {
|
||||||
|
condition.referenceValue = new Date(
|
||||||
|
condition.referenceValue
|
||||||
|
).toISOString()
|
||||||
|
}
|
||||||
|
if (condition.newValue) {
|
||||||
|
condition.newValue = new Date(condition.newValue).toISOString()
|
||||||
|
}
|
||||||
|
} else if (condition.valueType === "boolean") {
|
||||||
|
condition.referenceValue =
|
||||||
|
`${condition.referenceValue}`.toLowerCase() === "true"
|
||||||
|
condition.newValue = `${condition.newValue}`.toLowerCase() === "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build lucene compatible condition expression
|
||||||
|
const luceneCondition = {
|
||||||
|
...condition,
|
||||||
|
type: condition.valueType,
|
||||||
|
field: "newValue",
|
||||||
|
value: condition.referenceValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = buildLuceneQuery([luceneCondition])
|
||||||
|
const result = luceneQuery([luceneCondition], query)
|
||||||
|
return result.length > 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const reduceConditionActions = conditions => {
|
||||||
|
let settingUpdates = {}
|
||||||
|
let visible = null
|
||||||
|
|
||||||
|
conditions?.forEach(condition => {
|
||||||
|
if (condition.action === "show") {
|
||||||
|
visible = true
|
||||||
|
} else if (condition.action === "hide") {
|
||||||
|
visible = false
|
||||||
|
} else if (condition.setting) {
|
||||||
|
settingUpdates[condition.setting] = condition.settingValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return { settingUpdates, visible }
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ export const buildLuceneQuery = filter => {
|
||||||
value = parseFloat(value)
|
value = parseFloat(value)
|
||||||
}
|
}
|
||||||
if (type === "boolean") {
|
if (type === "boolean") {
|
||||||
value = value?.toLowerCase() === "true"
|
value = `${value}`?.toLowerCase() === "true"
|
||||||
}
|
}
|
||||||
if (operator.startsWith("range")) {
|
if (operator.startsWith("range")) {
|
||||||
if (!query.range[field]) {
|
if (!query.range[field]) {
|
||||||
|
@ -91,6 +91,11 @@ export const luceneQuery = (docs, query) => {
|
||||||
return !doc[key] || !doc[key].startsWith(value)
|
return !doc[key] || !doc[key].startsWith(value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Process a fuzzy match (treat the same as starts with when running locally)
|
||||||
|
const fuzzyMatch = match("fuzzy", (key, value, doc) => {
|
||||||
|
return !doc[key] || !doc[key].startsWith(value)
|
||||||
|
})
|
||||||
|
|
||||||
// Process a range match
|
// Process a range match
|
||||||
const rangeMatch = match("range", (key, value, doc) => {
|
const rangeMatch = match("range", (key, value, doc) => {
|
||||||
return !doc[key] || doc[key] < value.low || doc[key] > value.high
|
return !doc[key] || doc[key] < value.low || doc[key] > value.high
|
||||||
|
@ -120,6 +125,7 @@ export const luceneQuery = (docs, query) => {
|
||||||
const docMatch = doc => {
|
const docMatch = doc => {
|
||||||
return (
|
return (
|
||||||
stringMatch(doc) &&
|
stringMatch(doc) &&
|
||||||
|
fuzzyMatch(doc) &&
|
||||||
rangeMatch(doc) &&
|
rangeMatch(doc) &&
|
||||||
equalMatch(doc) &&
|
equalMatch(doc) &&
|
||||||
notEqualMatch(doc) &&
|
notEqualMatch(doc) &&
|
||||||
|
|
Loading…
Reference in New Issue