budibase/packages/builder/src/components/automation/AutomationBuilder/FlowChart/BranchNode.svelte

346 lines
8.1 KiB
Svelte

<script>
import FilterBuilder from "@/components/design/settings/controls/FilterEditor/FilterBuilder.svelte"
import {
Drawer,
DrawerContent,
Icon,
Body,
Divider,
Button,
Modal,
ModalContent,
} from "@budibase/bbui"
import PropField from "@/components/automation/SetupPanel/PropField.svelte"
import AutomationBindingPanel from "@/components/common/bindings/ServerBindingPanel.svelte"
import FlowItemActions from "./FlowItemActions.svelte"
import FlowItemStatus from "./FlowItemStatus.svelte"
import {
automationStore,
selectedAutomation,
evaluationContext,
contextMenuStore,
} from "@/stores/builder"
import { QueryUtils, Utils, memo } from "@budibase/frontend-core"
import { cloneDeep } from "lodash/fp"
import { createEventDispatcher, getContext } from "svelte"
import DragZone from "./DragZone.svelte"
import BlockHeader from "../../SetupPanel/BlockHeader.svelte"
const dispatch = createEventDispatcher()
export let pathTo
export let branchIdx
export let step
export let isLast
export let bindings
export let automation
const view = getContext("draggableView")
const memoContext = memo({})
let drawer
let confirmDeleteModal
$: memoContext.set($evaluationContext)
$: branch = step.inputs?.branches?.[branchIdx]
$: editableConditionUI = branch.conditionUI || {}
// Parse all the bindings into fields for the condition builder
$: schemaFields = bindings.map(binding => {
return {
name: `{{${binding.runtimeBinding}}}`,
displayName: `${binding.category} - ${binding.display.name}`,
type: "string",
}
})
$: branchBlockRef = {
branchNode: true,
pathTo: (pathTo || []).concat({ branchIdx, branchStepId: step.id }),
}
const getContextMenuItems = () => {
return [
{
icon: "Delete",
name: "Delete",
keyBind: null,
visible: true,
disabled: false,
callback: async () => {
const branchSteps = step.inputs?.children[branch.id]
if (branchSteps.length) {
confirmDeleteModal.show()
} else {
await automationStore.actions.deleteBranch(
branchBlockRef.pathTo,
$selectedAutomation.data
)
}
},
},
{
icon: "ArrowLeft",
name: "Move left",
keyBind: null,
visible: true,
disabled: branchIdx == 0,
callback: async () => {
automationStore.actions.branchLeft(
branchBlockRef.pathTo,
$selectedAutomation.data,
step
)
},
},
{
icon: "ArrowRight",
name: "Move right",
keyBind: null,
visible: true,
disabled: isLast,
callback: async () => {
automationStore.actions.branchRight(
branchBlockRef.pathTo,
$selectedAutomation.data,
step
)
},
},
]
}
const openContextMenu = e => {
e.preventDefault()
e.stopPropagation()
const items = getContextMenuItems()
contextMenuStore.open(branch.id, items, { x: e.clientX, y: e.clientY })
}
</script>
<Modal bind:this={confirmDeleteModal}>
<ModalContent
size="M"
title={"Are you sure you want to delete?"}
confirmText="Delete"
onConfirm={async () => {
await automationStore.actions.deleteBranch(
branchBlockRef.pathTo,
$selectedAutomation.data
)
}}
>
<Body>By deleting this branch, you will delete all of its contents.</Body>
</ModalContent>
</Modal>
<Drawer bind:this={drawer} title="Branch condition" forceModal>
<Button
cta
slot="buttons"
on:click={() => {
drawer.hide()
const updatedConditionsUI = Utils.parseFilter(editableConditionUI)
dispatch("change", {
conditionUI: updatedConditionsUI,
condition: QueryUtils.buildQuery(updatedConditionsUI),
})
}}
>
Save
</Button>
<DrawerContent slot="body">
<FilterBuilder
filters={editableConditionUI}
{bindings}
{schemaFields}
datasource={{ type: "custom" }}
panel={AutomationBindingPanel}
on:change={e => {
editableConditionUI = e.detail
}}
allowOnEmpty={false}
builderType={"condition"}
docsURL={null}
evaluationContext={$memoContext}
/>
</DrawerContent>
</Drawer>
<div class="flow-item branch">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class={`block branch-node hoverable`}
class:selected={false}
on:mousedown={e => {
e.stopPropagation()
}}
>
<div class="block-float">
<FlowItemStatus
block={step}
{automation}
{branch}
hideStatus={$view?.dragging}
/>
</div>
<div class="blockSection">
<div class="heading">
<BlockHeader
{automation}
block={step}
itemName={branch.name}
on:update={async e => {
let stepUpdate = cloneDeep(step)
let branchUpdate = stepUpdate.inputs?.branches.find(
stepBranch => stepBranch.id == branch.id
)
branchUpdate.name = e.detail
const updatedAuto = automationStore.actions.updateStep(
pathTo,
$selectedAutomation.data,
stepUpdate
)
await automationStore.actions.save(updatedAuto)
}}
/>
<div class="actions">
<Icon
name="Info"
tooltip="Branch sequencing checks each option in order and follows the first one that matches the rules."
/>
<Icon
on:click={e => {
openContextMenu(e)
}}
size="S"
hoverable
name="MoreSmallList"
/>
</div>
</div>
</div>
<Divider noMargin />
<div class="blockSection filter-button">
<PropField label="Only run when:" fullWidth>
<div style="width: 100%">
<Button secondary on:click={drawer.show}>
{editableConditionUI?.groups?.length
? "Update condition"
: "Add condition"}
</Button>
</div>
</PropField>
</div>
</div>
<div class="separator" />
{#if $view.dragging}
<DragZone path={branchBlockRef.pathTo} />
{:else}
<FlowItemActions block={branchBlockRef} />
{/if}
{#if step.inputs.children[branch.id]?.length}
<div class="separator" />
{/if}
</div>
<style>
.filter-button :global(.spectrum-Button) {
width: 100%;
}
.branch-actions {
display: flex;
gap: var(--spacing-l);
cursor: default;
}
.footer {
display: flex;
align-items: center;
gap: var(--spacing-m);
}
.flow-item {
display: flex;
flex-direction: column;
align-items: center;
}
.block-options {
justify-content: flex-end;
align-items: center;
display: flex;
gap: var(--spacing-m);
}
.center-items {
display: flex;
align-items: center;
}
.splitHeader {
display: flex;
justify-content: space-between;
align-items: center;
}
.iconAlign {
padding: 0 0 0 var(--spacing-m);
display: inline-block;
}
.block {
width: 360px;
font-size: 16px;
background-color: var(--background);
border: 1px solid var(--spectrum-global-color-gray-300);
border-radius: 4px 4px 4px 4px;
cursor: default;
}
.blockSection {
padding: var(--spacing-xl);
}
.separator {
width: 1px;
height: 25px;
border-left: 1px dashed var(--grey-4);
color: var(--grey-4);
align-self: center;
}
.blockTitle {
display: flex;
align-items: center;
gap: var(--spacing-s);
}
.branch-node {
position: relative;
}
.block-float {
pointer-events: none;
width: 100%;
position: absolute;
top: -35px;
left: 0px;
}
.blockSection .heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-m);
}
.blockSection .heading .actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-m);
}
</style>