budibase/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ConditionalUIDrawer.svelte

301 lines
8.6 KiB
Svelte

<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 { LuceneUtils, Constants } from "@budibase/frontend-core"
import { selectedComponent } from "builderStore"
import { getComponentForSettingType } from "./componentSettings"
import PropertyControl from "./PropertyControl.svelte"
import { getComponentSettings } from "builderStore/componentUtils"
export let conditions = []
export let bindings = []
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
$: settings = getComponentSettings($selectedComponent?._component)
$: settingOptions = settings.map(setting => ({
label: setting.label,
value: setting.key,
}))
$: conditions.forEach(link => {
if (!link.id) {
link.id = generate()
}
})
const getSettingDefinition = key => {
return settings.find(setting => 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: Constants.OperatorOptions.Equals.value,
},
]
}
const removeCondition = id => {
conditions = conditions.filter(link => link.id !== id)
}
const duplicateCondition = id => {
const condition = conditions.find(link => link.id === id)
const duplicate = { ...condition, id: generate() }
conditions = [...conditions, duplicate]
}
const handleFinalize = e => {
updateConditions(e)
dragDisabled = true
}
const updateConditions = e => {
conditions = e.detail.items
}
const getOperatorOptions = condition => {
return LuceneUtils.getValidOperatorsForType(condition.valueType)
}
const onOperatorChange = (condition, newOperator) => {
const noValueOptions = [
Constants.OperatorOptions.Empty.value,
Constants.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 = LuceneUtils.getValidOperatorsForType(newType).map(
x => x.value
)
if (!validOperators.includes(condition.operator)) {
condition.operator =
validOperators[0] ?? Constants.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={settingOptions}
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,
}}
{bindings}
/>
{:else}
<Select disabled placeholder=" " />
{/if}
{/if}
<div>IF</div>
<DrawerBindableInput
{bindings}
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}
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="Duplicate"
hoverable
size="S"
on:click={() => duplicateCondition(condition.id)}
/>
<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 160px auto 1fr 130px 130px 1fr auto auto;
border-radius: var(--border-radius-s);
transition: background-color ease-in-out 130ms;
}
.condition.update {
grid-template-columns: auto 160px 1fr auto 1fr auto 1fr 130px 130px 1fr auto auto;
}
.condition:hover {
background-color: var(--spectrum-global-color-gray-100);
}
.handle {
display: grid;
place-items: center;
}
</style>