Rewrite workflow editing state for better UI sync
This commit is contained in:
parent
b886c8f342
commit
494d38029f
|
@ -1,4 +1,3 @@
|
||||||
import mustache from "mustache"
|
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -6,9 +5,8 @@ import { generate } from "shortid"
|
||||||
* Workflow definitions are stored in linked lists.
|
* Workflow definitions are stored in linked lists.
|
||||||
*/
|
*/
|
||||||
export default class Workflow {
|
export default class Workflow {
|
||||||
constructor(workflow, blockDefinitions) {
|
constructor(workflow) {
|
||||||
this.workflow = workflow
|
this.workflow = workflow
|
||||||
this.blockDefinitions = blockDefinitions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasTrigger() {
|
hasTrigger() {
|
||||||
|
@ -22,23 +20,26 @@ export default class Workflow {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.workflow.definition.steps.push({
|
const newBlock = { id: generate(), ...block }
|
||||||
id: generate(),
|
this.workflow.definition.steps = [
|
||||||
...block,
|
...this.workflow.definition.steps,
|
||||||
})
|
newBlock,
|
||||||
|
]
|
||||||
|
return newBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBlock(updatedBlock, id) {
|
updateBlock(updatedBlock, id) {
|
||||||
const { steps, trigger } = this.workflow.definition
|
const { steps, trigger } = this.workflow.definition
|
||||||
|
|
||||||
if (trigger && trigger.id === id) {
|
if (trigger && trigger.id === id) {
|
||||||
this.workflow.definition.trigger = null
|
this.workflow.definition.trigger = updatedBlock
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const stepIdx = steps.findIndex(step => step.id === id)
|
const stepIdx = steps.findIndex(step => step.id === id)
|
||||||
if (stepIdx < 0) throw new Error("Block not found.")
|
if (stepIdx < 0) throw new Error("Block not found.")
|
||||||
steps.splice(stepIdx, 1, updatedBlock)
|
steps.splice(stepIdx, 1, updatedBlock)
|
||||||
|
this.workflow.definition.steps = steps
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteBlock(id) {
|
deleteBlock(id) {
|
||||||
|
@ -52,46 +53,6 @@ export default class Workflow {
|
||||||
const stepIdx = steps.findIndex(step => step.id === id)
|
const stepIdx = steps.findIndex(step => step.id === id)
|
||||||
if (stepIdx < 0) throw new Error("Block not found.")
|
if (stepIdx < 0) throw new Error("Block not found.")
|
||||||
steps.splice(stepIdx, 1)
|
steps.splice(stepIdx, 1)
|
||||||
}
|
this.workflow.definition.steps = steps
|
||||||
|
|
||||||
createUiTree() {
|
|
||||||
if (!this.workflow.definition) return []
|
|
||||||
return Workflow.buildUiTree(this.workflow.definition, this.blockDefinitions)
|
|
||||||
}
|
|
||||||
|
|
||||||
static buildUiTree(definition, blockDefinitions) {
|
|
||||||
const steps = []
|
|
||||||
if (definition.trigger) {
|
|
||||||
steps.push(definition.trigger)
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...steps, ...definition.steps].map(step => {
|
|
||||||
// The client side display definition for the block
|
|
||||||
const definition = blockDefinitions[step.type][step.stepId]
|
|
||||||
if (!definition) {
|
|
||||||
throw new Error(
|
|
||||||
`No block definition exists for the chosen block. Check there's an entry in the block definitions for ${step.stepId}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!definition.params) {
|
|
||||||
throw new Error(
|
|
||||||
`Blocks should always have parameters. Ensure that the block definition is correct for ${step.stepId}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagline = definition.tagline || ""
|
|
||||||
const args = step.args || {}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: step.id,
|
|
||||||
type: step.type,
|
|
||||||
params: step.params,
|
|
||||||
args,
|
|
||||||
heading: step.stepId,
|
|
||||||
body: mustache.render(tagline, args),
|
|
||||||
name: definition.name,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import api from "../../api"
|
import api from "../../api"
|
||||||
import Workflow from "./Workflow"
|
import Workflow from "./Workflow"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
const workflowActions = store => ({
|
const workflowActions = store => ({
|
||||||
fetch: async () => {
|
fetch: async () => {
|
||||||
|
@ -32,11 +33,8 @@ const workflowActions = store => ({
|
||||||
const response = await api.post(CREATE_WORKFLOW_URL, workflow)
|
const response = await api.post(CREATE_WORKFLOW_URL, workflow)
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.workflows = state.workflows.concat(json.workflow)
|
state.workflows = [...state.workflows, json.workflow]
|
||||||
state.currentWorkflow = new Workflow(
|
store.actions.select(json.workflow)
|
||||||
json.workflow,
|
|
||||||
state.blockDefinitions
|
|
||||||
)
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -50,23 +48,7 @@ const workflowActions = store => ({
|
||||||
)
|
)
|
||||||
state.workflows.splice(existingIdx, 1, json.workflow)
|
state.workflows.splice(existingIdx, 1, json.workflow)
|
||||||
state.workflows = state.workflows
|
state.workflows = state.workflows
|
||||||
state.currentWorkflow = new Workflow(
|
store.actions.select(json.workflow)
|
||||||
json.workflow,
|
|
||||||
state.blockDefinitions
|
|
||||||
)
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
update: async ({ workflow }) => {
|
|
||||||
const UPDATE_WORKFLOW_URL = `/api/workflows`
|
|
||||||
const response = await api.put(UPDATE_WORKFLOW_URL, workflow)
|
|
||||||
const json = await response.json()
|
|
||||||
store.update(state => {
|
|
||||||
const existingIdx = state.workflows.findIndex(
|
|
||||||
existing => existing._id === workflow._id
|
|
||||||
)
|
|
||||||
state.workflows.splice(existingIdx, 1, json.workflow)
|
|
||||||
state.workflows = state.workflows
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -81,37 +63,34 @@ const workflowActions = store => ({
|
||||||
)
|
)
|
||||||
state.workflows.splice(existingIdx, 1)
|
state.workflows.splice(existingIdx, 1)
|
||||||
state.workflows = state.workflows
|
state.workflows = state.workflows
|
||||||
state.currentWorkflow = null
|
state.selectedWorkflow = null
|
||||||
|
state.selectedBlock = null
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
select: workflow => {
|
select: workflow => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.currentWorkflow = new Workflow(workflow, state.blockDefinitions)
|
state.selectedWorkflow = new Workflow(cloneDeep(workflow))
|
||||||
state.selectedWorkflowBlock = null
|
state.selectedBlock = null
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
addBlockToWorkflow: block => {
|
addBlockToWorkflow: block => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.currentWorkflow.addBlock(block)
|
const newBlock = state.selectedWorkflow.addBlock(block)
|
||||||
const steps = state.currentWorkflow.workflow.definition.steps
|
state.selectedBlock = newBlock
|
||||||
state.selectedWorkflowBlock = steps.length
|
|
||||||
? steps[steps.length - 1]
|
|
||||||
: state.currentWorkflow.workflow.definition.trigger
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
deleteWorkflowBlock: block => {
|
deleteWorkflowBlock: block => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
console.log(state.currentWorkflow.workflow)
|
const idx = state.selectedWorkflow.workflow.definition.steps.findIndex(
|
||||||
const idx = state.currentWorkflow.workflow.definition.steps.findIndex(
|
|
||||||
x => x.id === block.id
|
x => x.id === block.id
|
||||||
)
|
)
|
||||||
state.currentWorkflow.deleteBlock(block.id)
|
state.selectedWorkflow.deleteBlock(block.id)
|
||||||
|
|
||||||
// Select next closest step
|
// Select next closest step
|
||||||
const steps = state.currentWorkflow.workflow.definition.steps
|
const steps = state.selectedWorkflow.workflow.definition.steps
|
||||||
let nextSelectedBlock
|
let nextSelectedBlock
|
||||||
if (steps[idx] != null) {
|
if (steps[idx] != null) {
|
||||||
nextSelectedBlock = steps[idx]
|
nextSelectedBlock = steps[idx]
|
||||||
|
@ -119,9 +98,9 @@ const workflowActions = store => ({
|
||||||
nextSelectedBlock = steps[idx - 1]
|
nextSelectedBlock = steps[idx - 1]
|
||||||
} else {
|
} else {
|
||||||
nextSelectedBlock =
|
nextSelectedBlock =
|
||||||
state.currentWorkflow.workflow.definition.trigger || null
|
state.selectedWorkflow.workflow.definition.trigger || null
|
||||||
}
|
}
|
||||||
state.selectedWorkflowBlock = nextSelectedBlock
|
state.selectedBlock = nextSelectedBlock
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -135,8 +114,8 @@ export const getWorkflowStore = () => {
|
||||||
ACTION: [],
|
ACTION: [],
|
||||||
LOGIC: [],
|
LOGIC: [],
|
||||||
},
|
},
|
||||||
|
selectedWorkflow: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = writable(INITIAL_WORKFLOW_STATE)
|
const store = writable(INITIAL_WORKFLOW_STATE)
|
||||||
store.actions = workflowActions(store)
|
store.actions = workflowActions(store)
|
||||||
return store
|
return store
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
async function deleteWorkflow() {
|
async function deleteWorkflow() {
|
||||||
await workflowStore.actions.delete({
|
await workflowStore.actions.delete({
|
||||||
instanceId,
|
instanceId,
|
||||||
workflow: $workflowStore.currentWorkflow.workflow,
|
workflow: $workflowStore.selectedWorkflow.workflow,
|
||||||
})
|
})
|
||||||
onClosed()
|
onClosed()
|
||||||
notifier.danger("Workflow deleted.")
|
notifier.danger("Workflow deleted.")
|
||||||
|
|
|
@ -2,13 +2,15 @@
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
let modelId = value ? value._id : ""
|
||||||
|
$: value = $backendUiStore.models.find(x => x._id === modelId)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="block-field">
|
<div class="block-field">
|
||||||
<select class="budibase__input" bind:value>
|
<select class="budibase__input" bind:value={modelId}>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
{#each $backendUiStore.models as model}
|
{#each $backendUiStore.models as model}
|
||||||
<option value={model}>{model.name}</option>
|
<option value={model._id}>{model.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import { onMount, getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { backendUiStore, workflowStore } from "builderStore"
|
import { backendUiStore, workflowStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"
|
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"
|
||||||
|
@ -9,40 +9,22 @@
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
const ACCESS_LEVELS = [
|
|
||||||
{
|
|
||||||
name: "Admin",
|
|
||||||
key: "ADMIN",
|
|
||||||
canExecute: true,
|
|
||||||
editable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Power User",
|
|
||||||
key: "POWER_USER",
|
|
||||||
canExecute: true,
|
|
||||||
editable: false,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
let selectedTab = "SETUP"
|
let selectedTab = "SETUP"
|
||||||
let testResult
|
let testResult
|
||||||
|
|
||||||
$: workflow =
|
$: workflow =
|
||||||
$workflowStore.currentWorkflow && $workflowStore.currentWorkflow.workflow
|
$workflowStore.selectedWorkflow && $workflowStore.selectedWorkflow.workflow
|
||||||
$: workflowBlock = $workflowStore.selectedWorkflowBlock
|
|
||||||
|
|
||||||
function deleteWorkflow() {
|
function deleteWorkflow() {
|
||||||
open(
|
open(
|
||||||
DeleteWorkflowModal,
|
DeleteWorkflowModal,
|
||||||
{
|
{ onClosed: close },
|
||||||
onClosed: close,
|
|
||||||
},
|
|
||||||
{ styleContent: { padding: "0" } }
|
{ styleContent: { padding: "0" } }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteWorkflowBlock() {
|
function deleteWorkflowBlock() {
|
||||||
workflowStore.actions.deleteWorkflowBlock(workflowBlock)
|
workflowStore.actions.deleteWorkflowBlock($workflowStore.selectedBlock)
|
||||||
notifier.info("Workflow block deleted.")
|
notifier.info("Workflow block deleted.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +33,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveWorkflow() {
|
async function saveWorkflow() {
|
||||||
const workflow = $workflowStore.currentWorkflow.workflow
|
|
||||||
await workflowStore.actions.save({
|
await workflowStore.actions.save({
|
||||||
instanceId: $backendUiStore.selectedDatabase._id,
|
instanceId: $backendUiStore.selectedDatabase._id,
|
||||||
workflow,
|
workflow,
|
||||||
|
@ -71,7 +52,7 @@
|
||||||
}}>
|
}}>
|
||||||
Setup
|
Setup
|
||||||
</span>
|
</span>
|
||||||
{#if !workflowBlock}
|
{#if !$workflowStore.selectedBlock}
|
||||||
<span
|
<span
|
||||||
class="test-tab"
|
class="test-tab"
|
||||||
class:selected={selectedTab === 'TEST'}
|
class:selected={selectedTab === 'TEST'}
|
||||||
|
@ -95,8 +76,8 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if selectedTab === 'SETUP'}
|
{#if selectedTab === 'SETUP'}
|
||||||
{#if workflowBlock}
|
{#if $workflowStore.selectedBlock}
|
||||||
<WorkflowBlockSetup {workflowBlock} />
|
<WorkflowBlockSetup bind:block={$workflowStore.selectedBlock} />
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<Button
|
<Button
|
||||||
green
|
green
|
||||||
|
@ -107,25 +88,10 @@
|
||||||
</Button>
|
</Button>
|
||||||
<Button red wide on:click={deleteWorkflowBlock}>Delete Block</Button>
|
<Button red wide on:click={deleteWorkflowBlock}>Delete Block</Button>
|
||||||
</div>
|
</div>
|
||||||
{:else if $workflowStore.currentWorkflow}
|
{:else if $workflowStore.selectedWorkflow}
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="block-label">Workflow: {workflow.name}</div>
|
<div class="block-label">Workflow: {workflow.name}</div>
|
||||||
<div class="config-item">
|
|
||||||
<Label small forAttr={'useraccess'}>User Access</Label>
|
|
||||||
<div class="access-levels">
|
|
||||||
|
|
||||||
{#each ACCESS_LEVELS as level}
|
|
||||||
<span class="access-level">
|
|
||||||
<label>{level.name}</label>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
disabled={!level.editable}
|
|
||||||
bind:checked={level.canExecute} />
|
|
||||||
</span>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<Button
|
<Button
|
||||||
|
@ -181,10 +147,6 @@
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-item {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header > span {
|
header > span {
|
||||||
color: var(--grey-5);
|
color: var(--grey-5);
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
@ -205,13 +167,6 @@
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.access-level {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.access-level label {
|
.access-level label {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
|
|
|
@ -1,51 +1,43 @@
|
||||||
<script>
|
<script>
|
||||||
import { backendUiStore, store } from "builderStore"
|
|
||||||
import ComponentSelector from "./ParamInputs/ComponentSelector.svelte"
|
import ComponentSelector from "./ParamInputs/ComponentSelector.svelte"
|
||||||
import ModelSelector from "./ParamInputs/ModelSelector.svelte"
|
import ModelSelector from "./ParamInputs/ModelSelector.svelte"
|
||||||
import RecordSelector from "./ParamInputs/RecordSelector.svelte"
|
import RecordSelector from "./ParamInputs/RecordSelector.svelte"
|
||||||
import { Input, TextArea, Select } from "@budibase/bbui"
|
import { Input, TextArea, Select } from "@budibase/bbui"
|
||||||
|
|
||||||
export let workflowBlock
|
export let block
|
||||||
|
|
||||||
let params
|
$: params = block.params ? Object.entries(block.params) : []
|
||||||
|
|
||||||
$: workflowParams = workflowBlock.params
|
|
||||||
? Object.entries(workflowBlock.params)
|
|
||||||
: []
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{#each workflowParams as [parameter, type]}
|
{#each params as [parameter, type]}
|
||||||
<div class="block-field">
|
<div class="block-field">
|
||||||
<label class="label">{parameter}</label>
|
<label class="label">{parameter}</label>
|
||||||
{#if Array.isArray(type)}
|
{#if Array.isArray(type)}
|
||||||
<Select bind:value={workflowBlock.args[parameter]} thin secondary>
|
<Select bind:value={block.args[parameter]} thin secondary>
|
||||||
{#each type as option}
|
{#each type as option}
|
||||||
<option value={option}>{option}</option>
|
<option value={option}>{option}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
{:else if type === 'component'}
|
{:else if type === 'component'}
|
||||||
<ComponentSelector bind:value={workflowBlock.args[parameter]} />
|
<ComponentSelector bind:value={block.args[parameter]} />
|
||||||
{:else if type === 'accessLevel'}
|
{:else if type === 'accessLevel'}
|
||||||
<Select bind:value={workflowBlock.args[parameter]} thin secondary>
|
<Select bind:value={block.args[parameter]} thin secondary>
|
||||||
<option value="ADMIN">Admin</option>
|
<option value="ADMIN">Admin</option>
|
||||||
<option value="POWER_USER">Power User</option>
|
<option value="POWER_USER">Power User</option>
|
||||||
</Select>
|
</Select>
|
||||||
{:else if type === 'password'}
|
{:else if type === 'password'}
|
||||||
<Input
|
<Input type="password" thin bind:value={block.args[parameter]} />
|
||||||
type="password"
|
|
||||||
thin
|
|
||||||
bind:value={workflowBlock.args[parameter]} />
|
|
||||||
{:else if type === 'number'}
|
{:else if type === 'number'}
|
||||||
<Input type="number" thin bind:value={workflowBlock.args[parameter]} />
|
<Input type="number" thin bind:value={block.args[parameter]} />
|
||||||
{:else if type === 'longText'}
|
{:else if type === 'longText'}
|
||||||
<TextArea type="text" thin bind:value={workflowBlock.args[parameter]} />
|
<TextArea type="text" thin bind:value={block.args[parameter]} />
|
||||||
{:else if type === 'model'}
|
{:else if type === 'model'}
|
||||||
<ModelSelector bind:value={workflowBlock.args[parameter]} />
|
<ModelSelector bind:value={block.args[parameter]} />
|
||||||
{:else if type === 'record'}
|
{:else if type === 'record'}
|
||||||
<RecordSelector value={workflowBlock.args[parameter]} />
|
<RecordSelector value={block.args[parameter]} />
|
||||||
{:else if type === 'string'}
|
{:else if type === 'string'}
|
||||||
<Input type="text" thin bind:value={workflowBlock.args[parameter]} />
|
<Input type="text" thin bind:value={block.args[parameter]} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -1,30 +1,24 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { afterUpdate } from "svelte"
|
||||||
import { workflowStore, backendUiStore } from "builderStore"
|
import { workflowStore, backendUiStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import Flowchart from "./flowchart/FlowChart.svelte"
|
import Flowchart from "./flowchart/FlowChart.svelte"
|
||||||
|
|
||||||
let selectedWorkflow
|
let section
|
||||||
let uiTree
|
|
||||||
let instanceId = $backendUiStore.selectedDatabase._id
|
|
||||||
|
|
||||||
$: selectedWorkflow = $workflowStore.currentWorkflow
|
|
||||||
|
|
||||||
$: workflowLive = selectedWorkflow && selectedWorkflow.workflow.live
|
|
||||||
|
|
||||||
$: uiTree = selectedWorkflow ? selectedWorkflow.createUiTree() : []
|
|
||||||
|
|
||||||
|
$: workflow =
|
||||||
|
$workflowStore.selectedWorkflow && $workflowStore.selectedWorkflow.workflow
|
||||||
|
$: workflowLive = workflow && workflow.live
|
||||||
$: instanceId = $backendUiStore.selectedDatabase._id
|
$: instanceId = $backendUiStore.selectedDatabase._id
|
||||||
|
|
||||||
function onSelect(block) {
|
function onSelect(block) {
|
||||||
workflowStore.update(state => {
|
workflowStore.update(state => {
|
||||||
state.selectedWorkflowBlock = block
|
state.selectedBlock = block
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWorkflowLive(live) {
|
function setWorkflowLive(live) {
|
||||||
const { workflow } = selectedWorkflow
|
|
||||||
workflow.live = live
|
workflow.live = live
|
||||||
workflowStore.actions.save({ instanceId, workflow })
|
workflowStore.actions.save({ instanceId, workflow })
|
||||||
if (live) {
|
if (live) {
|
||||||
|
@ -33,13 +27,17 @@
|
||||||
notifier.danger(`Workflow ${workflow.name} disabled.`)
|
notifier.danger(`Workflow ${workflow.name} disabled.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
section.scrollTo(0, section.scrollHeight)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section bind:this={section}>
|
||||||
<Flowchart blocks={uiTree} {onSelect} />
|
<Flowchart {workflow} {onSelect} />
|
||||||
</section>
|
</section>
|
||||||
<footer>
|
<footer>
|
||||||
{#if selectedWorkflow}
|
{#if workflow}
|
||||||
<button
|
<button
|
||||||
class:highlighted={workflowLive}
|
class:highlighted={workflowLive}
|
||||||
class:hoverable={workflowLive}
|
class:hoverable={workflowLive}
|
||||||
|
|
|
@ -1,12 +1,25 @@
|
||||||
<script>
|
<script>
|
||||||
import FlowItem from "./FlowItem.svelte"
|
import FlowItem from "./FlowItem.svelte"
|
||||||
import Arrow from "./Arrow.svelte"
|
import Arrow from "./Arrow.svelte"
|
||||||
|
import { flip } from "svelte/animate"
|
||||||
|
import { fade, fly } from "svelte/transition"
|
||||||
|
|
||||||
export let blocks = []
|
export let workflow
|
||||||
export let onSelect
|
export let onSelect
|
||||||
|
let blocks
|
||||||
|
|
||||||
|
$: {
|
||||||
|
blocks = []
|
||||||
|
if (workflow) {
|
||||||
|
if (workflow.definition.trigger) {
|
||||||
|
blocks.push(workflow.definition.trigger)
|
||||||
|
}
|
||||||
|
blocks = blocks.concat(workflow.definition.steps || [])
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="canvas">
|
<section>
|
||||||
{#each blocks as block, idx (block.id)}
|
{#each blocks as block, idx (block.id)}
|
||||||
<FlowItem {onSelect} {block} />
|
<FlowItem {onSelect} {block} />
|
||||||
{#if idx !== blocks.length - 1}
|
{#if idx !== blocks.length - 1}
|
||||||
|
@ -19,9 +32,6 @@
|
||||||
section {
|
section {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 20px 40px;
|
padding: 20px 40px;
|
||||||
}
|
|
||||||
|
|
||||||
.canvas {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
<script>
|
<script>
|
||||||
|
import mustache from "mustache"
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import { workflowStore } from "builderStore"
|
import { workflowStore } from "builderStore"
|
||||||
|
|
||||||
export let onSelect
|
export let onSelect
|
||||||
export let block
|
export let block
|
||||||
let selected = false
|
let selected
|
||||||
|
|
||||||
|
$: selected =
|
||||||
|
$workflowStore.selectedBlock != null &&
|
||||||
|
$workflowStore.selectedBlock.id === block.id
|
||||||
|
|
||||||
function selectBlock() {
|
function selectBlock() {
|
||||||
onSelect(block)
|
onSelect(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: selected =
|
|
||||||
$workflowStore.selectedWorkflowBlock != null &&
|
|
||||||
$workflowStore.selectedWorkflowBlock.id === block.id
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -34,7 +35,7 @@
|
||||||
</header>
|
</header>
|
||||||
<hr />
|
<hr />
|
||||||
<p>
|
<p>
|
||||||
{@html block.body}
|
{@html mustache.render(block.tagline, block.args)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
$: blocks = Object.entries($workflowStore.blockDefinitions[selectedTab])
|
$: blocks = Object.entries($workflowStore.blockDefinitions[selectedTab])
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($workflowStore.currentWorkflow.hasTrigger()) {
|
if ($workflowStore.selectedWorkflow.hasTrigger()) {
|
||||||
buttonProps = [
|
buttonProps = [
|
||||||
{ value: "ACTION", text: "Action" },
|
{ value: "ACTION", text: "Action" },
|
||||||
{ value: "LOGIC", text: "Logic" },
|
{ value: "LOGIC", text: "Logic" },
|
||||||
|
|
|
@ -8,9 +8,9 @@
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
$: currentWorkflowId =
|
$: selectedWorkflowId =
|
||||||
$workflowStore.currentWorkflow &&
|
$workflowStore.selectedWorkflow &&
|
||||||
$workflowStore.currentWorkflow.workflow._id
|
$workflowStore.selectedWorkflow.workflow._id
|
||||||
|
|
||||||
function newWorkflow() {
|
function newWorkflow() {
|
||||||
open(
|
open(
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
{#each $workflowStore.workflows as workflow}
|
{#each $workflowStore.workflows as workflow}
|
||||||
<li
|
<li
|
||||||
class="workflow-item"
|
class="workflow-item"
|
||||||
class:selected={workflow._id === currentWorkflowId}
|
class:selected={workflow._id === selectedWorkflowId}
|
||||||
on:click={() => workflowStore.actions.select(workflow)}>
|
on:click={() => workflowStore.actions.select(workflow)}>
|
||||||
<i class="ri-stackshare-line" class:live={workflow.live} />
|
<i class="ri-stackshare-line" class:live={workflow.live} />
|
||||||
{workflow.name}
|
{workflow.name}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
on:click={() => (selectedTab = 'WORKFLOWS')}>
|
on:click={() => (selectedTab = 'WORKFLOWS')}>
|
||||||
Workflows
|
Workflows
|
||||||
</span>
|
</span>
|
||||||
{#if $workflowStore.currentWorkflow}
|
{#if $workflowStore.selectedWorkflow}
|
||||||
<span
|
<span
|
||||||
data-cy="add-workflow-component"
|
data-cy="add-workflow-component"
|
||||||
class="hoverable"
|
class="hoverable"
|
||||||
|
|
Loading…
Reference in New Issue