mirror of https://github.com/Budibase/budibase.git
21 changed files with 1152 additions and 231 deletions
@ -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 |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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 |
|||
} |
|||
@ -0,0 +1 @@ |
|||
export { default as BlockPanel } from "./BlockPanel.svelte"; |
|||
@ -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> |
|||
@ -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> |
|||
@ -0,0 +1 @@ |
|||
export { default as WorkflowList } from "./WorkflowList.svelte"; |
|||
@ -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> |
|||
@ -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" /> |
|||
@ -0,0 +1,5 @@ |
|||
<script> |
|||
import WorkflowBuilder from "./flowy/WorkflowBuilder.svelte"; |
|||
</script> |
|||
|
|||
<WorkflowBuilder /> |
|||
@ -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; |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -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)} } )` |
|||
@ -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", |
|||
}, |
|||
], |
|||
}) |
|||
Loading…
Reference in new issue