adding workflow blocks, parameter parsing, templating

This commit is contained in:
Martin McKeaveney 2020-05-28 20:20:03 +01:00
parent 2abda35443
commit 6f0a84dd38
41 changed files with 630 additions and 445 deletions

View File

@ -47,9 +47,11 @@
"lodash": "^4.17.13", "lodash": "^4.17.13",
"logrocket": "^1.0.6", "logrocket": "^1.0.6",
"lunr": "^2.3.5", "lunr": "^2.3.5",
"mustache": "^4.0.1",
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"shortid": "^2.2.8", "shortid": "^2.2.8",
"string_decoder": "^1.2.0", "string_decoder": "^1.2.0",
"svelte-grid": "^1.10.8",
"svelte-simple-modal": "^0.3.0", "svelte-simple-modal": "^0.3.0",
"uikit": "^3.1.7" "uikit": "^3.1.7"
}, },

View File

@ -25,5 +25,5 @@ export default {
get, get,
patch, patch,
delete: del, delete: del,
put put,
} }

View File

@ -1,6 +1,6 @@
import { getStore } from "./store" import { getStore } from "./store"
import { getBackendUiStore } from "./store/backend" import { getBackendUiStore } from "./store/backend"
import { getWorkflowStore } from "./store/workflow" import { getWorkflowStore } from "./store/workflow/"
import LogRocket from "logrocket" import LogRocket from "logrocket"
export const store = getStore() export const store = getStore()

View File

@ -1,55 +0,0 @@
import { writable } from "svelte/store"
import api from "../api"
const workflowActions = store => ({
fetch: async instanceId => {
const WORKFLOWS_URL = `/api/${instanceId}/workflows`;
const workflowResponse = await api.get(WORKFLOWS_URL);
const json = await workflowResponse.json();
store.update(state => {
state.workflows = json
return state
})
},
create: async ({ instanceId, name }) => {
const workflow = { name }
const CREATE_WORKFLOW_URL = `/api/${instanceId}/workflows`;
const response = await api.post(CREATE_WORKFLOW_URL, workflow)
const json = await response.json();
store.update(state => {
state.workflows = state.workflows.concat(json.workflow)
state.selectedWorkflowId = json.workflow._id
return state
})
},
update: async ({ instanceId, workflow }) => {
const UPDATE_WORKFLOW_URL = `/api/${instanceId}/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
})
},
select: workflow => {
store.update(state => {
state.selectedWorkflowId = workflow._id
state.selectedWorkflowBlock = null
return state;
})
}
});
export const getWorkflowStore = () => {
const INITIAL_WORKFLOW_STATE = {
workflows: []
}
const store = writable(INITIAL_WORKFLOW_STATE)
store.actions = workflowActions(store);
return store
}

View File

@ -0,0 +1,79 @@
import mustache from "mustache"
// TODO: tidy up import
import blockDefinitions from "../../../pages/[application]/workflow/WorkflowPanel/blockDefinitions"
/**
* Class responsible for the traversing of the workflow definition.
* Workflow definitions are stored in linked lists.
*/
export default class Workflow {
constructor(workflow) {
this.workflow = workflow
}
addBlock(block) {
let node = this.workflow.definition
while (node.next) node = node.next
node.next = block
}
updateBlock(updatedBlock, id) {
let block = this.workflow.definition
while (block.id !== id) block = block.next
if (!block) throw new Error("Block not found.")
block = updatedBlock
}
deleteBlock(id) {
let previous = null
let block = this.workflow.definition
// iterate through the blocks
while (block.id !== id) {
previous = block
block = block.next
}
// delete the block found
previous.next = block.next || {}
}
createUiTree() {
if (!this.workflow.definition.next) return []
return Workflow.buildUiTree(this.workflow.definition.next)
}
static buildUiTree(block, tree = []) {
if (!block) return tree
// The client side display definition for the block
const definition = blockDefinitions[block.type][block.actionId]
if (!definition) {
throw new Error(
`No block definition exists for the chosen block. Check there's an entry in the block definitions for ${block.actionId}`
)
}
if (!definition.params) {
throw new Error(
`Blocks should always have parameters. Ensure that the block definition is correct for ${block.actionId}`
)
}
const tagline = definition.tagline || ""
const args = block.args || {}
tree.push({
id: block.id,
type: block.type,
params: block.params,
args,
heading: block.actionId,
body: mustache.render(tagline, args),
})
return this.buildUiTree(block.next, tree)
}
}

View File

@ -0,0 +1,102 @@
import { writable } from "svelte/store"
import api from "../../api"
import Workflow from "./Workflow"
const workflowActions = store => ({
fetch: async instanceId => {
const WORKFLOWS_URL = `/api/${instanceId}/workflows`
const workflowResponse = await api.get(WORKFLOWS_URL)
const json = await workflowResponse.json()
store.update(state => {
state.workflows = json
return state
})
},
create: async ({ instanceId, name }) => {
const workflow = { name, definition: {} }
const CREATE_WORKFLOW_URL = `/api/${instanceId}/workflows`
const response = await api.post(CREATE_WORKFLOW_URL, workflow)
const json = await response.json()
store.update(state => {
state.workflows = state.workflows.concat(json.workflow)
state.currentWorkflow = new Workflow(json.workflow)
return state
})
},
save: async ({ instanceId, workflow }) => {
const UPDATE_WORKFLOW_URL = `/api/${instanceId}/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
state.currentWorkflow = new Workflow(json.workflow)
return state
})
},
update: async ({ instanceId, workflow }) => {
const UPDATE_WORKFLOW_URL = `/api/${instanceId}/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
})
},
delete: async ({ instanceId, workflow }) => {
const { _id, _rev } = workflow
const DELETE_WORKFLOW_URL = `/api/${instanceId}/workflows/${_id}/${_rev}`
await api.delete(DELETE_WORKFLOW_URL)
store.update(state => {
const existingIdx = state.workflows.findIndex(
existing => existing._id === _id
)
state.workflows.splice(existingIdx, 1)
state.workflows = state.workflows
state.currentWorkflow = null
return state
})
},
select: workflow => {
store.update(state => {
// TODO: better naming
state.currentWorkflow = new Workflow(workflow)
state.selectedWorkflowBlock = null
return state
})
},
addBlockToWorkflow: block => {
store.update(state => {
state.currentWorkflow.addBlock(block)
state.selectedWorkflowBlock = block
return state
})
},
deleteWorkflowBlock: block => {
store.update(state => {
state.currentWorkflow.deleteBlock(block._id)
state.selectedWorkflowBlock = null
return state
})
},
})
export const getWorkflowStore = () => {
const INITIAL_WORKFLOW_STATE = {
workflows: [],
}
const store = writable(INITIAL_WORKFLOW_STATE)
store.actions = workflowActions(store)
return store
}

View File

@ -0,0 +1 @@
describe("Workflow Data Object", () => {})

View File

@ -34,7 +34,7 @@
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label" for="form-stacked-text">Access Level</label> <label class="uk-form-label" for="form-stacked-text">Access Level</label>
<select class="uk-select" bind:value={accessLevelId}> <select class="uk-select" bind:value={accessLevelId}>
<option value=""></option> <option value="" />
<option value="POWER_USER">Power User</option> <option value="POWER_USER">Power User</option>
<option value="ADMIN">Admin</option> <option value="ADMIN">Admin</option>
</select> </select>

View File

@ -25,7 +25,7 @@
let categories = [ let categories = [
{ value: "design", name: "Design" }, { value: "design", name: "Design" },
{ value: "settings", name: "Settings" }, { value: "settings", name: "Settings" },
{ value: "events", name: "Events" } { value: "events", name: "Events" },
] ]
let selectedCategory = categories[0] let selectedCategory = categories[0]

View File

@ -78,6 +78,7 @@
--primary: #0055ff; --primary: #0055ff;
--secondary: #f1f4fc; --secondary: #f1f4fc;
--color: #393c44; --color: #393c44;
--light-grey: #fbfbfb;
--dark-grey: #808192; --dark-grey: #808192;
--medium-grey: #e8e8ef; --medium-grey: #e8e8ef;
--background: rgb(251, 251, 251); --background: rgb(251, 251, 251);

View File

@ -0,0 +1,87 @@
<script>
import { store, backendUiStore, workflowStore } from "builderStore"
import api from "builderStore/api"
import ActionButton from "components/common/ActionButton.svelte"
export let onClosed
let name
$: valid = !!name
$: instanceId = $backendUiStore.selectedDatabase._id
async function deleteWorkflow() {
await workflowStore.actions.delete({
instanceId,
workflow: $workflowStore.currentWorkflow.workflow,
})
onClosed()
}
</script>
<header>
<i class="ri-stackshare-line" />
Delete Workflow
</header>
<div>
<p>
Are you sure you want to delete this workflow? This action can't be undone.
</p>
</div>
<footer>
<a href="https://docs.budibase.com">
<i class="ri-information-line" />
Learn about workflows
</a>
<ActionButton on:click={onClosed}>Cancel</ActionButton>
<ActionButton alert on:click={deleteWorkflow}>Delete</ActionButton>
</footer>
<style>
header {
font-size: 24px;
color: var(--font);
font-weight: bold;
padding: 30px;
}
header i {
margin-right: 10px;
font-size: 20px;
background: var(--secondary);
color: var(--dark-grey);
padding: 8px;
}
div {
padding: 0 30px 30px 30px;
}
label {
font-size: 18px;
font-weight: 500;
}
footer {
display: grid;
grid-auto-flow: column;
grid-gap: 5px;
grid-auto-columns: 3fr 1fr 1fr;
padding: 20px;
background: #fafafa;
border-radius: 0.5rem;
}
footer a {
color: var(--primary);
font-size: 14px;
vertical-align: middle;
display: flex;
align-items: center;
}
footer i {
font-size: 20px;
margin-right: 10px;
}
</style>

View File

@ -1,20 +1,27 @@
<script> <script>
import { onMount } from "svelte" import { onMount, getContext } from "svelte"
import { backendUiStore, workflowStore } from "builderStore" import { backendUiStore, workflowStore } from "builderStore"
import api from "builderStore/api" import api from "builderStore/api"
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"; import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"
import DeleteWorkflowModal from "./DeleteWorkflowModal.svelte"
$: workflow = $workflowStore.workflows.find( const { open, close } = getContext("simple-modal")
wf => wf._id === $workflowStore.selectedWorkflowId
) $: workflow = $workflowStore.currentWorkflow
$: workflowBlock = $workflowStore.selectedWorkflowBlock $: workflowBlock = $workflowStore.selectedWorkflowBlock
function deleteWorkflow() { function deleteWorkflow() {
workflowStore.actions.deleteWorkflow(workflow) open(
DeleteWorkflowModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
} }
function deleteWorkflowBlock() { function deleteWorkflowBlock() {
// TODO: implement // TODO: implement, need to put IDs against workflow blocks
workflowStore.actions.deleteWorkflowBlock(workflowBlock) workflowStore.actions.deleteWorkflowBlock(workflowBlock)
} }
</script> </script>
@ -26,10 +33,12 @@
<div class="panel-body"> <div class="panel-body">
{#if workflowBlock} {#if workflowBlock}
<WorkflowBlockSetup {workflowBlock} /> <WorkflowBlockSetup {workflowBlock} />
<button class="delete-workflow-button hoverable" on:click={deleteWorkflowBlock}> <button
Delete {workflowBlock.type} class="delete-workflow-button hoverable"
on:click={deleteWorkflowBlock}>
Delete Block
</button> </button>
{:else if $workflowStore.selectedWorkflowId} {:else if $workflowStore.currentWorkflow}
<label class="uk-form-label">Workflow: {workflow.name}</label> <label class="uk-form-label">Workflow: {workflow.name}</label>
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label">Name</label> <label class="uk-form-label">Name</label>
@ -44,7 +53,9 @@
<label class="uk-form-label">User Access</label> <label class="uk-form-label">User Access</label>
Some User Access Stuff Here Some User Access Stuff Here
</div> </div>
<button class="delete-workflow-button hoverable" on:click={deleteWorkflow}> <button
class="delete-workflow-button hoverable"
on:click={deleteWorkflow}>
Delete Workflow Delete Workflow
</button> </button>
{/if} {/if}

View File

@ -1,17 +1,62 @@
<script> <script>
import { backendUiStore } from "builderStore"
export let workflowBlock export let workflowBlock
$: workflowArgs = Object.keys(workflowBlock.args)
let params
$: workflowParams = workflowBlock.params
? Object.entries(workflowBlock.params)
: []
// $: workflowArgs = workflowBlock.args ? Object.keys(workflowBlock.args) : []
</script> </script>
<label class="uk-form-label">{workflowBlock.heading}: {workflowBlock.heading}</label> <label class="uk-form-label">
{#each workflowArgs as workflowArg} {workflowBlock.type}: {workflowBlock.heading}
<div class="uk-margin"> </label>
<label class="uk-form-label">Name</label> {#each workflowParams as [parameter, type]}
<div class="uk-margin block-field">
<label class="uk-form-label">{parameter}</label>
<div class="uk-form-controls"> <div class="uk-form-controls">
{#if type === 'number'}
<input
type="number"
class="budibase__input"
bind:value={workflowBlock.args[parameter]} />
{:else if type === 'model'}
<select
class="budibase__input"
bind:value={workflowBlock.args[parameter]}>
{#each $backendUiStore.models as model}
<option value={model._id}>{model.name}</option>
{/each}
</select>
{:else if type === 'component'}
<!-- <select>
{#each $store.components as question}
<option value={question}>{question.text}</option>
{/each}
</select> -->
{:else if type === 'string'}
<input <input
type="text" type="text"
class="budibase__input" class="budibase__input"
bind:value={workflowBlock.args[workflowArg]} /> bind:value={workflowBlock.args[parameter]} />
{/if}
</div> </div>
</div> </div>
{/each} {/each}
<style>
.block-field {
border-radius: 3px;
background: var(--light-grey);
padding: 20px;
}
label {
text-transform: capitalize;
font-size: 14px;
font-weight: 500;
}
</style>

View File

@ -1 +1 @@
export { default as SetupPanel } from "./SetupPanel.svelte"; export { default as SetupPanel } from "./SetupPanel.svelte"

View File

@ -9,29 +9,13 @@
let uiTree let uiTree
let instanceId = $backendUiStore.selectedDatabase._id let instanceId = $backendUiStore.selectedDatabase._id
$: workflow = $workflowStore.workflows.find( $: workflow = $workflowStore.currentWorkflow
wf => wf._id === $workflowStore.selectedWorkflowId
)
// Build a renderable UI Tree for the flowchart generator $: if (workflow) uiTree = workflow ? workflow.createUiTree() : []
function buildUiTree(block, tree = []) {
if (!block) return tree
tree.push({
type: block.type,
heading: block.actionId,
args: block.args,
body: JSON.stringify(block.args),
})
return buildUiTree(block.next, tree)
}
$: if (workflow) uiTree = workflow.definition ? buildUiTree(workflow.definition.next) : []
function onDelete(block) { function onDelete(block) {
// TODO finish // TODO finish
workflowStore.actions.deleteWorkflowBlock(block); workflowStore.actions.deleteWorkflowBlock(block)
} }
function onSelect(block) { function onSelect(block) {
@ -43,9 +27,45 @@
</script> </script>
<section> <section>
<Flowchart <Flowchart blocks={uiTree} {onSelect} on:delete={onDelete} />
blocks={uiTree} <footer>
onSelect={onSelect} <button class="stop-button hoverable">
on:delete={onDelete} <i class="ri-stop-fill" />
/> </button>
<button class="play-button hoverable">
<i class="ri-play-fill" />
</button>
</footer>
</section> </section>
<style>
footer {
position: absolute;
bottom: 0;
right: 0;
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;
margin-right: 24px;
}
.play-button:hover {
background: var(--primary);
}
.stop-button:hover {
background: var(--coral);
}
</style>

View File

@ -7,9 +7,9 @@
<section class="canvas"> <section class="canvas">
{#each blocks as block, idx} {#each blocks as block, idx}
<FlowItem onSelect={onSelect} {block} /> <FlowItem {onSelect} {block} />
{#if idx !== blocks.length - 1} {#if idx !== blocks.length - 1}
<i class="ri-arrow-down-line"></i> <i class="ri-arrow-down-line" />
{/if} {/if}
{/each} {/each}
</section> </section>

View File

@ -3,19 +3,22 @@
export let block export let block
function selectBlock() { function selectBlock() {
onSelect(block); onSelect(block)
} }
console.log(block)
</script> </script>
<div class="hoverable" on:click={selectBlock}> <div class={`${block.type} hoverable`} on:click={selectBlock}>
<header>{block.heading}</header> <header>{block.heading}</header>
<hr /> <hr />
<p>{block.body}</p> <p>
{@html block.body}
</p>
</div> </div>
<style> <style>
div { div {
border: 1px solid black;
width: 320px; width: 320px;
padding: 20px; padding: 20px;
margin-bottom: 60px; margin-bottom: 60px;
@ -27,6 +30,21 @@
color: var(--white); color: var(--white);
} }
.ACTION {
background-color: var(--white);
color: var(--font);
}
.TRIGGER {
background-color: var(--font);
color: var(--white);
}
.LOGIC {
background-color: var(--secondary);
color: var(--font);
}
div:hover { div:hover {
transform: scale(1.05); transform: scale(1.05);
} }

View File

@ -1,19 +1,19 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import { WorkflowList } from "../"; import { WorkflowList } from "../"
import WorkflowBlock from "./WorkflowBlock.svelte"; import WorkflowBlock from "./WorkflowBlock.svelte"
import api from "builderStore/api" import api from "builderStore/api"
import blockDefinitions from "../blockDefinitions" import blockDefinitions from "../blockDefinitions"
const SUB_TABS = [ const SUB_TABS = [
{ {
name: "Triggers", name: "Triggers",
key: "TRIGGERS", key: "TRIGGER",
}, },
{ {
name: "Actions", name: "Actions",
key: "ACTIONS", key: "ACTION",
}, },
{ {
name: "Logic", name: "Logic",
@ -21,14 +21,10 @@
}, },
] ]
let selectedTab = "TRIGGERS" let selectedTab = "TRIGGER"
let definitions = [] let definitions = []
$: definitions = Object.values(blockDefinitions[selectedTab]) $: definitions = Object.entries(blockDefinitions[selectedTab])
function myAction(node) {
console.log("ACTION FIRED", node);
}
</script> </script>
<section> <section>
@ -43,8 +39,8 @@
{/each} {/each}
</div> </div>
<div id="blocklist"> <div id="blocklist">
{#each definitions as blockDefinition} {#each definitions as [actionId, blockDefinition]}
<WorkflowBlock {blockDefinition} blockType={selectedTab} /> <WorkflowBlock {blockDefinition} {actionId} blockType={selectedTab} />
{/each} {/each}
</div> </div>
</section> </section>

View File

@ -1,12 +1,18 @@
<script> <script>
import { workflowStore } from "builderStore"; import { workflowStore } from "builderStore"
export let blockType export let blockType
export let blockDefinition export let blockDefinition
export let actionId
function addBlockToWorkflow() { function addBlockToWorkflow() {
// TODO: store the block type in the DB as well // TODO: store the block type in the DB as well
workflowStore.actions.addBlockToWorkflow({ blockDefinition, blockType }); workflowStore.actions.addBlockToWorkflow({
...blockDefinition,
args: {},
actionId,
type: blockType,
})
} }
</script> </script>

View File

@ -7,6 +7,9 @@
const { open, close } = getContext("simple-modal") const { open, close } = getContext("simple-modal")
$: currentWorkflowId =
$workflowStore.currentWorkflow && $workflowStore.currentWorkflow._id
function newWorkflow() { function newWorkflow() {
open( open(
CreateWorkflowModal, CreateWorkflowModal,
@ -20,6 +23,14 @@
onMount(() => { onMount(() => {
workflowStore.actions.fetch($backendUiStore.selectedDatabase._id) workflowStore.actions.fetch($backendUiStore.selectedDatabase._id)
}) })
function saveWorkflow() {
// TODO: Clean up args
workflowStore.actions.save({
instanceId: $backendUiStore.selectedDatabase._id,
workflow: $workflowStore.currentWorkflow.workflow,
})
}
</script> </script>
<section> <section>
@ -30,13 +41,18 @@
{#each $workflowStore.workflows as workflow} {#each $workflowStore.workflows as workflow}
<li <li
class="workflow-item" class="workflow-item"
class:selected={workflow._id === $workflowStore.selectedWorkflowId} class:selected={workflow._id === currentWorkflowId}
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}
</li> </li>
{/each} {/each}
</ul> </ul>
{#if $workflowStore.currentWorkflow}
<button class="new-workflow-button hoverable" on:click={saveWorkflow}>
Save Workflow
</button>
{/if}
</section> </section>
<style> <style>

View File

@ -28,7 +28,7 @@
on:click={() => (selectedTab = 'WORKFLOWS')}> on:click={() => (selectedTab = 'WORKFLOWS')}>
Workflows Workflows
</span> </span>
{#if $workflowStore.selectedWorkflowId} {#if $workflowStore.currentWorkflow}
<span <span
class="hoverable" class="hoverable"
class:selected={selectedTab === 'ADD'} class:selected={selectedTab === 'ADD'}
@ -58,4 +58,14 @@
color: var(--dark-grey); color: var(--dark-grey);
} }
.delete-workflow-button {
font-family: Roboto;
width: 100%;
border: solid 1px #f2f2f2;
border-radius: 2px;
background: var(--white);
height: 32px;
font-size: 12px;
font-weight: 500;
}
</style> </style>

View File

@ -1,89 +1,126 @@
const ACTIONS = { const ACTION = {
SET_STATE: { SET_STATE: {
name: "Update UI State", name: "Update UI State",
tagline: "Update <b>{{path}}</b> to <b>{{value}}</b>",
icon: "", icon: "",
description: "Update your User Interface with some data.", description: "Update your User Interface with some data.",
type: "CLIENT", environment: "CLIENT",
params: {
path: "string",
value: "string",
},
}, },
NAVIGATE: { NAVIGATE: {
name: "Navigate", name: "Navigate",
icon: "ri-navigation-line", icon: "ri-navigation-line",
description: "Navigate to another page.", description: "Navigate to another page.",
type: "CLIENT" environment: "CLIENT",
params: {
url: "string",
},
}, },
SAVE_RECORD: { SAVE_RECORD: {
name: "Save Record", name: "Save Record",
icon: "ri-save-3-fill", icon: "ri-save-3-fill",
description: "Save a record to your database.", description: "Save a record to your database.",
type: "SERVER", environment: "SERVER",
params: {
model: "string",
},
}, },
DELETE_RECORD: { DELETE_RECORD: {
description: "Delete a record from your database.", description: "Delete a record from your database.",
icon: "ri-delete-bin-line", icon: "ri-delete-bin-line",
name: "Delete Record", name: "Delete Record",
type: "SERVER", environment: "SERVER",
params: {
record: "string",
},
}, },
FIND_RECORD: { FIND_RECORD: {
description: "Delete a record from your database.", description: "Delete a record from your database.",
icon: "ri-delete-bin-line", icon: "ri-delete-bin-line",
name: "Find Record", name: "Find Record",
type: "SERVER", environment: "SERVER",
params: {
record: "string",
},
}, },
CREATE_USER: { CREATE_USER: {
description: "Create a new user.", description: "Create a new user.",
icon: "ri-user-add-fill", icon: "ri-user-add-fill",
name: "Create User", name: "Create User",
type: "SERVER", environment: "SERVER",
params: {
name: "string",
password: "password",
accessLevel: "accessLevel",
},
}, },
SEND_EMAIL: { SEND_EMAIL: {
description: "Send an email.", description: "Send an email.",
tagline: "Send email to {{to}}",
icon: "ri-mail-open-fill", icon: "ri-mail-open-fill",
name: "Send Email", name: "Send Email",
type: "SERVER", environment: "SERVER",
} params: {
}; to: "string",
from: "string",
subject: "string",
text: "string",
},
},
}
const TRIGGERS = { const TRIGGER = {
SAVE_RECORD: { SAVE_RECORD: {
name: "Record Saved", name: "Record Saved",
icon: "ri-delete-bin-line", icon: "ri-delete-bin-line",
tagline: "Record is added to {{model}}",
description: "Save a record to your database.", description: "Save a record to your database.",
type: "SERVER", environment: "SERVER",
params: {
model: "model",
},
}, },
CLICK: { CLICK: {
name: "Click", name: "Click",
icon: "ri-cursor-line", icon: "ri-cursor-line",
description: "Trigger when you click on an element in the UI." description: "Trigger when you click on an element in the UI.",
}, },
LOAD: { LOAD: {
name: "Load", name: "Load",
icon: "ri-loader-line", icon: "ri-loader-line",
description: "Trigger an element has finished loading." description: "Trigger an element has finished loading.",
}, },
INPUT: { INPUT: {
name: "Input", name: "Input",
icon: "ri-text", icon: "ri-text",
description: "Trigger when you type into an input box." description: "Trigger when you environment into an input box.",
}, },
}; }
const LOGIC = { const LOGIC = {
FILTER: { FILTER: {
name: "Filter", name: "Filter",
tagline: "{{key}} {{condition}} {{value}}",
icon: "ri-git-branch-line", icon: "ri-git-branch-line",
description: "Filter any workflows which do not meet certain conditions.", description: "Filter any workflows which do not meet certain conditions.",
type: "CLIENT" environment: "CLIENT",
params: {
if: "string",
},
}, },
DELAY: { DELAY: {
name: "Delay", name: "Delay",
icon: "ri-git-branch-line", icon: "ri-git-branch-line",
description: "Delay the workflow until an amount of time has passed.", description: "Delay the workflow until an amount of time has passed.",
type: "CLIENT" environment: "CLIENT",
}, },
} }
export default { export default {
ACTIONS, ACTION,
TRIGGERS, TRIGGER,
LOGIC LOGIC,
} }

View File

@ -1,3 +1,3 @@
export { default as WorkflowPanel } from "./WorkflowPanel.svelte"; export { default as WorkflowPanel } from "./WorkflowPanel.svelte"
export { default as BlockList } from "./BlockList/BlockList.svelte"; export { default as BlockList } from "./BlockList/BlockList.svelte"
export { default as WorkflowList } from "./WorkflowList/WorkflowList.svelte"; export { default as WorkflowList } from "./WorkflowList/WorkflowList.svelte"

View File

@ -24,6 +24,7 @@
.nav { .nav {
padding: 20px; padding: 20px;
height: 100%;
} }
.root { .root {

View File

@ -1,5 +1,5 @@
<script> <script>
import WorkflowBuilder from "./WorkflowBuilder/WorkflowBuilder.svelte"; import WorkflowBuilder from "./WorkflowBuilder/WorkflowBuilder.svelte"
</script> </script>
<WorkflowBuilder /> <WorkflowBuilder />

View File

@ -2,8 +2,7 @@ import { loadRecord } from "./loadRecord"
import { listRecords } from "./listRecords" import { listRecords } from "./listRecords"
import { authenticate } from "./authenticate" import { authenticate } from "./authenticate"
import { saveRecord } from "./saveRecord" import { saveRecord } from "./saveRecord"
import { triggerWorkflow } from "./workflow"; import { triggerWorkflow } from "./workflow"
export const createApi = ({ rootPath = "", setState, getState }) => { export const createApi = ({ rootPath = "", setState, getState }) => {
const apiCall = method => async ({ url, body }) => { const apiCall = method => async ({ url, body }) => {
@ -16,7 +15,6 @@ export const createApi = ({ rootPath = "", setState, getState }) => {
credentials: "same-origin", credentials: "same-origin",
}) })
switch (response.status) { switch (response.status) {
case 200: case 200:
return response.json() return response.json()
@ -66,6 +64,6 @@ export const createApi = ({ rootPath = "", setState, getState }) => {
listRecords: listRecords(apiOpts), listRecords: listRecords(apiOpts),
authenticate: authenticate(apiOpts), authenticate: authenticate(apiOpts),
saveRecord: saveRecord(apiOpts), saveRecord: saveRecord(apiOpts),
triggerWorkflow: triggerWorkflow(apiOpts) triggerWorkflow: triggerWorkflow(apiOpts),
} }
} }

View File

@ -1,15 +1,13 @@
import Orchestrator, { clientStrategy } from "./orchestrator"; import Orchestrator, { clientStrategy } from "./orchestrator"
export const triggerWorkflow = api => ({ workflow }) => { export const triggerWorkflow = api => ({ workflow }) => {
const workflowOrchestrator = new Orchestrator( const workflowOrchestrator = new Orchestrator(
api, api,
"inst_60dd510_700f7dc06735403e81d5af91072d7241" "inst_60dd510_700f7dc06735403e81d5af91072d7241"
); )
workflowOrchestrator.strategy = clientStrategy workflowOrchestrator.strategy = clientStrategy
workflowOrchestrator.execute(workflow); workflowOrchestrator.execute(workflow)
// hit the API and get the workflow data back // hit the API and get the workflow data back
} }

View File

@ -1,4 +1,4 @@
import get from "lodash/fp/get"; import get from "lodash/fp/get"
/** /**
* The workflow orhestrator is a class responsible for executing workflows. * The workflow orhestrator is a class responsible for executing workflows.
@ -18,13 +18,13 @@ export default class Orchestrator {
} }
async execute(workflowId) { async execute(workflowId) {
const EXECUTE_WORKFLOW_URL = `/api/${this.instanceId}/workflows/${workflowId}`; const EXECUTE_WORKFLOW_URL = `/api/${this.instanceId}/workflows/${workflowId}`
const workflow = await this.api.get({ url: EXECUTE_WORKFLOW_URL }) const workflow = await this.api.get({ url: EXECUTE_WORKFLOW_URL })
this._strategy.run({ this._strategy.run({
workflow: workflow.definition, workflow: workflow.definition,
api: this.api, api: this.api,
instanceId: this.instanceId instanceId: this.instanceId,
}); })
} }
} }
@ -32,76 +32,76 @@ export default class Orchestrator {
export const clientStrategy = { export const clientStrategy = {
context: {}, context: {},
bindContextArgs: function(args, api) { bindContextArgs: function(args, api) {
const mappedArgs = { ...args }; const mappedArgs = { ...args }
console.log("original args", args) console.log("original args", args)
// bind the workflow action args to the workflow context, if required // bind the workflow action args to the workflow context, if required
for (let arg in args) { for (let arg in args) {
const argValue = args[arg]; const argValue = args[arg]
// Means that it's bound to state or workflow context // Means that it's bound to state or workflow context
if (argValue.startsWith("$")) { if (argValue.startsWith("$")) {
// if value is bound to workflow context. // if value is bound to workflow context.
if (argValue.startsWith("$context")) { if (argValue.startsWith("$context")) {
const path = argValue.replace("$context.", ""); const path = argValue.replace("$context.", "")
// pass in the value from context // pass in the value from context
mappedArgs[arg] = get(path, this.context); mappedArgs[arg] = get(path, this.context)
} }
// if the value is bound to state // if the value is bound to state
if (argValue.startsWith("$state")) { if (argValue.startsWith("$state")) {
const path = argValue.replace("$state.", ""); const path = argValue.replace("$state.", "")
// pass in the value from state // pass in the value from state
// TODO: not working // TODO: not working
mappedArgs[arg] = api.getState(path); mappedArgs[arg] = api.getState(path)
} }
} }
} }
console.log(mappedArgs); console.log(mappedArgs)
return Object.values(mappedArgs); return Object.values(mappedArgs)
}, },
run: async function({ workflow, api, instanceId }) { run: async function({ workflow, api, instanceId }) {
const block = workflow.next; const block = workflow.next
console.log("Executing workflow block", block); console.log("Executing workflow block", block)
if (!block) return; if (!block) return
// This code gets run in the browser // This code gets run in the browser
if (block.type === "CLIENT") { if (block.environment === "CLIENT") {
if (block.actionId === "SET_STATE") { if (block.actionId === "SET_STATE") {
// get props from the workflow context if required // get props from the workflow context if required
api.setState(...this.bindContextArgs(block.args)) api.setState(...this.bindContextArgs(block.args))
// update the context with the data // update the context with the data
this.context = { this.context = {
...this.context, ...this.context,
SET_STATE: block.args SET_STATE: block.args,
}
} }
} }
};
// this workflow block gets executed on the server // this workflow block gets executed on the server
if (block.type === "SERVER") { if (block.environment === "SERVER") {
const EXECUTE_WORKFLOW_URL = `/api/${instanceId}/workflows/action` const EXECUTE_WORKFLOW_URL = `/api/${instanceId}/workflows/action`
const response = await api.post({ const response = await api.post({
url: EXECUTE_WORKFLOW_URL, url: EXECUTE_WORKFLOW_URL,
body: { body: {
action: block.actionId, action: block.actionId,
args: this.bindContextArgs(block.args, api) args: this.bindContextArgs(block.args, api),
} },
}); })
this.context = { this.context = {
...this.context, ...this.context,
[block.actionId]: response [block.actionId]: response,
} }
} }
console.log("workflowContext", this.context) console.log("workflowContext", this.context)
// TODO: clean this up, don't pass all those args // TODO: clean this up, don't pass all those args
this.run({ workflow: workflow.next, instanceId, api }); this.run({ workflow: workflow.next, instanceId, api })
} },
} }

View File

@ -5,7 +5,7 @@ const newid = require("../../db/newid")
const ajv = new Ajv() const ajv = new Ajv()
exports.save = async function(ctx) { exports.save = async function(ctx) {
console.log("THIS INSTANCE", ctx.params.instanceId); console.log("THIS INSTANCE", ctx.params.instanceId)
const db = new CouchDB(ctx.params.instanceId) const db = new CouchDB(ctx.params.instanceId)
const record = ctx.request.body const record = ctx.request.body
record.modelId = ctx.params.modelId record.modelId = ctx.params.modelId

View File

@ -1,9 +1,9 @@
export default async function () { export default async function() {
const response = await fetch("www.google.com"); const response = await fetch("www.google.com")
console.log(response); console.log(response)
console.log("CUSTOM ACTION"); console.log("CUSTOM ACTION")
return { return {
message: "CUSTOM_WORKFLOW_SCRIPT", message: "CUSTOM_WORKFLOW_SCRIPT",
response response,
} }
} }

View File

@ -1,20 +1,20 @@
const recordController = require("../../record"); const recordController = require("../../record")
module.exports = async function saveRecord(args) { module.exports = async function saveRecord(args) {
console.log("SAVING this record", args.record); console.log("SAVING this record", args.record)
const ctx = { const ctx = {
params: { params: {
instanceId: "inst_60dd510_700f7dc06735403e81d5af91072d7241", instanceId: "inst_60dd510_700f7dc06735403e81d5af91072d7241",
}, },
request: { request: {
body: args.record body: args.record,
} },
} }
await recordController.save(ctx); await recordController.save(ctx)
return { return {
record: ctx.body record: ctx.body,
} }
} }

View File

@ -1,27 +1,25 @@
const sgMail = require('@sendgrid/mail'); const sgMail = require("@sendgrid/mail")
sgMail.setApiKey(process.env.SENDGRID_API_KEY); sgMail.setApiKey(process.env.SENDGRID_API_KEY)
module.exports = async function sendEmail(args) { module.exports = async function sendEmail(args) {
const msg = { const msg = {
to: args.to, to: args.to,
from: args.from, from: args.from,
subject: args.subject, subject: args.subject,
text: args.text text: args.text,
}; }
try { try {
await sgMail.send(msg); await sgMail.send(msg)
return { return {
success: true, success: true,
err err,
} }
} catch (err) { } catch (err) {
return { return {
success: false, success: false,
err err,
} }
} }
} }

View File

@ -28,7 +28,6 @@ exports.create = async function(ctx) {
// return // return
// } // }
workflow.type = "workflow" workflow.type = "workflow"
const response = await db.post(workflow) const response = await db.post(workflow)
workflow._rev = response.rev workflow._rev = response.rev
@ -39,14 +38,14 @@ exports.create = async function(ctx) {
workflow: { workflow: {
...workflow, ...workflow,
_rev: response.rev, _rev: response.rev,
_id: response.id _id: response.id,
},
} }
};
} }
exports.update = async function(ctx) { exports.update = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId) const db = new CouchDB(ctx.params.instanceId)
const workflow = ctx.request.body; const workflow = ctx.request.body
const response = await db.put(workflow) const response = await db.put(workflow)
workflow._rev = response.rev workflow._rev = response.rev
@ -57,7 +56,7 @@ exports.update = async function(ctx) {
workflow: { workflow: {
...workflow, ...workflow,
_rev: response.rev, _rev: response.rev,
_id: response.id _id: response.id,
}, },
} }
} }
@ -77,15 +76,15 @@ exports.find = async function(ctx) {
} }
exports.executeAction = async function(ctx) { exports.executeAction = async function(ctx) {
const workflowAction = require(`./actions/${ctx.request.body.action}`); const workflowAction = require(`./actions/${ctx.request.body.action}`)
const response = await workflowAction(ctx.request.body.args); const response = await workflowAction(ctx.request.body.args)
ctx.body = response; ctx.body = response
} }
exports.fetchActionScript = async function(ctx) { exports.fetchActionScript = async function(ctx) {
const workflowAction = require(`./actions/${ctx.action}`); const workflowAction = require(`./actions/${ctx.action}`)
console.log(workflowAction); console.log(workflowAction)
ctx.body = workflowAction; ctx.body = workflowAction
} }
exports.destroy = async function(ctx) { exports.destroy = async function(ctx) {

View File

@ -4,7 +4,7 @@ const logger = require("koa-pino-logger")
const http = require("http") const http = require("http")
const api = require("./api") const api = require("./api")
const env = require("./environment") const env = require("./environment")
const eventPublisher = require("./events"); const eventPublisher = require("./events")
const app = new Koa() const app = new Koa()
@ -20,7 +20,7 @@ app.use(
}) })
) )
app.context.publisher = eventPublisher; app.context.publisher = eventPublisher
// api routes // api routes
app.use(api.routes()) app.use(api.routes())

View File

@ -1,3 +1,3 @@
const EventEmitter = require("events").EventEmitter; const EventEmitter = require("events").EventEmitter
module.exports = new EventEmitter(); module.exports = new EventEmitter()

View File

@ -2,16 +2,16 @@ const WORKFLOW_SCHEMA = {
properties: { properties: {
type: "workflow", type: "workflow",
pageId: { pageId: {
type: "string" type: "string",
}, },
screenId: { screenId: {
type: "string" type: "string",
}, },
live: { live: {
type: "boolean" type: "boolean",
}, },
uiTree: { uiTree: {
type: "object" type: "object",
}, },
definition: { definition: {
type: "object", type: "object",
@ -20,19 +20,20 @@ const WORKFLOW_SCHEMA = {
next: { next: {
type: "object", type: "object",
properties: { properties: {
environment: { environment: "string" },
type: { type: "string" }, type: { type: "string" },
actionId: { type: "string" }, actionId: { type: "string" },
args: { type: "object" }, args: { type: "object" },
conditions: { type: "array" }, conditions: { type: "array" },
errorHandling: { type: "object" }, errorHandling: { type: "object" },
next: { type: "object" } next: { type: "object" },
}
}, },
} },
} },
} },
}; },
}
module.exports = { module.exports = {
WORKFLOW_SCHEMA WORKFLOW_SCHEMA,
}; }

View File

@ -194,20 +194,6 @@
lodash "^4.17.13" lodash "^4.17.13"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@budibase/client@^0.0.32":
version "0.0.32"
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.0.32.tgz#76d9f147563a0bf939eae7f32ce75b2a527ba496"
integrity sha512-jmCCLn0CUoQbL6h623S5IqK6+GYLqX3WzUTZInSb1SCBOM3pI0eLP5HwTR6s7r42SfD0v9jTWRdyTnHiElNj8A==
dependencies:
"@nx-js/compiler-util" "^2.0.0"
bcryptjs "^2.4.3"
deep-equal "^2.0.1"
lodash "^4.17.15"
lunr "^2.3.5"
regexparam "^1.3.0"
shortid "^2.2.8"
svelte "^3.9.2"
"@budibase/core@^0.0.32": "@budibase/core@^0.0.32":
version "0.0.32" version "0.0.32"
resolved "https://registry.yarnpkg.com/@budibase/core/-/core-0.0.32.tgz#c5d9ab869c5e9596a1ac337aaf041e795b1cc7fa" resolved "https://registry.yarnpkg.com/@budibase/core/-/core-0.0.32.tgz#c5d9ab869c5e9596a1ac337aaf041e795b1cc7fa"
@ -863,11 +849,6 @@ array-equal@^1.0.0:
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=
array-filter@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83"
integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=
array-unique@^0.3.2: array-unique@^0.3.2:
version "0.3.2" version "0.3.2"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
@ -940,13 +921,6 @@ atomic-sleep@^1.0.0:
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5"
integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==
dependencies:
array-filter "^1.0.0"
aws-sign2@~0.7.0: aws-sign2@~0.7.0:
version "0.7.0" version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@ -1669,26 +1643,6 @@ decompress-response@^3.3.0:
dependencies: dependencies:
mimic-response "^1.0.0" mimic-response "^1.0.0"
deep-equal@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.3.tgz#cad1c15277ad78a5c01c49c2dee0f54de8a6a7b0"
integrity sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==
dependencies:
es-abstract "^1.17.5"
es-get-iterator "^1.1.0"
is-arguments "^1.0.4"
is-date-object "^1.0.2"
is-regex "^1.0.5"
isarray "^2.0.5"
object-is "^1.1.2"
object-keys "^1.1.1"
object.assign "^4.1.0"
regexp.prototype.flags "^1.3.0"
side-channel "^1.0.2"
which-boxed-primitive "^1.0.1"
which-collection "^1.0.1"
which-typed-array "^1.1.2"
deep-equal@~1.0.1: deep-equal@~1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
@ -2047,7 +2001,7 @@ error-inject@^1.0.0:
resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37" resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37"
integrity sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc= integrity sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=
es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5: es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5:
version "1.17.5" version "1.17.5"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9"
integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==
@ -2064,19 +2018,6 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstrac
string.prototype.trimleft "^2.1.1" string.prototype.trimleft "^2.1.1"
string.prototype.trimright "^2.1.1" string.prototype.trimright "^2.1.1"
es-get-iterator@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8"
integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==
dependencies:
es-abstract "^1.17.4"
has-symbols "^1.0.1"
is-arguments "^1.0.4"
is-map "^2.0.1"
is-set "^2.0.1"
is-string "^1.0.5"
isarray "^2.0.5"
es-to-primitive@^1.2.1: es-to-primitive@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
@ -3069,21 +3010,11 @@ is-accessor-descriptor@^1.0.0:
dependencies: dependencies:
kind-of "^6.0.0" kind-of "^6.0.0"
is-arguments@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
is-arrayish@^0.2.1: is-arrayish@^0.2.1:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
is-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4"
integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==
is-binary-path@~2.1.0: is-binary-path@~2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
@ -3091,11 +3022,6 @@ is-binary-path@~2.1.0:
dependencies: dependencies:
binary-extensions "^2.0.0" binary-extensions "^2.0.0"
is-boolean-object@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e"
integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==
is-buffer@^1.1.5: is-buffer@^1.1.5:
version "1.1.6" version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
@ -3132,7 +3058,7 @@ is-data-descriptor@^1.0.0:
dependencies: dependencies:
kind-of "^6.0.0" kind-of "^6.0.0"
is-date-object@^1.0.1, is-date-object@^1.0.2: is-date-object@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
@ -3207,21 +3133,11 @@ is-installed-globally@^0.3.1:
global-dirs "^2.0.1" global-dirs "^2.0.1"
is-path-inside "^3.0.1" is-path-inside "^3.0.1"
is-map@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1"
integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==
is-npm@^4.0.0: is-npm@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d"
integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==
is-number-object@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
is-number@^3.0.0: is-number@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
@ -3258,21 +3174,11 @@ is-regex@^1.0.5:
dependencies: dependencies:
has "^1.0.3" has "^1.0.3"
is-set@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43"
integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==
is-stream@^1.1.0: is-stream@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
is-string@^1.0.4, is-string@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
is-symbol@^1.0.2: is-symbol@^1.0.2:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
@ -3289,31 +3195,11 @@ is-type-of@^1.0.0:
is-class-hotfix "~0.0.6" is-class-hotfix "~0.0.6"
isstream "~0.1.2" isstream "~0.1.2"
is-typed-array@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.3.tgz#a4ff5a5e672e1a55f99c7f54e59597af5c1df04d"
integrity sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==
dependencies:
available-typed-arrays "^1.0.0"
es-abstract "^1.17.4"
foreach "^2.0.5"
has-symbols "^1.0.1"
is-typedarray@^1.0.0, is-typedarray@~1.0.0: is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
is-weakmap@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2"
integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==
is-weakset@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83"
integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==
is-windows@^1.0.2: is-windows@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@ -3339,11 +3225,6 @@ isarray@1.0.0, isarray@~1.0.0:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
isarray@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
isbinaryfile@^4.0.6: isbinaryfile@^4.0.6:
version "4.0.6" version "4.0.6"
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b"
@ -4751,14 +4632,6 @@ object-inspect@^1.7.0:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==
object-is@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6"
integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.5"
object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1: object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
@ -5480,19 +5353,6 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2" extend-shallow "^3.0.2"
safe-regex "^1.1.0" safe-regex "^1.1.0"
regexp.prototype.flags@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75"
integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
regexparam@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f"
integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g==
regexpp@^2.0.1: regexpp@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
@ -5821,14 +5681,6 @@ shortid@^2.2.8:
dependencies: dependencies:
nanoid "^2.1.0" nanoid "^2.1.0"
side-channel@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947"
integrity sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==
dependencies:
es-abstract "^1.17.0-next.1"
object-inspect "^1.7.0"
signal-exit@^3.0.0, signal-exit@^3.0.2: signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
@ -6237,11 +6089,6 @@ supports-color@^7.1.0:
dependencies: dependencies:
has-flag "^4.0.0" has-flag "^4.0.0"
svelte@^3.9.2:
version "3.22.3"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.22.3.tgz#6af3bdcfea44c2fadbf17a32c479f49bdf1aba4b"
integrity sha512-DumSy5eWPFPlMUGf3+eHyFSkt5yLqyAmMdCuXOE4qc5GtFyLxwTAGKZmgKmW2jmbpTTeFQ/fSQfDBQbl9Eo7yw==
symbol-tree@^3.2.2: symbol-tree@^3.2.2:
version "3.2.4" version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
@ -6722,44 +6569,11 @@ whatwg-url@^7.0.0:
tr46 "^1.0.1" tr46 "^1.0.1"
webidl-conversions "^4.0.2" webidl-conversions "^4.0.2"
which-boxed-primitive@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1"
integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==
dependencies:
is-bigint "^1.0.0"
is-boolean-object "^1.0.0"
is-number-object "^1.0.3"
is-string "^1.0.4"
is-symbol "^1.0.2"
which-collection@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906"
integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==
dependencies:
is-map "^2.0.1"
is-set "^2.0.1"
is-weakmap "^2.0.1"
is-weakset "^2.0.1"
which-module@^2.0.0: which-module@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
which-typed-array@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.2.tgz#e5f98e56bda93e3dac196b01d47c1156679c00b2"
integrity sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==
dependencies:
available-typed-arrays "^1.0.2"
es-abstract "^1.17.5"
foreach "^2.0.5"
function-bind "^1.1.1"
has-symbols "^1.0.1"
is-typed-array "^1.1.3"
which@^1.2.9, which@^1.3.0: which@^1.2.9, which@^1.3.0:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"