workflow UI with DnD
This commit is contained in:
parent
39c894c459
commit
6f3ad884db
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
|
@ -1,4 +1,5 @@
|
|||
import "./global.css"
|
||||
import "./flowy.css";
|
||||
import "./fonts.css"
|
||||
import "./budibase.css"
|
||||
import "/assets/roboto-v20-latin-ext_latin-300"
|
||||
|
|
|
@ -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,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) {
|
||||
|
|
|
@ -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