forked from tsai/budibase
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