mirror of https://github.com/Budibase/budibase.git
27 changed files with 467 additions and 496 deletions
@ -1,93 +1,31 @@ |
|||||
<script> |
<script> |
||||
import { afterUpdate } from "svelte" |
|
||||
import { automationStore, backendUiStore } from "builderStore" |
import { automationStore, backendUiStore } from "builderStore" |
||||
import { notifier } from "builderStore/store/notifications" |
|
||||
import Flowchart from "./flowchart/FlowChart.svelte" |
import Flowchart from "./flowchart/FlowChart.svelte" |
||||
|
import BlockList from "./BlockList.svelte" |
||||
|
|
||||
$: automation = $automationStore.selectedAutomation?.automation |
$: automation = $automationStore.selectedAutomation?.automation |
||||
$: automationLive = automation?.live |
$: automationLive = automation?.live |
||||
$: instanceId = $backendUiStore.selectedDatabase._id |
$: instanceId = $backendUiStore.selectedDatabase._id |
||||
|
$: automationCount = $automationStore.automations?.length ?? 0 |
||||
|
|
||||
function onSelect(block) { |
function onSelect(block) { |
||||
automationStore.update(state => { |
automationStore.update((state) => { |
||||
state.selectedBlock = block |
state.selectedBlock = block |
||||
return state |
return state |
||||
}) |
}) |
||||
} |
} |
||||
|
|
||||
function setAutomationLive(live) { |
|
||||
automation.live = live |
|
||||
automationStore.actions.save({ instanceId, automation }) |
|
||||
if (live) { |
|
||||
notifier.info(`Automation ${automation.name} enabled.`) |
|
||||
} else { |
|
||||
notifier.danger(`Automation ${automation.name} disabled.`) |
|
||||
} |
|
||||
} |
|
||||
</script> |
</script> |
||||
|
|
||||
<section> |
{#if automation} |
||||
|
<BlockList /> |
||||
<Flowchart {automation} {onSelect} /> |
<Flowchart {automation} {onSelect} /> |
||||
</section> |
{:else if automationCount === 0} |
||||
<footer> |
<i>Create your first automation to get started</i> |
||||
{#if automation} |
{:else}<i>Select an automation to edit</i>{/if} |
||||
<button |
|
||||
class:highlighted={automationLive} |
|
||||
class:hoverable={automationLive} |
|
||||
class="stop-button hoverable"> |
|
||||
<i class="ri-stop-fill" on:click={() => setAutomationLive(false)} /> |
|
||||
</button> |
|
||||
<button |
|
||||
class:highlighted={!automationLive} |
|
||||
class:hoverable={!automationLive} |
|
||||
class="play-button hoverable" |
|
||||
data-cy="activate-automation" |
|
||||
on:click={() => setAutomationLive(true)}> |
|
||||
<i class="ri-play-fill" /> |
|
||||
</button> |
|
||||
{/if} |
|
||||
</footer> |
|
||||
|
|
||||
<style> |
<style> |
||||
section { |
i { |
||||
display: flex; |
font-size: var(--font-size-m); |
||||
flex-direction: column; |
color: var(--grey-5); |
||||
justify-content: flex-start; |
|
||||
align-items: center; |
|
||||
overflow: auto; |
|
||||
height: 100%; |
|
||||
position: relative; |
|
||||
} |
|
||||
|
|
||||
footer { |
|
||||
position: absolute; |
|
||||
bottom: var(--spacing-xl); |
|
||||
right: 30px; |
|
||||
display: flex; |
|
||||
align-items: flex-end; |
|
||||
} |
|
||||
|
|
||||
footer > button { |
|
||||
border-radius: 100%; |
|
||||
color: var(--white); |
|
||||
width: 76px; |
|
||||
height: 76px; |
|
||||
border: none; |
|
||||
background: #adaec4; |
|
||||
font-size: 45px; |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
justify-content: center; |
|
||||
} |
|
||||
footer > button:first-child { |
|
||||
margin-right: var(--spacing-m); |
|
||||
} |
|
||||
|
|
||||
.play-button.highlighted { |
|
||||
background: var(--purple); |
|
||||
} |
|
||||
|
|
||||
.stop-button.highlighted { |
|
||||
background: var(--red); |
|
||||
} |
} |
||||
</style> |
</style> |
||||
|
|||||
@ -0,0 +1,129 @@ |
|||||
|
<script> |
||||
|
import { sortBy } from "lodash/fp" |
||||
|
import { automationStore } from "builderStore" |
||||
|
import { DropdownMenu } from "@budibase/bbui" |
||||
|
import { DropdownContainer, DropdownItem } from "../../common/Dropdowns" |
||||
|
import analytics from "analytics" |
||||
|
|
||||
|
$: hasTrigger = $automationStore.selectedAutomation.hasTrigger() |
||||
|
$: tabs = [ |
||||
|
{ |
||||
|
label: "Trigger", |
||||
|
value: "TRIGGER", |
||||
|
icon: "ri-organization-chart", |
||||
|
disabled: hasTrigger, |
||||
|
}, |
||||
|
{ |
||||
|
label: "Action", |
||||
|
value: "ACTION", |
||||
|
icon: "ri-flow-chart", |
||||
|
disabled: !hasTrigger, |
||||
|
}, |
||||
|
{ |
||||
|
label: "Logic", |
||||
|
value: "LOGIC", |
||||
|
icon: "ri-filter-line", |
||||
|
disabled: !hasTrigger, |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
let buttonProps = [] |
||||
|
let selectedIndex |
||||
|
let anchors = [] |
||||
|
let popover |
||||
|
$: selectedTab = selectedIndex == null ? null : tabs[selectedIndex].value |
||||
|
$: anchor = selectedIndex === -1 ? null : anchors[selectedIndex] |
||||
|
$: blocks = sortBy((entry) => entry[1].name)( |
||||
|
Object.entries($automationStore.blockDefinitions[selectedTab] ?? {}) |
||||
|
) |
||||
|
|
||||
|
function onChangeTab(idx) { |
||||
|
selectedIndex = idx |
||||
|
popover.show() |
||||
|
} |
||||
|
|
||||
|
function closePopover() { |
||||
|
selectedIndex = null |
||||
|
popover.hide() |
||||
|
} |
||||
|
|
||||
|
function addBlockToAutomation(stepId, blockDefinition) { |
||||
|
const newBlock = { |
||||
|
...blockDefinition, |
||||
|
inputs: blockDefinition.inputs || {}, |
||||
|
stepId, |
||||
|
type: selectedTab, |
||||
|
} |
||||
|
automationStore.actions.addBlockToAutomation(newBlock) |
||||
|
analytics.captureEvent("Added Automation Block", { |
||||
|
name: blockDefinition.name, |
||||
|
}) |
||||
|
closePopover() |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="tab-container"> |
||||
|
{#each tabs as tab, idx} |
||||
|
<div |
||||
|
bind:this={anchors[idx]} |
||||
|
class="tab" |
||||
|
class:disabled={tab.disabled} |
||||
|
on:click={tab.disabled ? null : () => onChangeTab(idx)} |
||||
|
class:active={idx === selectedIndex}> |
||||
|
{#if tab.icon}<i class={tab.icon} />{/if} |
||||
|
<span>{tab.label}</span> |
||||
|
<i class="ri-arrow-down-s-line arrow" /> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
<DropdownMenu |
||||
|
on:close={() => (selectedIndex = null)} |
||||
|
bind:this={popover} |
||||
|
{anchor} |
||||
|
align="left"> |
||||
|
<DropdownContainer> |
||||
|
{#each blocks as [stepId, blockDefinition]} |
||||
|
<DropdownItem |
||||
|
icon={blockDefinition.icon} |
||||
|
title={blockDefinition.name} |
||||
|
subtitle={blockDefinition.description} |
||||
|
on:click={() => addBlockToAutomation(stepId, blockDefinition)} /> |
||||
|
{/each} |
||||
|
</DropdownContainer> |
||||
|
</DropdownMenu> |
||||
|
|
||||
|
<style> |
||||
|
.tab-container { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: flex-start; |
||||
|
align-items: center; |
||||
|
gap: var(--spacing-l); |
||||
|
min-height: 24px; |
||||
|
} |
||||
|
|
||||
|
.tab { |
||||
|
color: var(--grey-7); |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: flex-start; |
||||
|
align-items: center; |
||||
|
gap: var(--spacing-xs); |
||||
|
font-size: var(--font-size-xs); |
||||
|
} |
||||
|
.tab span { |
||||
|
font-weight: 500; |
||||
|
user-select: none; |
||||
|
} |
||||
|
.tab.active, |
||||
|
.tab:not(.disabled):hover { |
||||
|
color: var(--ink); |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
.tab.disabled { |
||||
|
color: var(--grey-5); |
||||
|
} |
||||
|
.tab i:not(:last-child) { |
||||
|
font-size: 16px; |
||||
|
} |
||||
|
</style> |
||||
|
Before Width: | Height: | Size: 303 B After Width: | Height: | Size: 303 B |
@ -0,0 +1,34 @@ |
|||||
|
<script> |
||||
|
import { onMount } from "svelte" |
||||
|
import { automationStore } from "builderStore" |
||||
|
import NavItem from "components/common/NavItem.svelte" |
||||
|
import EditAutomationPopover from "./EditAutomationPopover.svelte" |
||||
|
|
||||
|
$: selectedAutomationId = $automationStore.selectedAutomation?.automation?._id |
||||
|
|
||||
|
onMount(() => { |
||||
|
automationStore.actions.fetch() |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<div class="automations-list"> |
||||
|
{#each $automationStore.automations as automation, idx} |
||||
|
<NavItem |
||||
|
border={idx > 0} |
||||
|
icon="ri-stackshare-line" |
||||
|
text={automation.name} |
||||
|
selected={automation._id === selectedAutomationId} |
||||
|
on:click={() => automationStore.actions.select(automation)}> |
||||
|
<EditAutomationPopover {automation} /> |
||||
|
</NavItem> |
||||
|
{/each} |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.automations-list { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: flex-start; |
||||
|
align-items: stretch; |
||||
|
} |
||||
|
</style> |
||||
@ -1,79 +0,0 @@ |
|||||
<script> |
|
||||
import { onMount } from "svelte" |
|
||||
import { automationStore } from "builderStore" |
|
||||
import CreateAutomationModal from "./CreateAutomationModal.svelte" |
|
||||
import { Button, Modal } from "@budibase/bbui" |
|
||||
|
|
||||
let modal |
|
||||
|
|
||||
$: selectedAutomationId = $automationStore.selectedAutomation?.automation?._id |
|
||||
|
|
||||
onMount(() => { |
|
||||
automationStore.actions.fetch() |
|
||||
}) |
|
||||
</script> |
|
||||
|
|
||||
<section> |
|
||||
<Button primary wide on:click={modal.show}>Create New Automation</Button> |
|
||||
<ul> |
|
||||
{#each $automationStore.automations as automation} |
|
||||
<li |
|
||||
class="automation-item" |
|
||||
class:selected={automation._id === selectedAutomationId} |
|
||||
on:click={() => automationStore.actions.select(automation)}> |
|
||||
<i class="ri-stackshare-line" class:live={automation.live} /> |
|
||||
{automation.name} |
|
||||
</li> |
|
||||
{/each} |
|
||||
</ul> |
|
||||
</section> |
|
||||
<Modal bind:this={modal}> |
|
||||
<CreateAutomationModal /> |
|
||||
</Modal> |
|
||||
|
|
||||
<style> |
|
||||
section { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
} |
|
||||
|
|
||||
ul { |
|
||||
list-style-type: none; |
|
||||
padding: 0; |
|
||||
margin: var(--spacing-xl) 0 0 0; |
|
||||
flex: 1; |
|
||||
} |
|
||||
|
|
||||
i { |
|
||||
color: var(--grey-6); |
|
||||
} |
|
||||
i.live { |
|
||||
color: var(--ink); |
|
||||
} |
|
||||
|
|
||||
li { |
|
||||
font-size: 14px; |
|
||||
} |
|
||||
|
|
||||
.automation-item { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: flex-start; |
|
||||
align-items: center; |
|
||||
border-radius: var(--border-radius-m); |
|
||||
padding: var(--spacing-s) var(--spacing-m); |
|
||||
margin-bottom: var(--spacing-xs); |
|
||||
color: var(--ink); |
|
||||
} |
|
||||
.automation-item i { |
|
||||
font-size: 24px; |
|
||||
margin-right: var(--spacing-m); |
|
||||
} |
|
||||
.automation-item:hover { |
|
||||
cursor: pointer; |
|
||||
background: var(--grey-1); |
|
||||
} |
|
||||
.automation-item.selected { |
|
||||
background: var(--grey-2); |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,68 +0,0 @@ |
|||||
<script> |
|
||||
import { automationStore } from "builderStore" |
|
||||
import analytics from "analytics" |
|
||||
|
|
||||
export let blockDefinition |
|
||||
export let stepId |
|
||||
export let blockType |
|
||||
|
|
||||
function addBlockToAutomation() { |
|
||||
automationStore.actions.addBlockToAutomation({ |
|
||||
...blockDefinition, |
|
||||
inputs: blockDefinition.inputs || {}, |
|
||||
stepId, |
|
||||
type: blockType, |
|
||||
}) |
|
||||
analytics.captureEvent("Added Automation Block", { |
|
||||
name: blockDefinition.name, |
|
||||
}) |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<div |
|
||||
class="automation-block hoverable" |
|
||||
on:click={addBlockToAutomation} |
|
||||
data-cy={stepId}> |
|
||||
<div><i class={blockDefinition.icon} /></div> |
|
||||
<div class="automation-text"> |
|
||||
<h4>{blockDefinition.name}</h4> |
|
||||
<p>{blockDefinition.description}</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.automation-block { |
|
||||
display: grid; |
|
||||
grid-template-columns: 20px auto; |
|
||||
align-items: center; |
|
||||
margin-top: var(--spacing-s); |
|
||||
padding: var(--spacing-m); |
|
||||
border-radius: var(--border-radius-m); |
|
||||
} |
|
||||
.automation-block:hover { |
|
||||
background-color: var(--grey-1); |
|
||||
} |
|
||||
.automation-block:first-child { |
|
||||
margin-top: 0; |
|
||||
} |
|
||||
|
|
||||
i { |
|
||||
color: var(--grey-7); |
|
||||
font-size: 20px; |
|
||||
} |
|
||||
|
|
||||
.automation-text { |
|
||||
margin-left: 16px; |
|
||||
} |
|
||||
.automation-text h4 { |
|
||||
font-size: 14px; |
|
||||
font-weight: 500; |
|
||||
margin-bottom: 5px; |
|
||||
margin-top: 0; |
|
||||
} |
|
||||
.automation-text p { |
|
||||
font-size: 12px; |
|
||||
color: var(--grey-7); |
|
||||
margin: 0; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,48 +0,0 @@ |
|||||
<script> |
|
||||
import { sortBy } from "lodash/fp" |
|
||||
import { automationStore } from "builderStore" |
|
||||
import AutomationBlock from "./AutomationBlock.svelte" |
|
||||
import FlatButtonGroup from "components/userInterface/FlatButtonGroup.svelte" |
|
||||
|
|
||||
let selectedTab = "TRIGGER" |
|
||||
let buttonProps = [] |
|
||||
$: blocks = sortBy(entry => entry[1].name)( |
|
||||
Object.entries($automationStore.blockDefinitions[selectedTab]) |
|
||||
) |
|
||||
|
|
||||
$: { |
|
||||
if ($automationStore.selectedAutomation.hasTrigger()) { |
|
||||
buttonProps = [ |
|
||||
{ value: "ACTION", text: "Action" }, |
|
||||
{ value: "LOGIC", text: "Logic" }, |
|
||||
] |
|
||||
if (selectedTab === "TRIGGER") { |
|
||||
selectedTab = "ACTION" |
|
||||
} |
|
||||
} else { |
|
||||
buttonProps = [{ value: "TRIGGER", text: "Trigger" }] |
|
||||
if (selectedTab !== "TRIGGER") { |
|
||||
selectedTab = "TRIGGER" |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function onChangeTab(tab) { |
|
||||
selectedTab = tab |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<section> |
|
||||
<FlatButtonGroup value={selectedTab} {buttonProps} onChange={onChangeTab} /> |
|
||||
<div id="blocklist"> |
|
||||
{#each blocks as [stepId, blockDefinition]} |
|
||||
<AutomationBlock {blockDefinition} {stepId} blockType={selectedTab} /> |
|
||||
{/each} |
|
||||
</div> |
|
||||
</section> |
|
||||
|
|
||||
<style> |
|
||||
#blocklist { |
|
||||
margin-top: var(--spacing-xl); |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,86 @@ |
|||||
|
<script> |
||||
|
import { automationStore, backendUiStore } from "builderStore" |
||||
|
import { notifier } from "builderStore/store/notifications" |
||||
|
import { DropdownMenu } from "@budibase/bbui" |
||||
|
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" |
||||
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
||||
|
|
||||
|
export let automation |
||||
|
|
||||
|
let anchor |
||||
|
let dropdown |
||||
|
let confirmDeleteDialog |
||||
|
$: instanceId = $backendUiStore.selectedDatabase._id |
||||
|
|
||||
|
function showModal() { |
||||
|
dropdown.hide() |
||||
|
confirmDeleteDialog.show() |
||||
|
} |
||||
|
|
||||
|
async function deleteAutomation() { |
||||
|
await automationStore.actions.delete({ |
||||
|
instanceId, |
||||
|
automation, |
||||
|
}) |
||||
|
notifier.success("Automation deleted.") |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div bind:this={anchor} class="icon" on:click={dropdown.show}> |
||||
|
<i class="ri-more-line" /> |
||||
|
</div> |
||||
|
<DropdownMenu align="left" {anchor} bind:this={dropdown}> |
||||
|
<DropdownContainer> |
||||
|
<DropdownItem |
||||
|
icon="ri-delete-bin-line" |
||||
|
title="Delete" |
||||
|
on:click={showModal} /> |
||||
|
</DropdownContainer> |
||||
|
</DropdownMenu> |
||||
|
<ConfirmDialog |
||||
|
bind:this={confirmDeleteDialog} |
||||
|
okText="Delete Automation" |
||||
|
onOk={deleteAutomation} |
||||
|
title="Confirm Delete"> |
||||
|
Are you sure you wish to delete the automation |
||||
|
<i>{automation.name}?</i> |
||||
|
This action cannot be undone. |
||||
|
</ConfirmDialog> |
||||
|
|
||||
|
<style> |
||||
|
div.icon { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: flex-end; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
div.icon i { |
||||
|
font-size: 16px; |
||||
|
} |
||||
|
|
||||
|
ul { |
||||
|
list-style: none; |
||||
|
margin: 0; |
||||
|
padding: var(--spacing-s) 0; |
||||
|
} |
||||
|
|
||||
|
li { |
||||
|
display: flex; |
||||
|
font-family: var(--font-sans); |
||||
|
font-size: var(--font-size-xs); |
||||
|
color: var(--ink); |
||||
|
padding: var(--spacing-s) var(--spacing-m); |
||||
|
margin: auto 0; |
||||
|
align-items: center; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
li:hover { |
||||
|
background-color: var(--grey-2); |
||||
|
} |
||||
|
|
||||
|
li:active { |
||||
|
color: var(--blue); |
||||
|
} |
||||
|
</style> |
||||
@ -1,3 +0,0 @@ |
|||||
export { default as AutomationPanel } from "./AutomationPanel.svelte" |
|
||||
export { default as BlockList } from "./BlockList/BlockList.svelte" |
|
||||
export { default as AutomationList } from "./AutomationList/AutomationList.svelte" |
|
||||
@ -1 +0,0 @@ |
|||||
export { default as SetupPanel } from "./SetupPanel.svelte" |
|
||||
@ -1,5 +1,5 @@ |
|||||
<script> |
<script> |
||||
import { AutomationBuilder } from "components/automation" |
import AutomationBuilder from "components/automation/AutomationBuilder/AutomationBuilder.svelte" |
||||
</script> |
</script> |
||||
|
|
||||
<AutomationBuilder /> |
<AutomationBuilder /> |
||||
|
|||||
Loading…
Reference in new issue