workflow UI with DnD

This commit is contained in:
Martin McKeaveney 2020-05-21 21:40:16 +01:00
parent 2965c2338d
commit e659e69e86
21 changed files with 1152 additions and 231 deletions

View File

@ -1,9 +1,11 @@
import { getStore } from "./store"
import { getBackendUiStore } from "./store/backend"
import { getWorkflowStore } from "./store/workflow"
import LogRocket from "logrocket"
export const store = getStore()
export const backendUiStore = getBackendUiStore()
export const workflowStore = getWorkflowStore()
export const initialise = async () => {
try {

View File

@ -0,0 +1,34 @@
import { writable } from "svelte/store"
import api from "../api"
export const getWorkflowStore = () => {
const INITIAL_WORKFLOW_STATE = {
workflows: []
}
const store = writable(INITIAL_WORKFLOW_STATE)
store.actions = {
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)
return state
})
},
}
return store
}

View File

@ -35,7 +35,7 @@
}
</script>
<heading>
<header>
{#if !showFieldView}
<i class="ri-list-settings-line button--toggled" />
<h3 class="budibase__title--3">Create / Edit Model</h3>
@ -43,7 +43,7 @@
<i class="ri-file-list-line button--toggled" />
<h3 class="budibase__title--3">Create / Edit Field</h3>
{/if}
</heading>
</header>
{#if !showFieldView}
<div class="padding">
<h4 class="budibase__label--big">Settings</h4>

View File

@ -0,0 +1,640 @@
body, html {
margin: 0px;
padding: 0px;
overflow: hidden;
background-repeat: repeat;
background-size: 30px 30px;
background-color: #FBFBFB;
height: 100%;
}
#navigation {
height: 71px;
background-color: #FFF;
border: 1px solid #E8E8EF;
width: 100%;
display: table;
box-sizing: border-box;
position: fixed;
top: 0;
z-index: 9
}
#back {
width: 40px;
height: 40px;
border-radius: 100px;
background-color: #F1F4FC;
text-align: center;
display: inline-block;
vertical-align: top;
margin-top: 12px;
margin-right: 10px
}
#back img {
margin-top: 13px;
}
#names {
display: inline-block;
vertical-align: top;
}
#title {
font-family: Roboto;
font-weight: 500;
font-size: 16px;
color: #393C44;
margin-bottom: 0px;
}
#subtitle {
font-family: Roboto;
color: #808292;
font-size: 14px;
margin-top: 5px;
}
#leftside {
display: inline-block;
vertical-align: middle;
margin-left: 20px;
}
#centerswitch {
position: absolute;
width: 222px;
left: 50%;
margin-left: -111px;
top: 15px;
}
#leftswitch {
border: 1px solid #E8E8EF;
background-color: #FBFBFB;
width: 111px;
height: 39px;
line-height: 39px;
border-radius: 5px 0px 0px 5px;
font-family: Roboto;
color: #393C44;
display: inline-block;
font-size: 14px;
text-align: center;
}
#rightswitch {
font-family: Roboto;
color: #808292;
border-radius: 0px 5px 5px 0px;
border: 1px solid #E8E8EF;
height: 39px;
width: 102px;
display: inline-block;
font-size: 14px;
line-height: 39px;
text-align: center;
margin-left: -5px;
}
#discard {
font-family: Roboto;
font-weight: 500;
font-size: 14px;
color: #A6A6B3;
width: 95px;
height: 38px;
border: 1px solid #E8E8EF;
border-radius: 5px;
text-align: center;
line-height: 38px;
display: inline-block;
vertical-align: top;
transition: all .2s cubic-bezier(.05,.03,.35,1);
}
#discard:hover {
cursor: pointer;
opacity: .7;
}
#publish {
font-family: Roboto;
font-weight: 500;
font-size: 14px;
color: #FFF;
background-color: #217CE8;
border-radius: 5px;
width: 143px;
height: 38px;
margin-left: 10px;
display: inline-block;
vertical-align: top;
text-align: center;
line-height: 38px;
margin-right: 20px;
transition: all .2s cubic-bezier(.05,.03,.35,1);
}
#publish:hover {
cursor: pointer;
opacity: .7;
}
#buttonsright {
float: right;
margin-top: 15px;
}
#leftcard {
width: 363px;
background-color: #FFF;
border: 1px solid #E8E8EF;
box-sizing: border-box;
padding-top: 85px;
padding-left: 20px;
height: 100%;
position: absolute;
z-index: 2;
}
#search input {
width: 318px;
height: 40px;
background-color: #FFF;
border: 1px solid #E8E8EF;
box-sizing: border-box;
box-shadow: 0px 2px 8px rgba(34,34,87,0.05);
border-radius: 5px;
text-indent: 35px;
font-family: Roboto;
font-size: 16px;
}
::-webkit-input-placeholder { /* Edge */
color: #C9C9D5;
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: #C9C9D5
}
::placeholder {
color: #C9C9D5;
}
#search img {
position: absolute;
margin-top: 10px;
width: 18px;
margin-left: 12px;
}
#header {
font-size: 20px;
font-family: Roboto;
font-weight: bold;
color: #393C44;
}
#subnav {
border-bottom: 1px solid #E8E8EF;
width: calc(100% + 20px);
margin-left: -20px;
margin-top: 10px;
}
.navdisabled {
transition: all .3s cubic-bezier(.05,.03,.35,1);
}
.navdisabled:hover {
cursor: pointer;
opacity: .5;
}
.navactive {
color: #393C44!important;
}
#triggers {
margin-left: 20px;
font-family: Roboto;
font-weight: 500;
font-size: 14px;
text-align: center;
color: #808292;
width: calc(88% / 3);
height: 48px;
line-height: 48px;
display: inline-block;
float: left;
}
.navactive:after {
display: block;
content: "";
width: 100%;
height: 4px;
background-color: #217CE8;
margin-top: -4px;
}
#actions {
display: inline-block;
font-family: Roboto;
font-weight: 500;
color: #808292;
font-size: 14px;
height: 48px;
line-height: 48px;
width: calc(88% / 3);
text-align: center;
float: left;
}
#loggers {
width: calc(88% / 3);
display: inline-block;
font-family: Roboto;
font-weight: 500;
color: #808292;
font-size: 14px;
height: 48px;
line-height: 48px;
text-align: center;
}
#footer {
position: absolute;
left: 0;
padding-left: 20px;
line-height: 40px;
bottom: 0;
width: 362px;
border: 1px solid #E8E8EF;
height: 67px;
box-sizing: border-box;
background-color: #FFF;
font-family: Roboto;
font-size: 14px;
}
#footer a {
text-decoration: none;
color: #393C44;
transition: all .2s cubic-bezier(.05,.03,.35,1);
}
#footer a:hover {
opacity: .5;
}
#footer span {
color: #808292;
}
#footer p {
display: inline-block;
color: #808292;
}
#footer img {
margin-left: 5px;
margin-right: 5px;
}
.blockelem:first-child {
margin-top: 20px
}
.blockelem {
padding-top: 10px;
width: 318px;
border: 1px solid transparent;
transition-property: box-shadow, height;
transition-duration: .2s;
transition-timing-function: cubic-bezier(.05,.03,.35,1);
border-radius: 5px;
box-shadow: 0px 0px 30px rgba(22, 33, 74, 0);
box-sizing: border-box;
}
.blockelem:hover {
box-shadow: 0px 4px 30px rgba(22, 33, 74, 0.08);
border-radius: 5px;
background-color: #FFF;
cursor: pointer;
}
.grabme, .blockico {
display: inline-block;
}
.grabme {
margin-top: 10px;
margin-left: 10px;
margin-bottom: -14px;
width: 15px;
}
#blocklist {
height: calc(100% - 220px);
overflow: auto;
}
#proplist {
height: calc(100% - 305px);
overflow: auto;
margin-top: -30px;
padding-top: 30px;
}
.blockin {
display: inline-block;
vertical-align: top;
margin-left: 12px;
}
.blockico {
width: 36px;
height: 36px;
background-color: #F1F4FC;
border-radius: 5px;
text-align: center;
white-space: nowrap;
}
.blockico span {
height: 100%;
width: 0px;
display: inline-block;
vertical-align: middle;
}
.blockico img {
vertical-align: middle;
margin-left: auto;
margin-right: auto;
display: inline-block;
}
.blocktext {
display: inline-block;
width: 220px;
vertical-align: top;
margin-left: 12px
}
.blocktitle {
margin: 0px!important;
padding: 0px!important;
font-family: Roboto;
font-weight: 500;
font-size: 16px;
color: #393C44;
}
.blockdesc {
margin-top: 5px;
font-family: Roboto;
color: #808292;
font-size: 14px;
line-height: 21px;
}
.blockdisabled {
background-color: #F0F2F9;
opacity: .5;
}
#closecard {
position: absolute;
margin-left: 340px;
background-color: #FFF;
border-radius: 0px 5px 5px 0px;
border-bottom: 1px solid #E8E8EF;
border-right: 1px solid #E8E8EF;
border-top: 1px solid #E8E8EF;
width: 53px;
height: 53px;
text-align: center;
z-index: 10;
}
#closecard img {
margin-top: 15px
}
#canvas {
border: 1px solid green;
position: absolute;
width: calc(100% - 361px);
height: calc(100% - 71px);
top: 71px;
left: 361px;
z-index: 0;
overflow: auto;
}
#propwrap {
position: absolute;
right: 0;
top: 0;
width: 311px;
height: 100%;
padding-left: 20px;
overflow: hidden;
z-index: -2;
}
#properties {
position: absolute;
height: 100%;
width: 311px;
background-color: #FFF;
right: -150px;
opacity: 0;
z-index: 2;
top: 0px;
box-shadow: -4px 0px 40px rgba(26, 26, 73, 0);
padding-left: 20px;
transition: all .25s cubic-bezier(.05,.03,.35,1);
}
.itson {
z-index: 2!important;
}
.expanded {
right: 0!important;
opacity: 1!important;
box-shadow: -4px 0px 40px rgba(26, 26, 73, 0.05);
z-index: 2;
}
#header2 {
font-size: 20px;
font-family: Roboto;
font-weight: bold;
color: #393C44;
margin-top: 101px;
}
#close {
margin-top: 100px;
position: absolute;
right: 20px;
z-index: 9999;
transition: all .25s cubic-bezier(.05,.03,.35,1);
}
#close:hover {
cursor: pointer;
opacity: .7;
}
#propswitch {
border-bottom: 1px solid #E8E8EF;
width: 331px;
margin-top: 10px;
margin-left: -20px;
margin-bottom: 30px;
}
#dataprop {
font-family: Roboto;
font-weight: 500;
font-size: 14px;
text-align: center;
color: #393C44;
width: calc(88% / 3);
height: 48px;
line-height: 48px;
display: inline-block;
float: left;
margin-left: 20px;
}
#dataprop:after {
display: block;
content: "";
width: 100%;
height: 4px;
background-color: #217CE8;
margin-top: -4px;
}
#alertprop {
display: inline-block;
font-family: Roboto;
font-weight: 500;
color: #808292;
font-size: 14px;
height: 48px;
line-height: 48px;
width: calc(88% / 3);
text-align: center;
float: left;
}
#logsprop {
width: calc(88% / 3);
display: inline-block;
font-family: Roboto;
font-weight: 500;
color: #808292;
font-size: 14px;
height: 48px;
line-height: 48px;
text-align: center;
}
.inputlabel {
font-family: Roboto;
font-size: 14px;
color: #253134;
}
.dropme {
background-color: #FFF;
border-radius: 5px;
border: 1px solid #E8E8EF;
box-shadow: 0px 2px 8px rgba(34, 34, 87, 0.05);
font-family: Roboto;
font-size: 14px;
color: #253134;
text-indent: 20px;
height: 40px;
line-height: 40px;
width: 287px;
margin-bottom: 25px;
}
.dropme img {
margin-top: 17px;
float: right;
margin-right: 15px;
}
.checkus {
margin-bottom: 10px;
}
.checkus img {
display: inline-block;
vertical-align: middle;
}
.checkus p {
display: inline-block;
font-family: Roboto;
font-size: 14px;
vertical-align: middle;
margin-left: 10px;
}
#divisionthing {
height: 1px;
width: 100%;
background-color: #E8E8EF;
position: absolute;
right: 0px;
bottom: 80;
}
#removeblock {
border-radius: 5px;
position: absolute;
bottom: 20px;
font-family: Roboto;
font-size: 14px;
text-align: center;
width: 287px;
height: 38px;
line-height: 38px;
color: #253134;
border: 1px solid #E8E8EF;
transition: all .3s cubic-bezier(.05,.03,.35,1);
}
#removeblock:hover {
cursor: pointer;
opacity: .5;
}
.noselect {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome, Opera and Firefox */
}
.blockyname {
font-family: Roboto;
font-weight: 500;
color: #253134;
display: inline-block;
vertical-align: middle;
margin-left: 8px;
font-size: 16px;
}
.blockyleft img {
display: inline-block;
vertical-align: middle;
}
.blockyright {
display: inline-block;
float: right;
vertical-align: middle;
margin-right: 20px;
margin-top: 10px;
width: 28px;
height: 28px;
border-radius: 5px;
text-align: center;
background-color: #FFF;
transition: all .3s cubic-bezier(.05,.03,.35,1);
z-index: 10;
}
.blockyright:hover {
background-color: #F1F4FC;
cursor: pointer;
}
.blockyright img {
margin-top: 12px;
}
.blockyleft {
display: inline-block;
margin-left: 20px;
}
.blockydiv {
width: 100%;
height: 1px;
background-color: #E9E9EF;
}
.blockyinfo {
font-family: Roboto;
font-size: 14px;
color: #808292;
margin-top: 15px;
text-indent: 20px;
margin-bottom: 20px;
}
.blockyinfo span {
color: #253134;
font-weight: 500;
display: inline-block;
border-bottom: 1px solid #D3DCEA;
line-height: 20px;
text-indent: 0px;
}
.block {
background-color: #FFF;
margin-top: 0px!important;
box-shadow: 0px 4px 30px rgba(22, 33, 74, 0.05);
}
.selectedblock {
border: 2px solid #217CE8;
box-shadow: 0px 4px 30px rgba(22, 33, 74, 0.08);
}
@media only screen and (max-width: 832px) {
#centerswitch {
display: none;
}
}
@media only screen and (max-width: 560px) {
#names {
display: none;
}
}

View File

@ -58,6 +58,16 @@
--background-button: #f9f9f9;
--button-text: #0055ff;
/* Budibase Styleguide Colors */
--primary: #0055ff;
--secondary: #f1f4fc;
--color: #393c44;
--dark-grey: #808192;
--medium-grey: #e8e8ef;
--background: rgb(251, 251, 251);
--font: #393c44;
--coral: #eb5757;
}
html, body {

View File

@ -10,15 +10,18 @@
<link rel='icon' type='image/png' href='/_builder/favicon.png'>
<link rel='stylesheet' href='/_builder/global.css'>
<link rel='stylesheet' href='/_builder/flowy.css'>
<link rel='stylesheet' href='/_builder/codemirror.css'>
<link rel='stylesheet' href='/_builder/budibase.css'>
<link rel='stylesheet' href='/_builder/monokai.css'>
<link rel='stylesheet' href='/_builder/bundle.css'>
<link rel='stylesheet' href='/_builder/fonts.css'>
<link rel='stylesheet' href="/_builder/uikit.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/alyssaxuu/flowy/flowy.min.css">
</head>
<body id="app">
<script src="https://cdn.jsdelivr.net/gh/alyssaxuu/flowy/flowy.min.js"></script>
<script src='/_builder/bundle.js'></script>
</body>
</html>

View File

@ -1,4 +1,5 @@
import "./global.css"
import "./flowy.css";
import "./fonts.css"
import "./budibase.css"
import "/assets/roboto-v20-latin-ext_latin-300"

View File

@ -0,0 +1,99 @@
<script>
import { onMount } from "svelte"
import { backendUiStore } from "builderStore"
import api from "builderStore/api"
import blockDefinitions from "./blockDefinitions"
const SUB_TABS = [
{
name: "Triggers",
key: "TRIGGERS",
},
{
name: "Actions",
key: "ACTIONS",
},
{
name: "Utilities",
key: "UTILITIES",
},
]
let selectedTab = "TRIGGERS"
let definitions = []
$: definitions = Object.values(blockDefinitions[selectedTab])
</script>
<section>
<header>
<span>Blocks</span>
<span>Props</span>
</header>
<div class="subtabs">
{#each SUB_TABS as tab}
<span
class="hoverable"
class:selected={tab.key === selectedTab}
on:click={() => (selectedTab = tab.key)}>
{tab.name}
</span>
{/each}
</div>
<div id="blocklist">
{#each definitions as blockDefinition}
<div class="blockelem create-flowy noselect">
<input
type="hidden"
name="blockelemtype"
class="blockelemtype"
value="1" />
<div class="grabme">
<!-- <img src="assets/grabme.svg" /> -->
</div>
<div class="blockin">
<div class="blockico">
<span />
<!-- <img src="assets/eye.svg" /> -->
</div>
<div class="blocktext">
<p class="blocktitle">{blockDefinition.name}</p>
<p class="blockdesc">{blockDefinition.description}</p>
</div>
</div>
</div>
{/each}
</div>
</section>
<style>
header {
font-size: 20px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: space-between;
}
.subtabs {
margin-top: 27px;
display: grid;
grid-gap: 5px;
grid-auto-flow: column;
grid-auto-columns: 1fr 1fr 1fr;
}
.subtabs span {
text-align: center;
color: var(--font);
font-weight: 500;
}
.subtabs span.selected {
border-bottom: 4px solid var(--primary);
}
.subtabs span:not(.selected) {
color: var(--dark-grey);
}
</style>

View File

@ -0,0 +1,54 @@
const ACTIONS = {
SET_STATE: {
name: "Update UI",
icon: "",
description: "Update your User Interface with some data.",
type: "CLIENT",
},
NAVIGATE: {
name: "Navigate",
icon: "",
description: "Navigate to another page.",
type: "CLIENT"
},
CREATE_RECORD: {
name: "Save Record",
icon: "",
description: "Save a record to your database.",
type: "SERVER",
},
DELETE_RECORD: {
description: "Delete a record from your database.",
icon: "",
name: "Delete Record",
type: "SERVER",
}
};
const TRIGGERS = {
CLICK: {
name: "Click",
icon: "",
description: "Trigger when you click on an element in the UI."
},
LOAD: {
name: "Load",
icon: "",
description: "Trigger an element has finished loading."
},
INPUT: {
name: "Input",
icon: "",
description: "Trigger when you type into an input box."
},
};
const UTILITIES = {
}
export default {
ACTIONS,
TRIGGERS,
UTILITIES
}

View File

@ -0,0 +1 @@
export { default as BlockPanel } from "./BlockPanel.svelte";

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
$: appId = $store.appId
async function createWorkflow() {
await workflowStore.actions.create({
name,
instanceId,
})
onClosed()
}
</script>
<header>
<i class="ri-stackshare-line" />
Create Workflow
</header>
<div>
<label class="uk-form-label" for="form-stacked-text">Name</label>
<input class="uk-input" type="text" bind:value={name} />
</div>
<footer>
<a href="https://docs.budibase.com">
<i class="ri-information-line" />
Learn about workflows
</a>
<ActionButton alert on:click={onClosed}>Cancel</ActionButton>
<ActionButton disabled={!valid} on:click={createWorkflow}>Save</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

@ -0,0 +1,84 @@
<script>
import Modal from "svelte-simple-modal"
import { onMount, getContext } from "svelte"
import { backendUiStore, workflowStore } from "builderStore";
import api from "builderStore/api"
import CreateWorkflowModal from "./CreateWorkflowModal.svelte";
const { open, close } = getContext("simple-modal")
function newWorkflow() {
open(
CreateWorkflowModal,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
onMount(() => {
workflowStore.actions.fetch($backendUiStore.selectedDatabase._id);
})
</script>
<section>
<header>
Workflows
<i on:click={newWorkflow} class="ri-add-circle-fill" />
</header>
<ul>
{#each $workflowStore.workflows as workflow}
<li class="workflow-item">
<i class="ri-stackshare-line" />
{workflow.name}
</li>
{/each}
</ul>
</section>
<style>
header {
font-size: 20px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: space-between;
}
i {
color: var(--dark-grey);
}
i:hover {
cursor: pointer;
}
ul {
list-style-type: none;
padding: 0;
}
li {
font-size: 14px;
}
.workflow-item {
padding: 20px;
display: flex;
align-items: center;
border-radius: 3px;
height: 40px;
}
.workflow-item i {
font-size: 24px;
margin-right: 10px;
}
.workflow-item:hover {
cursor: pointer;
background: var(--secondary);
}
</style>

View File

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

View File

@ -0,0 +1,47 @@
<script>
import { WorkflowList } from "./WorkflowList"
import { BlockPanel } from "./BlockPanel";
</script>
<div class="root">
<div class="nav">
<WorkflowList />
</div>
<div class="content">
<slot />
</div>
<div class="nav">
<BlockPanel />
</div>
</div>
<style>
.content {
position: relative;
background: var(--background);
}
.nav {
padding: 20px;
}
.root {
height: 100%;
display: flex;
background: #fafafa;
}
.content {
flex: 1 1 auto;
margin: 20px 40px;
}
.nav {
overflow: auto;
flex: 0 1 auto;
width: 275px;
height: 100%;
border: 1px solid var(--medium-grey);
background: var(--white);
}
</style>

View File

@ -0,0 +1,43 @@
<script>
import { onMount } from "svelte"
export let workflow = {}
let canvas
onMount(() => {
if (workflow.uiTree) {
flowy.import(workflow.uiTree);
return;
}
flowy(canvas, onGrab, onRelease);
})
function onGrab() {
}
function onRelease() {
console.log("RELEASED!")
}
// function onGrab(block) {
// // When the user grabs a block
// }
// function onRelease() {
// // When the user releases a block
// console.log(flowy.output())
// }
// function onSnap(block, first, parent) {
// console.log(flowy.output())
// console.log(block, first, parent)
// // When a block snaps with another one
// }
// function onRearrange(block, parent) {
// console.log(block, parent)
// // When a block is rearranged
// }
</script>
<section bind:this={canvas} class="canvas" />

View File

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

View File

@ -0,0 +1,38 @@
import api from "builderStore/api";
class Orchestrator {
set strategy(strategy) {
this._stategy = strategy
}
execute(workflow) {
this._strategy.execute(workflow);
}
}
const ClientStrategy = {
execute: function(workflow) {
const block = workflow.next;
const EXECUTE_WORKFLOW_URL = `api/${workflow.instanceId}/workflows/${workflow._id}`;
switch (block.type) {
case "CLIENT":
// fetch the workflow code from the server, then execute it here in the client
// catch any errors
// check against the conditions in the workflow
// if everything is fine, recurse
this.execute(workflow.next);
break;
case "SERVER":
// hit the server endpoint and wait for the response
// catch any errors
// check against the conditions in the workflow
// if everything is fine, recurse
await api.post()
break;
default:
break;
}
}
}

View File

@ -1,6 +1,7 @@
import regexparam from "regexparam"
import { writable } from "svelte/store"
// TODO: refactor
export const screenRouter = (screens, onScreenSelected, appRootPath) => {
const makeRootedPath = url => {
if (appRootPath) {

View File

@ -1,80 +0,0 @@
export default ({ indexes, helpers }) =>
indexes.map(i => ({
name: `Table based on view: ${i.name} `,
props: tableProps(
i,
helpers.indexSchema(i).filter(c => !excludedColumns.includes(c.name))
),
}))
const excludedColumns = ["id", "key", "sortKey", "type", "isNew"]
const tableProps = (index, indexSchema) => ({
_component: "@budibase/materialdesign-components/Datatable",
_children: [
{
_component: "@budibase/materialdesign-components/DatatableHead",
_children: [
{
_component: "@budibase/materialdesign-components/DatatableRow",
isHeader: true,
_children: columnHeaders(indexSchema),
},
],
},
{
_component: "@budibase/materialdesign-components/DatatableBody",
_children: [
{
_code: rowCode(index),
_component: "@budibase/materialdesign-components/DatatableRow",
_children: dataCells(index, indexSchema),
},
],
},
],
onLoad: [
{
"##eventHandlerType": "List Records",
parameters: {
indexKey: index.nodeKey(),
statePath: index.name,
},
},
],
})
const columnHeaders = indexSchema =>
indexSchema.map(col => ({
_component: "@budibase/materialdesign-components/DatatableCell",
isHeader: true,
_children: [
{
_component: "@budibase/standard-components/text",
type: "none",
text: col.name,
formattingTag: "<b> - bold",
},
],
}))
const dataCells = (index, indexSchema) =>
indexSchema.map(col => ({
_component: "@budibase/materialdesign-components/DatatableCell",
_children: [
{
_component: "@budibase/standard-components/text",
type: "none",
text: `context.${dataItem(index)}.${col.name}`,
},
],
}))
const dataItem = index => `${index.name}_item`
const dataCollection = index => `state.${index.name}`
const rowCode = index =>
`
if (!${dataCollection(index)}) return
for (let ${dataItem(index)} of ${dataCollection(index)})
render( { ${dataItem(index)} } )`

View File

@ -1,149 +0,0 @@
export default ({ records }) =>
records.map(r => ({
name: `Form for Record: ${r.nodeName()}`,
props: outerContainer(r),
}))
const outerContainer = record => ({
_component: "@budibase/standard-components/container",
_code: "",
type: "div",
onLoad: [
{
"##eventHandlerType": "Get New Record",
parameters: {
collectionKey: record.collectionNodeKey(),
childRecordType: record.name,
statePath: record.name,
},
},
],
_children: [
heading(record),
...record.fields.map(f => field(record, f)),
buttons(record),
],
})
const heading = record => ({
_component: "@budibase/materialdesign-components/H3",
text: capitalize(record.name),
})
const field = (record, f) => {
if (f.type === "bool") return checkbox(record, f)
if (
f.type === "string" &&
f.typeOptions &&
f.typeOptions.values &&
f.typeOptions.values.length > 0
)
return select(record, f)
return textField(record, f)
}
const textField = (record, f) => ({
_component: "@budibase/materialdesign-components/Textfield",
label: f.label,
variant: "filled",
disabled: false,
fullwidth: false,
colour: "primary",
maxLength:
f.typeOptions && f.typeOptions.maxLength ? f.typeOptions.maxLength : 0,
placeholder: f.label,
value: fieldValueBinding(record, f),
})
const checkbox = (record, f) => ({
_component: "@budibase/materialdesign-components/Checkbox",
label: f.label,
checked: fieldValueBinding(record, f),
})
const select = (record, f) => ({
_component: "@budibase/materialdesign-components/Select",
value: fieldValueBinding(record, f),
_children: f.typeOptions.values.map(val => ({
_component: "@budibase/materialdesign-components/ListItem",
value: val,
text: val,
})),
})
const fieldValueBinding = (record, f) => `state.${record.name}.${f.name}`
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1)
const buttons = record => ({
_component: "@budibase/standard-components/container",
borderWidth: "1px 0px 0px 0px",
borderColor: "lightgray",
borderStyle: "solid",
_styles: {
position: {
column: ["", ""],
row: ["", ""],
margin: ["", "", "", ""],
padding: ["30px", "", "", ""],
height: [""],
width: [""],
zindex: [""],
},
layout: {
templaterows: [""],
templatecolumns: [""],
},
},
_children: [
{
_component: "@budibase/materialdesign-components/Button",
onClick: [
{
"##eventHandlerType": "Save Record",
parameters: {
statePath: `${record.name}`,
},
},
{
"##eventHandlerType": "Navigate To",
parameters: {
url: `/${record.name}s`,
},
},
],
variant: "raised",
colour: "primary",
size: "medium",
text: `Save ${capitalize(record.name)}`,
},
{
_component: "@budibase/materialdesign-components/Button",
_styles: {
position: {
row: ["", ""],
column: ["", ""],
padding: ["", "", "", ""],
margin: ["", "", "", "10px"],
width: [""],
height: [""],
zindex: [""],
},
layout: {
templatecolumns: [""],
templaterows: [""],
},
},
onClick: [
{
"##eventHandlerType": "Navigate To",
parameters: {
url: `/${record.name}s`,
},
},
],
colour: "secondary",
text: "Cancel",
},
],
})