This commit is contained in:
Martin McKeaveney 2020-07-28 11:14:44 +01:00
commit 284474db4b
149 changed files with 3087 additions and 3394 deletions

18
.github/stale.yml vendored Normal file
View File

@ -0,0 +1,18 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- roadmap
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@ -28,12 +28,17 @@ jobs:
- run: yarn lint
- run: yarn bootstrap
- run: yarn build
env:
POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }}
POSTHOG_URL: ${{ secrets.POSTHOG_URL }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
- run: yarn test
- name: Prepare for app notarization (macOS)
if: startsWith(matrix.os, 'macos')
# Import Apple API key for app notarization on macOS
run: |
xattr -cr *
mkdir -p ~/private_keys/
echo '${{ secrets.api_key }}' > ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8

11
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"editor.formatOnSave": true,
"eslint.format.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"[svelte]": {
"editor.defaultFormatter": "JamesBirtles.svelte-vscode"
},
"editor.defaultFormatter": "esbenp.prettier-vscode"
}

View File

@ -27,15 +27,15 @@ A client represents a single budibase customer. Each budibase client will have 1
### App
A client can have one or more budibase applications. Think of a budibase application as a tree. Budibase applications have one definition of what the front end will look like,
A client can have one or more budibase applications. Budibase applications would be things like "Developer Inventory Management" or "Goat Herder CRM". Think of a budibase application as a tree.
### Database
An App can have one or more databases. Keeping with our [dendrology](https://en.wikipedia.org/wiki/Dendrology) analogy - think of an database as a branch on the tree. Databases are used to keep data separate for different instances of your app. For example, if you had a CRM app, you may create a database for your US office, and a database for your Australian office. Databases allow us to support [multitenancy](https://www.gartner.com/en/information-technology/glossary/multitenancy) in budibase applications.
### Model
### Table
Models in budibase are almost akin to tables in relational databases. A model may be a "Car" or an "Employee". They are the main building blocks for the creation and management of backend data in budibase.
Tables in budibase are almost akin to tables in relational databases. A table may be a "Car" or an "Employee". They are the main building blocks for the creation and management of backend data in budibase.
### View
@ -95,7 +95,7 @@ then `cd ` into your local copy.
### 4. Initialising Budibase and Creating a Budibase App
`yarn initialise` will initialise your budibase installation. A Budibase apps folder will have been created in `~/.budibase`.
`yarn initialise` will initialise your budibase installation. A Budibase apps folder will have been created in `~/.budibase`. You can also just start up the budibase electron app and it should initialise budibase for you.
This is a blank apps folder, so you will need to create yourself an app.
@ -149,7 +149,25 @@ The backend schema, models and records are stored using PouchDB when developing
### Publishing Budibase to NPM
You can publish all the latest versions of the monorepo packages by running:
#### Testing In Electron
At budibase, we pride ourselves on giving our users a fast, native and slick local development experience. As a result, we use the electron to provide a native GUI for the budibase builder. In order to release budibase out into the wild, you should test your changes in a packaged electron application. To do this, first build budibase from the root directory.
```
yarn build
```
Now everything is built, you can package up your electron application.
```
cd packages/server
yarn build:electron
```
Your new electron application will be stored in `packages/server/dist/<operating-system>`. Open up the executable and make sure everything is working smoothly.
#### Publishing to NPM
Once you are happy that your changes work in electron, you can publish all the latest versions of the monorepo packages by running:
```
yarn publishnpm
@ -157,6 +175,10 @@ yarn publishnpm
from your root directory.
#### CI Release
After NPM has successfully published the budibase packages, a new tag will be pushed to master. This will kick off a github action (can be found at `.github/workflows/release.yml`) this will build and package the electron application for every OS (Windows, Mac, Linux). The binaries will be stored under the new tag on the [budibase releases page](https://github.com/Budibase/budibase/releases).
### Troubleshooting
Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again:
@ -186,4 +208,4 @@ Or if you are in the builder you can run `yarn cy:test`.
* This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/server/blob/master/LICENSE).
* We use the [C4 (Collective Code Construction Contract)](https://rfc.zeromq.org/spec:42/C4/) process for contributions.
Please read this if you are unfamiliar with it.
Please read this if you are unfamiliar with it.

View File

@ -1,5 +1,5 @@
{
"version": "0.0.32",
"version": "0.1.12",
"npmClient": "yarn",
"packages": [
"packages/*"

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
"version": "0.0.32",
"version": "0.1.11",
"license": "AGPL-3.0",
"private": true,
"scripts": {
@ -55,18 +55,20 @@
]
},
"dependencies": {
"@budibase/bbui": "^1.15.0",
"@budibase/client": "^0.0.32",
"@budibase/bbui": "^1.16.0",
"@budibase/client": "^0.1.1",
"@budibase/colorpicker": "^1.0.1",
"@nx-js/compiler-util": "^2.0.0",
"@sentry/browser": "5.19.1",
"codemirror": "^5.51.0",
"date-fns": "^1.29.0",
"deepmerge": "^4.2.2",
"feather-icons": "^4.21.0",
"flatpickr": "^4.5.7",
"lodash": "^4.17.13",
"logrocket": "^1.0.6",
"lunr": "^2.3.5",
"mustache": "^4.0.1",
"posthog-js": "^1.3.1",
"safe-buffer": "^5.1.2",
"shortid": "^2.2.8",
"string_decoder": "^1.2.0",
@ -87,6 +89,7 @@
"babel-jest": "^24.8.0",
"browser-sync": "^2.26.7",
"cypress": "^4.8.0",
"eslint-plugin-cypress": "^2.11.1",
"http-proxy-middleware": "^0.19.1",
"jest": "^24.8.0",
"ncp": "^2.0.0",
@ -109,4 +112,4 @@
"svelte-jester": "^1.0.6"
},
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
}
}

View File

@ -180,6 +180,9 @@ export default {
"process.env.NODE_ENV": JSON.stringify(
production ? "production" : "development"
),
"process.env.POSTHOG_TOKEN": JSON.stringify(process.env.POSTHOG_TOKEN),
"process.env.POSTHOG_URL": JSON.stringify(process.env.POSTHOG_URL),
"process.env.SENTRY_DSN": JSON.stringify(process.env.SENTRY_DSN),
}),
svelte({

View File

@ -5,17 +5,9 @@
import { routes } from "../routify/routes"
import { store, initialise } from "builderStore"
import NotificationDisplay from "components/common/Notification/NotificationDisplay.svelte"
import { notifier } from "builderStore/store/notifications"
function showErrorBanner() {
notifier.danger(
"Whoops! Looks like we're having trouble. Please refresh the page."
)
}
onMount(async () => {
window.addEventListener("error", showErrorBanner)
window.addEventListener("unhandledrejection", showErrorBanner)
await initialise()
})
$basepath = "/_builder"

View File

@ -0,0 +1,24 @@
import * as Sentry from "@sentry/browser"
import posthog from "posthog-js"
function activate() {
Sentry.init({ dsn: process.env.SENTRY_DSN })
posthog.init(process.env.POSTHOG_TOKEN, {
api_host: process.env.POSTHOG_URL,
})
}
function captureException(err) {
Sentry.captureException(err)
}
function captureEvent(event) {
if (process.env.NODE_ENV !== "production") return
posthog.capture(event)
}
export default {
activate,
captureException,
captureEvent,
}

View File

@ -1,11 +1,5 @@
/* Budibase Component Styles */
.header {
font-size: 0.75rem;
color: var(--ink);
text-transform: uppercase;
margin-top: 1rem;
font-weight: 500;
}
.budibase__title {
font-weight: 900;
@ -69,7 +63,7 @@
.budibase__nav-item.selected {
color: var(--ink);
background: var(--blue-light);
background: var(--grey-2);
}
.budibase__nav-item:hover {

View File

@ -1,7 +1,7 @@
import { getStore } from "./store"
import { getBackendUiStore } from "./store/backend"
import { getWorkflowStore } from "./store/workflow/"
import LogRocket from "logrocket"
import analytics from "../analytics"
export const store = getStore()
export const backendUiStore = getBackendUiStore()
@ -10,7 +10,7 @@ export const workflowStore = getWorkflowStore()
export const initialise = async () => {
try {
if (process.env.NODE_ENV === "production") {
LogRocket.init("knlald/budibase")
analytics.activate()
}
} catch (err) {
console.log(err)

View File

@ -2,23 +2,24 @@ import { writable } from "svelte/store"
import { cloneDeep } from "lodash/fp"
import api from "../api"
export const getBackendUiStore = () => {
const INITIAL_BACKEND_UI_STATE = {
models: [],
views: [],
users: [],
selectedDatabase: {},
selectedModel: {},
draftModel: {},
tabs: {
SETUP_PANEL: "SETUP",
NAVIGATION_PANEL: "NAVIGATE",
},
}
const INITIAL_BACKEND_UI_STATE = {
models: [],
views: [],
users: [],
selectedDatabase: {},
selectedModel: {},
draftModel: {},
tabs: {
SETUP_PANEL: "SETUP",
NAVIGATION_PANEL: "NAVIGATE",
},
}
const store = writable(INITIAL_BACKEND_UI_STATE)
export const getBackendUiStore = () => {
const store = writable({ ...INITIAL_BACKEND_UI_STATE })
store.actions = {
reset: () => store.set({ ...INITIAL_BACKEND_UI_STATE }),
database: {
select: async db => {
const modelsResponse = await api.get(`/api/models`)
@ -78,7 +79,6 @@ export const getBackendUiStore = () => {
}
const SAVE_MODEL_URL = `/api/models`
console.log(updatedModel)
const response = await api.post(SAVE_MODEL_URL, updatedModel)
const savedModel = await response.json()
await store.actions.models.fetch()

View File

@ -29,7 +29,8 @@ import {
export const getStore = () => {
const initial = {
apps: [],
appname: "",
name: "",
description: "",
pages: DEFAULT_PAGES_OBJECT,
mainUi: {},
unauthenticatedUi: {},
@ -52,7 +53,6 @@ export const getStore = () => {
store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store)
store.saveScreen = saveScreen(store)
store.deleteScreen = deleteScreen(store)
store.setCurrentScreen = setCurrentScreen(store)
store.setCurrentPage = setCurrentPage(store)
store.createScreen = createScreen(store)
@ -101,7 +101,8 @@ const setPackage = (store, initial) => async pkg => {
initial.libraries = pkg.application.componentLibraries
initial.components = await fetchComponentLibDefinitions(pkg.application._id)
initial.appname = pkg.application.name
initial.name = pkg.application.name
initial.description = pkg.application.description
initial.appId = pkg.application._id
initial.pages = pkg.pages
initial.hasAppPackage = true
@ -160,6 +161,7 @@ const createScreen = store => (screenName, route, layoutComponentName) => {
props: createProps(rootComponent).props,
}
newScreen.route = route
newScreen.name = newScreen.props._id
newScreen.props._instanceName = screenName || ""
state.currentPreviewItem = newScreen
state.currentComponentInfo = newScreen.props
@ -189,24 +191,6 @@ const setCurrentScreen = store => screenName => {
})
}
const deleteScreen = store => name => {
store.update(s => {
const components = s.components.filter(c => c.name !== name)
const screens = s.screens.filter(c => c.name !== name)
s.components = components
s.screens = screens
if (s.currentPreviewItem.name === name) {
s.currentPreviewItem = null
s.currentFrontEndType = ""
}
api.delete(`/_builder/api/${s.appId}/screen/${name}`)
return s
})
}
const savePage = store => async page => {
store.update(state => {
if (state.currentFrontEndType !== "page" || !state.currentPageName) {

View File

@ -28,12 +28,15 @@
}
i {
font-size: 30px;
font-size: 24px;
color: var(--grey-7);
}
span {
font-size: 14px;
text-align: center;
margin-top: 8px;
line-height: 1.25;
}
div:hover {

View File

@ -1,13 +1,112 @@
<script>
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
export let checked = false
export let label = ""
function handleChange() {
checked = !checked
dispatch("change", checked)
}
</script>
{label}
<input class="uk-checkbox" type="checkbox" bind:checked on:change />
<input type="checkbox" class="checkbox" id="_checkbox" />
<label for="_checkbox" class:checked on:click={handleChange}>
<div class="tick_mark" />
</label>
<style>
input {
margin-right: 7px;
.checkbox {
display: none;
}
label {
position: relative;
width: 20px;
height: 20px;
/* background-color: #5e17e9; */
background-color: var(--grey-2);
transform: translateY(-50%);
cursor: pointer;
transition: 0.2s ease transform, 0.2s ease background-color,
0.2s ease box-shadow;
overflow: hidden;
z-index: 1;
border-radius: 4px;
}
label:before {
content: "";
position: absolute;
top: 50%;
right: 0;
left: 0;
width: 12px;
height: 12px;
margin: 0 auto;
background-color: #fff;
transform: translateY(-50%);
transition: 0.2s ease width, 0.2s ease height;
border-radius: 2px;
}
label:active {
transform: translateY(-50%) scale(0.9);
}
.tick_mark {
position: absolute;
top: 50%;
left: 6px;
width: 5px;
height: 4px;
margin: 0 auto;
transform: rotateZ(-40deg);
}
.tick_mark:before,
.tick_mark:after {
content: "";
position: absolute;
background-color: #000;
border-radius: 2px;
opacity: 0;
transition: 0.2s ease transform, 0.2s ease opacity;
}
.tick_mark:before {
left: 0;
bottom: 0;
width: 2px;
height: 6px;
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.23);
transform: translateY(-68px);
}
.tick_mark:after {
left: 0;
bottom: 0;
width: 12px;
height: 2px;
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.23);
transform: translateX(78px);
}
.checked {
/* background-color: #5e17e9; */
background-color: var(--grey-2);
/* box-shadow: 0 7px 10px #5e17e9; */
}
.checked:before {
width: 0;
height: 0;
}
.checked .tick_mark:before,
.checked .tick_mark:after {
transform: translate(0);
opacity: 1;
}
</style>

View File

@ -1,116 +0,0 @@
<script>
import { onMount } from "svelte"
// import { HsvPicker } from "svelte-color-picker"
// export let initialValue = "#ffffff"
export let onChange = color => {}
export let open = false
let value = "#ffffff"
let _justMounted = true //see onColorChange
let pickerHeight = 275
let colorPreview
let pickerTopPosition = null
function rbgaToHexa({ r, g, b, a }) {
r = r.toString(16)
g = g.toString(16)
b = b.toString(16)
a = Math.round(a * 255).toString(16)
if (r.length == 1) r = "0" + r
if (g.length == 1) g = "0" + g
if (b.length == 1) b = "0" + b
if (a.length == 1) a = "0" + a
return "#" + r + g + b + a
}
function onColourChange(rgba) {
value = rbgaToHexa(rgba.detail)
//Hack: so that color change doesn't fire onMount
if (!_justMounted) {
// onChange(value)
}
_justMounted = false
}
function toggleColorpicker(isOpen) {
if (isOpen) {
const {
y: previewYPosition,
height: previewHeight,
} = colorPreview.getBoundingClientRect()
let wiggleRoom = window.innerHeight - previewYPosition
let displayTop = wiggleRoom < pickerHeight
if (displayTop) {
pickerTopPosition = previewYPosition - (pickerHeight - window.scrollY)
} else {
pickerTopPosition = null
}
}
open = isOpen
}
$: style = open ? "display: block;" : "display: none;"
$: pickerStyle = pickerTopPosition ? `top: ${pickerTopPosition}px;` : ""
</script>
<div
bind:this={colorPreview}
on:click={() => toggleColorpicker(true)}
class="color-preview"
style={`background: ${value}`} />
<div class="colorpicker" {style}>
<div class="overlay" on:click|self={() => toggleColorpicker(false)} />
<div class="cp" style={pickerStyle}>
<!-- <HsvPicker on:colorChange={onColourChange} startColor={value} /> -->
</div>
</div>
<!--
OLD LOCAL STORAGE OPTIONS. INCLUDING FOR ADDING LATER
function getRecentColors() {
let colorStore = localStorage.getItem("bb:recentColors")
if (!!colorStore) {
swatches = JSON.parse(colorStore)
}
}
function setRecentColor(color) {
if (swatches.length >= 15) {
swatches.splice(0, 1)
picker.removeSwatch(0)
}
if (!swatches.includes(color)) {
swatches = [...swatches, color]
picker.addSwatch(color)
localStorage.setItem("bb:recentColors", JSON.stringify(swatches))
}
} -->
<style>
.overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
/* background: rgba(5, 5, 5, 0.25); */
}
.cp {
position: absolute;
right: 25px;
}
.color-preview {
height: 30px;
width: 100%;
margin: 5px;
cursor: pointer;
border: 1px solid gainsboro;
}
</style>

View File

@ -3,9 +3,12 @@
viewBox="0 0 24 24"
width="24"
height="24">
<path d="M0 0h24v24H0z" fill="none" />
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 1l9.5 5.5v11L12 23l-9.5-5.5v-11L12 1zm0 14a3 3 0 1 0 0-6 3 3 0 0 0 0
6z"
fill="currentColor" />
fill="currentColor"
d="M8.686 4l2.607-2.607a1 1 0 0 1 1.414 0L15.314 4H19a1 1 0 0 1 1
1v3.686l2.607 2.607a1 1 0 0 1 0 1.414L20 15.314V19a1 1 0 0 1-1
1h-3.686l-2.607 2.607a1 1 0 0 1-1.414 0L8.686 20H5a1 1 0 0
1-1-1v-3.686l-2.607-2.607a1 1 0 0 1 0-1.414L4 8.686V5a1 1 0 0 1
1-1h3.686zM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" />
</svg>

Before

Width:  |  Height:  |  Size: 263 B

After

Width:  |  Height:  |  Size: 494 B

View File

@ -60,7 +60,8 @@
<style>
.fields.selected {
background: var(--grey-1);
background: var(--grey-2);
border: var(--purple) 1px solid;
}
h3 {

View File

@ -65,6 +65,7 @@
margin-right: 20px;
background: none;
outline: none;
font-family: Inter;
}
.switcher > .selected {

View File

@ -113,15 +113,15 @@
on:click={() => {
editRecord(row)
}}>
<div>Edit</div>
<i class="ri-edit-line" />
<div class="label">Edit</div>
</li>
<li>
<div
on:click={() => {
deleteRecord(row)
}}>
Delete
</div>
<li
on:click={() => {
deleteRecord(row)
}}>
<i class="ri-delete-bin-2-line" />
<div class="label">Delete</div>
</li>
</ul>
</div>
@ -146,6 +146,9 @@
</section>
<style>
section {
margin-bottom: 20px;
}
.title {
font-size: 24px;
font-weight: 600;
@ -177,7 +180,7 @@
border-bottom: 1px solid var(--grey-4);
transition: 0.3s background-color;
color: var(--ink);
font-size: 14px;
font-size: 12px;
}
tbody tr:hover {
@ -204,4 +207,28 @@
display: flex;
align-items: center;
}
li {
display: flex;
align-items: center;
border-radius: 5px;
}
i {
color: var(--grey-7);
margin-right: 8px;
font-size: 20px;
}
.label {
color: var(--grey-7);
font-size: 14px;
font-family: inter;
font-weight: 400;
margin: 12px 0px;
}
.label:hover {
color: var(--ink);
cursor: pointer;
}
</style>

View File

@ -37,7 +37,7 @@
<div class="heading">
{#if !showFieldView}
<i class="ri-list-settings-line button--toggled" />
<h3 class="budibase__title--3">Create / Edit Model</h3>
<h3 class="budibase__title--3">Create / Edit Table</h3>
{:else}
<i class="ri-file-list-line button--toggled" />
<h3 class="budibase__title--3">Create / Edit Field</h3>

View File

@ -3,7 +3,7 @@
import { store, backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import { compose, map, get, flatten } from "lodash/fp"
import { Button } from "@budibase/bbui"
import { Input, TextArea, Button } from "@budibase/bbui"
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
import Select from "components/common/Select.svelte"
import RecordFieldControl from "./RecordFieldControl.svelte"
@ -70,7 +70,7 @@
<div class="actions">
<header>
<i class="ri-file-user-fill" />
<h4 class="budibase__title--4">Create / Edit Record</h4>
<h4>Create / Edit Record</h4>
</header>
<ErrorsBox {errors} />
<form on:submit|preventDefault class="uk-form-stacked">
@ -117,15 +117,16 @@
align-items: center;
justify-content: center;
background: var(--blue-light);
color: var(--ink);
color: var(--grey-7);
font-size: 20px;
border-radius: 3px;
border-radius: 5px;
}
h4 {
display: inline-block;
font-size: 24px;
font-weight: bold;
font-weight: 600;
font-family: sans-serif;
color: var(--ink);
margin: 0;
}

View File

@ -36,6 +36,7 @@
class={determineClassName(type)}
bind:value
class:uk-form-danger={errors.length > 0}>
<option />
{#each options as opt}
<option value={opt}>{opt}</option>
{/each}

View File

@ -52,17 +52,28 @@
}
span {
cursor: pointer;
display: grid;
justify-content: center;
align-content: center;
padding: 0px 16px;
height: 36px;
text-align: center;
padding: 10px;
font-weight: 500;
border-radius: 3px;
color: var(--ink-lighter);
background: #ffffff;
color: var(--grey-7);
border-radius: 5px;
font-family: inter;
font-size: 14px;
background: var(--grey-1);
font-weight: 400;
transition: all 0.3s;
text-rendering: optimizeLegibility;
border: none !important;
transition: 0.2s;
outline: none;
}
span:hover {
background: var(--blue-light);
color: var(--ink);
cursor: pointer;
}

View File

@ -19,19 +19,19 @@
...rest,
},
})
notifier.success(`${model.name} model created.`)
notifier.success(`${model.name} table created.`)
}
</script>
<section transition:fade>
<header>
<h2>Create New Model</h2>
<p>Before you can view your model, you need to set it up.</p>
<h2>Create New Table</h2>
<p>Before you can view your table, you need to set it up.</p>
</header>
<div class="block-row">
<span class="block-row-title">Fields</span>
<p>Blocks are pre-made fields and help you build your model quicker.</p>
<p>Blocks are pre-made fields and help you build your table quicker.</p>
<div class="blocks">
{#each Object.values(FIELDS) as field}
<Block
@ -45,7 +45,7 @@
<div class="block-row">
<span class="block-row-title">Blocks</span>
<p>Blocks are pre-made fields and help you build your model quicker.</p>
<p>Blocks are pre-made fields and help you build your table quicker.</p>
<div class="blocks">
{#each Object.values(BLOCKS) as field}
<Block
@ -58,8 +58,8 @@
</div>
<div class="block-row">
<span class="block-row-title">Models</span>
<p>Blocks are pre-made fields and help you build your model quicker.</p>
<span class="block-row-title">Tables</span>
<p>Blocks are pre-made fields and help you build your table quicker.</p>
<div class="blocks">
{#each Object.values(MODELS) as model}
<Block

View File

@ -31,11 +31,11 @@
}
.selected {
background-color: var(--blue-light);
background-color: var(--grey-2);
}
div:hover {
background-color: var(--blue-light);
background-color: var(--grey-1);
cursor: pointer;
}

View File

@ -53,7 +53,7 @@
bind:value={$backendUiStore.tabs.NAVIGATION_PANEL}>
{#if selectedTab === 'NAVIGATE'}
<Button purple wide on:click={setupForNewModel}>
Create New Model
Create New Table
</Button>
<div class="hierarchy-items-container">
{#each $backendUiStore.models as model}
@ -68,7 +68,7 @@
<ListItem
selected={model._id === $backendUiStore.selectedModel._id && fieldName === $backendUiStore.selectedField}
indented
icon="ri-layout-column-fill"
icon="ri-layout-column-line"
title={model.schema[fieldName].name}
on:click={() => selectModel(model, fieldName)} />
{/each}

View File

@ -21,7 +21,7 @@
$: required =
field.constraints &&
field.constraints.presence &&
!constraints.presence.allowEmpty
!field.constraints.presence.allowEmpty
</script>
<div class="info">
@ -41,7 +41,10 @@
<div class="info">
<div class="field">
<label>Required</label>
<input type="checkbox" />
<input
type="checkbox"
bind:checked={required}
on:change={() => (field.constraints.presence.allowEmpty = required)} />
</div>
{#if field.type === 'string'}

View File

@ -52,7 +52,35 @@
})
}
function validate() {
let errors = []
for (let field of Object.values($backendUiStore.draftModel.schema)) {
const restrictedFieldNames = ["type", "modelId"]
if (field.name.startsWith("_")) {
errors.push(`field '${field.name}' - name cannot begin with '_''`)
} else if (restrictedFieldNames.includes(field.name)) {
errors.push(
`field '${field.name}' - is a restricted name, please rename`
)
} else if (!field.name || !field.name.trim()) {
errors.push("field name cannot be blank")
}
}
if (!$backendUiStore.draftModel.name) {
errors.push("Table name cannot be blank")
}
return errors
}
async function saveModel() {
const errors = validate()
if (errors.length > 0) {
notifier.danger(errors.join("/n"))
return
}
await backendUiStore.actions.models.save({
model: $backendUiStore.draftModel,
})
@ -75,10 +103,12 @@
class="budibase__input"
bind:value={$backendUiStore.draftModel.name} />
</div>
<!-- dont have this capability yet..
<div class="titled-input">
<header>Import Data</header>
<Button wide secondary>Import CSV</Button>
</div>
-->
{/if}
<footer>
<Button disabled={!edited} green={edited} wide on:click={saveModel}>

View File

@ -11,7 +11,7 @@
}
$: currentAppInfo = {
appname: $store.appname,
name: $store.name,
}
async function fetchUsers() {

View File

@ -34,7 +34,7 @@
}
.topnavitemright {
cursor: pointer;
color: var(--ink-light);
color: var(--grey-7);
margin: 0px 20px 0px 0px;
padding-top: 4px;
font-weight: 500;

View File

@ -1,5 +1,5 @@
<script>
import { General, Users, DangerZone } from "./tabs"
import { General, Users, DangerZone, APIKeys } from "./tabs"
import { Input, TextArea, Button, Switcher } from "@budibase/bbui"
import { SettingsIcon, CloseIcon } from "components/common/Icons/"
@ -20,6 +20,11 @@
key: "USERS",
component: Users,
},
{
title: "API Keys",
key: "API_KEYS",
component: APIKeys,
},
{
title: "Danger Zone",
key: "DANGERZONE",
@ -50,6 +55,7 @@
<style>
.container {
position: relative;
height: 36rem;
}
.close-button {
@ -83,9 +89,10 @@
width: 20px;
padding: 10px;
background-color: var(--blue-light);
color: var(--grey-7);
}
.body {
padding: 40px 40px 80px 40px;
padding: 40px 40px 40px 40px;
display: grid;
grid-gap: 20px;
}

View File

@ -39,4 +39,16 @@
grid-gap: 18px;
grid-template-columns: 1fr 1fr 1fr;
}
.inputs :global(input) {
padding: 10px 12px;
border-radius: var(--rounded-small);
}
.inputs :global(select) {
padding: 9px 12px;
border-radius: var(--rounded-small);
}
.inputs :global(button) {
border-radius: var(--rounded-small);
height: initial;
}
</style>

View File

@ -0,0 +1,54 @@
<script>
import { Input, Button } from "@budibase/bbui"
import { store } from "builderStore"
import api from "builderStore/api"
import posthog from "posthog-js"
let keys = { budibase: "", sendGrid: "" }
async function updateKey([key, value]) {
const response = await api.put(`/api/keys/${key}`, { value })
const res = await response.json()
if (key === "budibase") posthog.identify(value)
keys = { ...keys, ...res }
}
// Get Keys
async function fetchKeys() {
const response = await api.get(`/api/keys/`)
const res = await response.json()
keys = res
}
fetchKeys()
</script>
<div class="container">
<div class="background">
<Input
on:save={e => updateKey(['budibase', e.detail])}
thin
edit
value={keys.budibase}
label="Budibase" />
</div>
<div class="background">
<Input
on:save={e => updateKey(['sendgrid', e.detail])}
thin
edit
value={keys.sendgrid}
label="Sendgrid" />
</div>
</div>
<style>
.container {
display: grid;
grid-gap: var(--space);
}
.background {
border-radius: 5px;
padding: 12px 0px;
}
</style>

View File

@ -1,31 +1,42 @@
<script>
import { params, goto } from "@sveltech/routify"
import { Input, TextArea, Button } from "@budibase/bbui"
import Title from "../TabTitle.svelte"
import { del } from "builderStore/api"
let value = ""
let loading = false
const deleteApp = () => {
async function deleteApp() {
loading = true
// Do stuff here to delete app!
// Navigate to start
const id = $params.application
const res = await del(`/api/${id}`)
const json = await res.json()
loading = false
if (res.ok) {
$goto("/")
return json
} else {
throw new Error(json)
}
}
</script>
<Title>Danger Zone</Title>
<div class="background">
<p>
Type DELETE into the textbox, then click the following button to delete your
web app:
</p>
<Input
on:change={e => (value = e.target.value)}
on:input={e => (value = e.target.value)}
thin
disabled={loading}
placeholder="Enter your name"
label="Type DELETE into the textbox, then click the following button to
delete your web app:" />
placeholder="" />
<Button
disabled={value !== 'DELETE' || loading}
primary
red
wide
on:click={deleteApp}>
Delete Entire Web App
@ -35,10 +46,12 @@
<style>
.background {
display: grid;
grid-gap: var(--space);
grid-gap: 16px;
border-radius: 5px;
background-color: var(--light-grey);
padding: 12px 12px 18px 12px;
padding: 12px 0px;
}
p {
margin: 0;
}
.background :global(button) {
max-width: 100%;

View File

@ -1,26 +1,40 @@
<script>
import { Input, TextArea, Button } from "@budibase/bbui"
import Title from "../TabTitle.svelte"
import { store } from "builderStore"
import api from "builderStore/api"
async function updateApplication(data) {
const response = await api.put(`/api/${$store.appId}`, data)
const app = await response.json()
store.update(state => {
state = {
...state,
...data,
}
return state
})
}
</script>
<Title>General</Title>
<div class="container">
<div class="background">
<Input thin edit placeholder="Enter your name" label="Name" />
</div>
<div class="background">
<TextArea thin edit placeholder="Enter your name" label="Name" />
</div>
<Input
on:save={e => updateApplication({ name: e.detail })}
thin
edit
value={$store.name}
label="Name" />
<TextArea
on:save={e => updateApplication({ description: e.detail })}
thin
edit
value={$store.description}
label="Description" />
</div>
<style>
.container {
display: grid;
grid-gap: var(--space);
}
.background {
border-radius: 5px;
background-color: var(--light-grey);
padding: 12px 12px 18px 12px;
grid-gap: 32px;
margin-top: 32px;
}
</style>

View File

@ -1,6 +1,5 @@
<script>
import { Input, Select, Button } from "@budibase/bbui"
import Title from "../TabTitle.svelte"
import UserRow from "../UserRow.svelte"
import { store, backendUiStore } from "builderStore"
@ -52,9 +51,8 @@
let fetchUsersPromise = fetchUsers()
</script>
<Title>Users</Title>
<div class="container">
<div class="background create">
<div class="background">
<div class="title">Create new user</div>
<div class="inputs">
<Input thin bind:value={username} name="Name" placeholder="Username" />
@ -69,10 +67,10 @@
</Select>
</div>
<div class="create-button">
<Button on:click={createUser} small blue>Create</Button>
<Button on:click={createUser} small primary>Create</Button>
</div>
</div>
<div class="background">
<div class="background-users">
<div class="title">Current Users</div>
{#await fetchUsersPromise}
Loading state!
@ -87,7 +85,8 @@
{/each}
</ul>
{:catch error}
err0r
Something went wrong when trying to fetch users. Please refresh (CMD + R /
CTRL + R) the page and try again.
{/await}
</div>
</div>
@ -95,27 +94,32 @@
<style>
.container {
display: grid;
grid-gap: 14px;
grid-gap: 32px;
margin-top: 32px;
}
.background {
position: relative;
display: grid;
grid-gap: 12px;
border-radius: 5px;
background-color: var(--grey-2);
padding: 12px 12px 18px 12px;
}
.background.create {
background-color: var(--blue-light);
}
.inputs :global(select) {
padding: 12px 9px;
height: initial;
.background-users {
position: relative;
display: grid;
grid-gap: 12px;
border-radius: 5px;
}
.create-button {
position: absolute;
top: 12px;
right: 12px;
top: 0px;
right: 0px;
}
.create-button :global(button) {
font-size: var(--font-size-sm);
min-width: 100px;
border-radius: var(--rounded-small);
}
.title {
font-size: 14px;
@ -123,13 +127,24 @@
}
.inputs {
display: grid;
margin-top: 12px;
grid-gap: 18px;
grid-template-columns: 1fr 1fr 1fr;
}
.inputs :global(input) {
padding: 10px 12px;
border-radius: var(--rounded-small);
}
.inputs :global(select) {
padding: 10px 12px;
border-radius: var(--rounded-small);
background-color: var(--grey-2);
}
ul {
list-style: none;
padding: 0;
display: grid;
grid-gap: 8px;
margin-top: 0;
}
</style>

View File

@ -2,4 +2,5 @@ export { default as General } from "./General.svelte"
export { default as Integrations } from "./Integrations.svelte"
export { default as Permissions } from "./Permissions.svelte"
export { default as Users } from "./Users.svelte"
export { default as APIKeys } from "./APIKeys.svelte"
export { default as DangerZone } from "./DangerZone.svelte"

View File

@ -10,18 +10,19 @@
<h3 class="app-title">{name}</h3>
<p class="app-desc">{description}</p>
<div class="card-footer">
<a href={`/_builder/${_id}`} class="app-button">Open Web App</a>
<a href={`/_builder/${_id}`} class="app-button">Open {name}</a>
</div>
</div>
<style>
.apps-card {
background-color: var(--white);
padding: 20px 20px 30px 20px;
padding: 20px 20px 20px 20px;
max-width: 400px;
max-height: 150px;
border-radius: 5px;
border: 1px solid var(--grey-4);
font-family: Inter;
}
.app-button:hover {
@ -34,12 +35,15 @@
font-weight: 600;
color: var(--ink);
text-transform: capitalize;
font-family: Inter;
}
.app-desc {
color: var(--grey-7);
font-family: Inter;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.card-footer {

View File

@ -25,7 +25,7 @@
<style>
.apps {
display: grid;
grid-template-columns: repeat(auto-fill, 380px);
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
grid-gap: 20px 40px;
justify-content: start;
}

View File

@ -6,6 +6,7 @@
import { getContext } from "svelte"
import { fade } from "svelte/transition"
import { post } from "builderStore/api"
import analytics from "../../analytics"
const { open, close } = getContext("simple-modal")
@ -38,6 +39,11 @@
const res = await response.json()
analytics.captureEvent("web_app_created", {
name,
description,
appId: res._id,
})
$goto(`./${res._id}`)
} catch (error) {
console.error(error)
@ -63,7 +69,7 @@
<span class="icon">
<AppsIcon />
</span>
<h3>Create new web app</h3>
<h3 class="header">Create new web app</h3>
</div>
<Input
name="name"
@ -90,7 +96,7 @@
<InfoIcon />
How to get started
</a>
<Button outline thin on:click={_onCancel}>Cancel</Button>
<Button secondary thin on:click={_onCancel}>Cancel</Button>
<Button primary thin on:click={_onOkay}>Save</Button>
</div>
<div class="close-button" on:click={_onCancel}>
@ -125,10 +131,11 @@
align-items: center;
margin-bottom: 20px;
}
h3 {
.header {
margin: 0;
font-size: 24px;
font-weight: bold;
font-weight: 600;
font-family: inter;
}
.icon {
display: grid;

View File

@ -25,38 +25,105 @@
name: "Screen Placeholder",
route: "*",
props: {
_id: "screenslot-placeholder",
_component: "@budibase/standard-components/container",
_styles: {
normal: {},
hover: {},
active: {},
selected: {},
},
_code: "",
className: "",
onLoad: [],
type: "div",
_children: [
{
_id: "51a1b494-0fa4-49c3-90cc-c2a6c7a3f888",
_component: "@budibase/standard-components/container",
_styles: { normal: {}, hover: {}, active: {}, selected: {} },
_id: "__screenslot__text",
_styles: {
normal: {
display: "flex",
"flex-direction": "column",
"align-items": "center",
},
hover: {},
active: {},
selected: {},
},
_code: "",
className: "",
onLoad: [],
type: "div",
_instanceId: "inst_40d9036_4c81114e2bf145ab8721978c66e09a10",
_instanceName: "Container",
_children: [
{
_component: "@budibase/standard-components/text",
_id: "90a52cd0-f215-46c1-b29b-e28f9e7edf72",
_component: "@budibase/standard-components/heading",
_styles: {
normal: {},
normal: {
width: "500px",
padding: "8px",
},
hover: {},
active: {},
selected: {},
},
_id: "__screenslot__text_2",
_code: "",
text: "content",
font: "",
color: "",
textAlign: "inline",
verticalAlign: "inline",
formattingTag: "none",
className: "",
text: "Screen Slot",
type: "h1",
_instanceId: "inst_40d9036_4c81114e2bf145ab8721978c66e09a10",
_instanceName: "Heading",
_children: [],
},
{
_id: "71a3da65-72c6-4c43-8c6a-49871c07b77d",
_component: "@budibase/standard-components/text",
_styles: {
normal: {
"max-width": "",
"text-align": "left",
width: "500px",
padding: "8px",
},
hover: {},
active: {},
selected: {},
},
_code: "",
text:
"The screens that you create will be displayed inside this box.",
type: "none",
_instanceId: "inst_40d9036_4c81114e2bf145ab8721978c66e09a10",
_instanceName: "Text",
},
{
_id: "8af80374-460d-497b-a5d8-7dd2ec4a7bbc",
_component: "@budibase/standard-components/text",
_styles: {
normal: {
"max-width": "",
"text-align": "left",
width: "500px",
padding: "8px",
},
hover: {},
active: {},
selected: {},
},
_code: "",
text:
"This box is just a placeholder, to show you the position of screens.",
type: "none",
_instanceId: "inst_40d9036_4c81114e2bf145ab8721978c66e09a10",
_instanceName: "Text",
},
],
},
],
_instanceName: "Content Placeholder",
},
}

View File

@ -5,7 +5,11 @@ export default `<html>
<style>
body, html {
height: 100%!important;
font-family: Roboto !important;
font-family: Inter !important;
margin: 0px!important;
}
*, *:before, *:after {
box-sizing: border-box;
}
.lay-__screenslot__text {
width: 100%;
@ -21,6 +25,24 @@ export default `<html>
text-transform: uppercase;
font-weight: bold;
}
.container-screenslot-placeholder {
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
text-align: center;
border-style: dashed !important;
border-width: 1px;
color: #000000;
background: #fafafa;
height: 94%;
}
.container-screenslot-placeholder span {
display: block;
margin-bottom: 10px;
}
</style>
<script src='/assets/budibase-client.js'></script>
<script>

View File

@ -1,29 +0,0 @@
<script>
import FlatButton from "./FlatButton.svelte"
export let format = "hex"
export let onclick = format => {}
let colorFormats = ["hex", "rgb", "hsl"]
</script>
<div class="flatbutton-group">
{#each colorFormats as text}
<FlatButton
selected={format === text}
{text}
on:click={() => onclick(text)} />
{/each}
</div>
<style>
.flatbutton-group {
font-weight: 500;
display: flex;
flex-flow: row nowrap;
justify-content: center;
width: 170px;
height: 30px;
align-self: center;
}
</style>

View File

@ -1,24 +0,0 @@
<script>
import { buildStyle } from "./helpers.js"
import { fade } from "svelte/transition"
export let backgroundSize = "10px"
export let borderRadius = ""
export let height = ""
export let width = ""
export let margin = ""
$: style = buildStyle({ backgroundSize, borderRadius, height, width, margin })
</script>
<div in:fade {style}>
<slot />
</div>
<style>
div {
background-image: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
height: fit-content;
width: fit-content;
}
</style>

View File

@ -1,29 +0,0 @@
<script>
export let text = ""
export let selected = false
</script>
<div class="flatbutton" class:selected on:click>{text}</div>
<style>
.flatbutton {
cursor: pointer;
border: 1px solid #d4d4d4;
border-radius: 8px;
text-transform: uppercase;
margin: 5px;
transition: all 0.3s;
font-size: 10px;
flex: 1;
display: flex;
justify-content: center;
align-items: center;
background: #f1f3f4;
}
.selected {
color: #ffffff;
background-color: #003cb0;
border: none;
}
</style>

View File

@ -1,28 +0,0 @@
<script>
export let value = ""
</script>
<div>
<input on:input on:change type="text" {value} maxlength="25" />
</div>
<style>
div {
display: flex;
justify-content: center;
margin: 5px 0px;
}
input {
width: 175px;
font-size: 13px;
background: #f1f3f4;
border-radius: 8px;
height: 20px;
outline-color: #003cb0;
color: inherit;
text-align: center;
border: 1px solid #dadada;
font-weight: 550;
}
</style>

View File

@ -1,73 +0,0 @@
<script>
import { onMount, createEventDispatcher } from "svelte"
import CheckedBackground from "./CheckedBackground.svelte"
const dispatch = createEventDispatcher()
export let h = 0
export let s = 0
export let v = 0
export let a = 1
let palette
let paletteHeight,
paletteWidth = 0
function handleClick(event) {
const { left, top } = palette.getBoundingClientRect()
let clickX = event.clientX - left
let clickY = event.clientY - top
if (
clickX > 0 &&
clickY > 0 &&
clickX < paletteWidth &&
clickY < paletteHeight
) {
let s = (clickX / paletteWidth) * 100
let v = 100 - (clickY / paletteHeight) * 100
dispatch("change", { s, v })
}
}
$: pickerX = (s * paletteWidth) / 100
$: pickerY = paletteHeight * ((100 - v) / 100)
$: paletteGradient = `linear-gradient(to top, rgba(0, 0, 0, 1), transparent),
linear-gradient(to left, hsla(${h}, 100%, 50%, ${a}), rgba(255, 255, 255, ${a}))
`
$: style = `background: ${paletteGradient};`
$: pickerStyle = `transform: translate(${pickerX - 8}px, ${pickerY - 8}px);`
</script>
<CheckedBackground width="100%">
<div
bind:this={palette}
bind:clientHeight={paletteHeight}
bind:clientWidth={paletteWidth}
on:click={handleClick}
class="palette"
{style}>
<div class="picker" style={pickerStyle} />
</div>
</CheckedBackground>
<style>
.palette {
position: relative;
width: 100%;
height: 140px;
cursor: crosshair;
overflow: hidden;
}
.picker {
position: absolute;
width: 10px;
height: 10px;
background: transparent;
border: 2px solid white;
border-radius: 50%;
}
</style>

View File

@ -1,86 +0,0 @@
<script>
import { onMount, createEventDispatcher } from "svelte"
import dragable from "./drag.js"
export let value = 1
export let type = "hue"
const dispatch = createEventDispatcher()
let slider
let sliderWidth = 0
function onSliderChange(mouseX, isDrag = false) {
const { left, width } = slider.getBoundingClientRect()
let clickPosition = mouseX - left
let percentageClick = (clickPosition / sliderWidth).toFixed(2)
if (percentageClick >= 0 && percentageClick <= 1) {
let value = type === "hue" ? 360 * percentageClick : percentageClick
dispatch("change", { color: value, isDrag })
}
}
$: thumbPosition =
type === "hue" ? sliderWidth * (value / 360) : sliderWidth * value
$: style = `transform: translateX(${thumbPosition - 6}px);`
</script>
<div
bind:this={slider}
bind:clientWidth={sliderWidth}
on:click={event => onSliderChange(event.clientX)}
class="color-format-slider"
class:hue={type === 'hue'}
class:alpha={type === 'alpha'}>
<div
use:dragable
on:drag={e => onSliderChange(e.detail, true)}
on:dragend
class="slider-thumb"
{style} />
</div>
<style>
.color-format-slider {
position: relative;
align-self: center;
height: 8px;
width: 150px;
border-radius: 10px;
margin: 10px 0px;
border: 1px solid #e8e8ef;
cursor: pointer;
}
.hue {
background: linear-gradient(
to right,
hsl(0, 100%, 50%),
hsl(60, 100%, 50%),
hsl(120, 100%, 50%),
hsl(180, 100%, 50%),
hsl(240, 100%, 50%),
hsl(300, 100%, 50%),
hsl(360, 100%, 50%)
);
}
.alpha {
background: linear-gradient(to right, transparent, rgb(0 0 0));
}
.slider-thumb {
position: absolute;
bottom: -3px;
height: 12px;
width: 12px;
border: 1px solid #777676;
border-radius: 50%;
background-color: #ffffff;
cursor: grab;
}
</style>

View File

@ -1,61 +0,0 @@
<script>
import { createEventDispatcher } from "svelte"
import { fade } from "svelte/transition"
import CheckedBackground from "./CheckedBackground.svelte"
export let hovered = false
export let color = "#fff"
const dispatch = createEventDispatcher()
</script>
<div class="space">
<CheckedBackground borderRadius="6px">
<div
in:fade
class="swatch"
style={`background: ${color};`}
on:click|self
on:mouseover={() => (hovered = true)}
on:mouseleave={() => (hovered = false)}>
{#if hovered}
<div
in:fade
class="remove-icon"
on:click|self={() => dispatch('removeswatch')}>
<span on:click|self={() => dispatch('removeswatch')}>&times;</span>
</div>
{/if}
</div>
</CheckedBackground>
</div>
<style>
.swatch {
position: relative;
cursor: pointer;
border-radius: 6px;
border: 1px solid #dedada;
height: 20px;
width: 20px;
}
.space {
padding: 3px 5px;
}
.remove-icon {
position: absolute;
right: 0;
top: -5px;
right: -4px;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #800000;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@ -1,23 +0,0 @@
export default function(node) {
function handleMouseDown() {
window.addEventListener("mousemove", handleMouseMove)
window.addEventListener("mouseup", handleMouseUp)
}
function handleMouseMove(event) {
let mouseX = event.clientX
node.dispatchEvent(
new CustomEvent("drag", {
detail: mouseX,
})
)
}
function handleMouseUp() {
window.removeEventListener("mousedown", handleMouseDown)
window.removeEventListener("mousemove", handleMouseMove)
node.dispatchEvent(new CustomEvent("dragend"))
}
node.addEventListener("mousedown", handleMouseDown)
}

View File

@ -1,14 +0,0 @@
export const buildStyle = styles => {
let str = ""
for (let s in styles) {
if (styles[s]) {
let key = convertCamel(s)
str += `${key}: ${styles[s]}; `
}
}
return str
}
export const convertCamel = str => {
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
}

View File

@ -1,2 +0,0 @@
import Colorpreview from "./Colorpreview.svelte"
export default Colorpreview

View File

@ -1,279 +0,0 @@
export const isValidHex = str =>
/^#(?:[A-F0-9]{3}$|[A-F0-9]{4}$|[A-F0-9]{6}$|[A-F0-9]{8})$/gi.test(str)
const getHexaValues = hexString => {
if (hexString.length <= 5) {
let hexArr = hexString.match(/[A-F0-9]/gi)
let t = hexArr.map(c => (c += c))
return t
} else {
return hexString.match(/[A-F0-9]{2}/gi)
}
}
export const isValidRgb = str => {
const hasValidStructure = /^(?:rgba\(|rgb\()(?:[0-9,\s]|\.(?=\d))*\)$/gi.test(
str
)
if (hasValidStructure) {
return testRgbaValues(str.toLowerCase())
}
}
const findNonNumericChars = /[a-z()\s]/gi
export const getNumericValues = str =>
str
.replace(findNonNumericChars, "")
.split(",")
.map(v => (v !== "" ? v : undefined))
export const testRgbaValues = str => {
const rgba = getNumericValues(str)
const [r, g, b, a] = rgba
let isValidLengthRange =
(str.startsWith("rgb(") && rgba.length === 3) ||
(str.startsWith("rgba(") && rgba.length === 4)
let isValidColorRange = [r, g, b].every(v => v >= 0 && v <= 255)
let isValidAlphaRange = str.startsWith("rgba(")
? `${a}`.length <= 4 && a >= 0 && a <= 1
: true
return isValidLengthRange && isValidColorRange && isValidAlphaRange
}
export const isValidHsl = str => {
const hasValidStructure = /^(?:hsl\(|hsla\()(?:[0-9,%\s]|\.(?=\d))*\)$/gi.test(
str
)
if (hasValidStructure) {
return testHslaValues(str.toLowerCase())
}
}
export const testHslaValues = str => {
const hsla = getNumericValues(str)
const [h, s, l, a] = hsla
const isUndefined = [h, s, l].some(v => v === undefined)
if (isUndefined) return false
let isValidLengthRange =
(str.startsWith("hsl(") && hsla.length === 3) ||
(str.startsWith("hsla(") && hsla.length === 4)
let isValidHue = h >= 0 && h <= 360
let isValidSatLum = [s, l].every(
v => v.endsWith("%") && parseInt(v) >= 0 && parseInt(v) <= 100
)
let isValidAlphaRange = str.startsWith("hsla(")
? `${a}`.length <= 4 && a >= 0 && a <= 1
: true
return isValidLengthRange && isValidHue && isValidSatLum && isValidAlphaRange
}
export const getColorFormat = color => {
if (typeof color === "string") {
if (isValidHex(color)) {
return "hex"
} else if (isValidRgb(color)) {
return "rgb"
} else if (isValidHsl(color)) {
return "hsl"
}
}
}
export const convertToHSVA = (value, format) => {
switch (format) {
case "hex":
return getAndConvertHexa(value)
case "rgb":
return getAndConvertRgba(value)
case "hsl":
return getAndConvertHsla(value)
}
}
export const convertHsvaToFormat = (hsva, format) => {
switch (format) {
case "hex":
return hsvaToHexa(hsva, true)
case "rgb":
return hsvaToRgba(hsva, true)
case "hsl":
return hsvaToHsla(hsva)
}
}
export const getAndConvertHexa = color => {
let [rHex, gHex, bHex, aHex] = getHexaValues(color)
return hexaToHSVA([rHex, gHex, bHex], aHex)
}
export const getAndConvertRgba = color => {
let rgba = getNumericValues(color)
return rgbaToHSVA(rgba)
}
export const getAndConvertHsla = color => {
let hsla = getNumericValues(color)
return hslaToHSVA(hsla)
}
export const hexaToHSVA = (hex, alpha = "FF") => {
const rgba = hex
.map(v => parseInt(v, 16))
.concat(Number((parseInt(alpha, 16) / 255).toFixed(2)))
return rgbaToHSVA(rgba)
}
export const rgbaToHSVA = rgba => {
const [r, g, b, a = 1] = rgba
let hsv = _rgbToHSV([r, g, b])
return [...hsv, a].map(x => parseFloat(x))
}
export const hslaToHSVA = ([h, s, l, a = 1]) => {
let sat = s.replace(/%/, "")
let lum = l.replace(/%/, "")
let hsv = _hslToHSV([h, sat, lum])
return [...hsv, a].map(x => parseFloat(x))
}
export const hsvaToHexa = (hsva, asString = false) => {
const [r, g, b, a] = hsvaToRgba(hsva)
const hexa = [r, g, b]
.map(v => {
let hex = Math.round(v).toString(16)
return hex.length === 1 ? `0${hex}` : hex
})
.concat(Math.round(a * 255).toString(16))
return asString ? `#${hexa.join("")}` : hexa
}
export const hsvaToRgba = ([h, s, v, a = 1], asString = false) => {
let rgb = _hsvToRgb([h, s, v]).map(x => Math.round(x))
let rgba = [...rgb, a < 1 ? _fixNum(a, 2) : a]
return asString ? `rgba(${rgba.join(",")})` : rgba
}
export const hsvaToHsla = ([h, s, v, a = 1]) => {
let [hue, sat, lum] = _hsvToHSL([h, s, v])
let hsla = [hue, sat + "%", lum + "%", a < 1 ? _fixNum(a, 2) : a]
return `hsla(${hsla.join(",")})`
}
export const _hslToHSV = hsl => {
const h = hsl[0]
let s = hsl[1] / 100
let l = hsl[2] / 100
let smin = s
const lmin = Math.max(l, 0.01)
l *= 2
s *= l <= 1 ? l : 2 - l
smin *= lmin <= 1 ? lmin : 2 - lmin
const v = (l + s) / 2
const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s)
return [h, sv * 100, v * 100]
}
//Credit : https://github.com/Qix-/color-convert
export const _rgbToHSV = rgb => {
let rdif
let gdif
let bdif
let h
let s
const r = rgb[0] / 255
const g = rgb[1] / 255
const b = rgb[2] / 255
const v = Math.max(r, g, b)
const diff = v - Math.min(r, g, b)
const diffc = function(c) {
return (v - c) / 6 / diff + 1 / 2
}
if (diff === 0) {
h = 0
s = 0
} else {
s = diff / v
rdif = diffc(r)
gdif = diffc(g)
bdif = diffc(b)
if (r === v) {
h = bdif - gdif
} else if (g === v) {
h = 1 / 3 + rdif - bdif
} else if (b === v) {
h = 2 / 3 + gdif - rdif
}
if (h < 0) {
h += 1
} else if (h > 1) {
h -= 1
}
}
const hsvResult = [h * 360, s * 100, v * 100].map(v => Math.round(v))
return hsvResult
}
//Credit : https://github.com/Qix-/color-convert
export const _hsvToRgb = hsv => {
const h = hsv[0] / 60
const s = hsv[1] / 100
let v = hsv[2] / 100
const hi = Math.floor(h) % 6
const f = h - Math.floor(h)
const p = 255 * v * (1 - s)
const q = 255 * v * (1 - s * f)
const t = 255 * v * (1 - s * (1 - f))
v *= 255
switch (hi) {
case 0:
return [v, t, p]
case 1:
return [q, v, p]
case 2:
return [p, v, t]
case 3:
return [p, q, v]
case 4:
return [t, p, v]
case 5:
return [v, p, q]
}
}
//Credit : https://github.com/Qix-/color-convert
export const _hsvToHSL = hsv => {
const h = hsv[0]
const s = hsv[1] / 100
const v = hsv[2] / 100
const vmin = Math.max(v, 0.01)
let sl
let l
l = (2 - s) * v
const lmin = (2 - s) * vmin
sl = s * vmin
sl /= lmin <= 1 ? lmin : 2 - lmin
sl = sl || 0
l /= 2
return [_fixNum(h, 0), _fixNum(sl * 100, 0), _fixNum(l * 100, 0)]
}
export const _fixNum = (value, decimalPlaces) =>
Number(parseFloat(value).toFixed(decimalPlaces))

View File

@ -1,106 +0,0 @@
import { getColorFormat, convertToHSVA, convertHsvaToFormat } from "./utils"
describe("convertToHSVA - convert to hsva from format", () => {
test("convert from hexa", () => {
expect(convertToHSVA("#f222d382", "hex")).toEqual([309, 86, 95, 0.51])
})
test("convert from hex", () => {
expect(convertToHSVA("#f222d3", "hex")).toEqual([309, 86, 95, 1])
})
test("convert from rgba", () => {
expect(convertToHSVA("rgba(242, 34, 211, 1)", "rgb")).toEqual([
309,
86,
95,
1,
])
})
test("convert from rgb", () => {
expect(convertToHSVA("rgb(150, 80, 255)", "rgb")).toEqual([264, 69, 100, 1])
})
test("convert from from hsl", () => {
expect(convertToHSVA("hsl(264, 100%, 65.7%)", "hsl")).toEqual([
264,
68.6,
100,
1,
])
})
test("convert from from hsla", () => {
expect(convertToHSVA("hsla(264, 100%, 65.7%, 0.51)", "hsl")).toEqual([
264,
68.6,
100,
0.51,
])
})
})
describe("convertHsvaToFormat - convert from hsva to format", () => {
test("Convert to hexa", () => {
expect(convertHsvaToFormat([264, 68.63, 100, 0.5], "hex")).toBe("#9650ff80")
})
test("Convert to rgba", () => {
expect(convertHsvaToFormat([264, 68.63, 100, 0.75], "rgb")).toBe(
"rgba(150,80,255,0.75)"
)
})
test("Convert to hsla", () => {
expect(convertHsvaToFormat([264, 68.63, 100, 1], "hsl")).toBe(
"hsla(264,100%,66%,1)"
)
})
})
describe("Get Color Format", () => {
test("Testing valid hex string", () => {
expect(getColorFormat("#FFF")).toBe("hex")
})
test("Testing invalid hex string", () => {
expect(getColorFormat("#FFZ")).toBeUndefined()
})
test("Testing valid hex with alpha", () => {
expect(getColorFormat("#FF00BB80")).toBe("hex")
})
test("Test valid rgb value", () => {
expect(getColorFormat("RGB(255, 20, 50)")).toBe("rgb")
})
test("Testing invalid rgb value", () => {
expect(getColorFormat("rgb(255, 0)")).toBeUndefined()
})
test("Testing rgb value with alpha", () => {
expect(getColorFormat("rgba(255, 0, 50, 0.5)")).toBe("rgb")
})
test("Testing rgb value with incorrectly provided alpha", () => {
expect(getColorFormat("rgb(255, 0, 50, 0.5)")).toBeUndefined()
})
test("Testing invalid hsl value", () => {
expect(getColorFormat("hsla(255, 0)")).toBeUndefined()
})
test("Testing hsla value with alpha", () => {
expect(getColorFormat("hsla(150, 60%, 50%, 0.5)")).toBe("hsl")
})
test("Testing hsl value with incorrectly provided alpha", () => {
expect(getColorFormat("hsl(150, 0, 50, 0.5)")).toBeUndefined()
})
test("Testing out of bounds hsl", () => {
expect(getColorFormat("hsl(375, 0, 50)")).toBeUndefined()
})
})

View File

@ -23,8 +23,8 @@
let codeEditor
let flattenedPanel = flattenComponents(panelStructure.categories)
let categories = [
{ value: "design", name: "Design" },
{ value: "settings", name: "Settings" },
{ value: "design", name: "Design" },
{ value: "events", name: "Events" },
]
let selectedCategory = categories[0]

View File

@ -61,8 +61,9 @@
<style>
.panel {
padding: 20px 0px;
display: flex;
flex-wrap: wrap;
margin-top: 20px;
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 20px;
}
</style>

View File

@ -6,6 +6,7 @@
import { pipe } from "components/common/core"
import { store } from "builderStore"
import { ArrowDownIcon, ShapeIcon } from "components/common/Icons/"
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
export let screens = []
@ -31,7 +32,7 @@
<div class="root">
{#each screens as screen}
<div
class="budibase__nav-item component"
class="budibase__nav-item screen-header-row"
class:selected={$store.currentComponentInfo._id === screen.props._id}
on:click|stopPropagation={() => changeScreen(screen)}>
@ -46,6 +47,10 @@
<i class="ri-artboard-2-fill icon" />
<span class="title">{screen.props._instanceName}</span>
<div class="dropdown-menu">
<ScreenDropdownMenu {screen} />
</div>
</div>
{#if $store.currentPreviewItem.props._instanceName && $store.currentPreviewItem.props._instanceName === screen.props._instanceName && screen.props._children}
@ -63,10 +68,16 @@
color: var(--ink);
}
.screen-header-row {
display: flex;
flex-direction: row;
}
.title {
margin-left: 14px;
font-size: 14px;
font-weight: 400;
flex: 1;
}
.icon {
@ -89,4 +100,20 @@
.rotate :global(svg) {
transform: rotate(-90deg);
}
.dropdown-menu {
display: none;
height: 24px;
width: 24px;
color: var(--ink);
padding: 0px 5px;
border-style: none;
background: rgba(0, 0, 0, 0);
cursor: pointer;
position: relative;
}
.budibase__nav-item:hover .dropdown-menu {
display: block;
}
</style>

View File

@ -42,7 +42,7 @@
class:selected={currentComponent === component}
style="padding-left: {level * 20 + 40}px">
<div class="nav-item">
<i class="icon ri-arrow-right-circle-fill" />
<i class="icon ri-arrow-right-circle-line" />
{isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
</div>
<div class="actions">
@ -73,7 +73,7 @@
grid-template-columns: 1fr auto auto auto;
padding: 0px 5px 0px 15px;
margin: auto 0px;
border-radius: 3px;
border-radius: 5px;
height: 36px;
align-items: center;
}

View File

@ -21,21 +21,17 @@
display: flex;
flex-direction: column;
cursor: pointer;
margin-bottom: 8px;
padding: 8px 0px 16px 0px;
width: 110px;
padding: 12px 16px 16px 16px;
height: 80px;
justify-content: center;
align-items: center;
margin-right: 8px;
background-color: var(--grey-1);
border-radius: 3px;
border-radius: 5px;
}
.item-item:hover {
background: var(--grey-2);
border-radius: 3px;
transition: all 0.2s;
transition: all 0.3s;
}
.item-icon {
@ -51,6 +47,7 @@
.item-name {
font-size: 14px;
font-weight: 400;
text-align: center;
}
i {

View File

@ -23,21 +23,26 @@
{#if !list.isCategory}
<button class="back-button" on:click={() => (list = category)}>Back</button>
{/if}
{#each list.children as item}
<Item {item} on:click={() => handleClick(item)} />
{/each}
<style>
.back-button {
font-size: 16px;
width: 100%;
grid-column: 1 / span 2;
font-size: 14px;
text-align: center;
height: 40px;
border-radius: 3px;
border: solid 1px #e8e8ef;
height: 36px;
border-radius: 5px;
border: solid 1px var(--grey-3);
background: white;
margin-bottom: 20px;
cursor: pointer;
font-weight: 500;
font-family: Inter;
transition: all 0.3ms;
}
.back-button:hover {
background: var(--grey-1);
}
</style>

View File

@ -82,6 +82,7 @@
function handleClick(val) {
value = val
onChange(value)
toggleSelect(false)
}
$: menuStyle = buildStyle({
@ -110,7 +111,7 @@
bind:this={select}
class="bb-select-container"
on:click={() => toggleSelect(!open)}>
<div bind:this={selectAnchor} class="bb-select-anchor selected">
<div bind:this={selectAnchor} title={value} class="bb-select-anchor selected">
<span>{displayLabel}</span>
<i bind:this={icon} class="ri-arrow-down-s-fill" />
</div>
@ -176,6 +177,7 @@
background-color: var(--grey-2);
border-radius: 5px;
align-items: center;
white-space: nowrap;
}
.bb-select-anchor > span {

View File

@ -0,0 +1,112 @@
<script>
import { MoreIcon } from "components/common/Icons"
import { store } from "builderStore"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import UIkit from "uikit"
import api from "builderStore/api"
import Portal from "svelte-portal"
import { DropdownMenu } from "@budibase/bbui"
export let screen
let confirmDeleteDialog
let dropdown
let buttonForDropdown
const hideDropdown = () => {
dropdown.hide()
}
const deleteScreen = () => {
store.update(s => {
const screens = s.screens.filter(c => c.name !== screen.name)
s.screens = screens
if (s.currentPreviewItem.name === screen.name) {
s.currentPreviewItem = s.pages[s.currentPageName]
s.currentFrontEndType = "page"
}
api.delete(
`/_builder/api/pages/${s.currentPageName}/screens/${screen.name}`
)
return s
})
}
</script>
<div class="root boundary" on:click|stopPropagation={() => {}}>
<button on:click={() => dropdown.show()} bind:this={buttonForDropdown}>
<MoreIcon />
</button>
<DropdownMenu bind:this={dropdown} anchor={buttonForDropdown}>
<ul class="menu" on:click={hideDropdown}>
<li class="item" on:click={() => confirmDeleteDialog.show()}>
<i class="icon ri-delete-bin-2-line" />
Delete
</li>
</ul>
</DropdownMenu>
</div>
<ConfirmDialog
bind:this={confirmDeleteDialog}
title="Confirm Delete"
body={`Are you sure you wish to delete the screen '${screen.props._instanceName}' ?`}
okText="Delete Screen"
onOk={deleteScreen} />
<style>
.root {
overflow: hidden;
z-index: 9;
}
.root button {
border-style: none;
border-radius: 2px;
padding: 5px;
background: transparent;
cursor: pointer;
color: var(--ink);
outline: none;
}
.menu {
z-index: 100000;
overflow: visible;
padding: 12px 0px;
border-radius: 5px;
margin: 0;
}
.menu li {
border-style: none;
background-color: transparent;
list-style-type: none;
padding: 4px 16px;
margin: 0;
width: 100%;
box-sizing: border-box;
}
.item {
display: flex;
align-items: center;
font-size: 14px;
}
.icon {
margin-right: 8px;
}
.menu li:not(.disabled) {
cursor: pointer;
color: var(--grey-7);
}
.menu li:not(.disabled):hover {
color: var(--ink);
background-color: var(--grey-1);
}
</style>

View File

@ -2,7 +2,6 @@
import PropertyControl from "./PropertyControl.svelte"
import InputGroup from "../common/Inputs/InputGroup.svelte"
import Input from "../common/Input.svelte"
import Colorpicker from "../common/Colorpicker.svelte"
import { goto } from "@sveltech/routify"
import { excludeProps } from "./propertyCategories.js"

View File

@ -2,7 +2,6 @@
import { backendUiStore } from "builderStore"
import IconButton from "../common/IconButton.svelte"
import Input from "../common/Input.svelte"
import Colorpicker from "../common/Colorpicker.svelte"
export let value = ""
export let onChanged = () => {}

View File

@ -1,7 +1,7 @@
import Input from "../common/Input.svelte"
import OptionSelect from "./OptionSelect.svelte"
import FlatButtonGroup from "./FlatButtonGroup.svelte"
import Colorpicker from "./Colorpicker"
import Colorpicker from "@budibase/colorpicker"
/*
TODO: Allow for default values for all properties
*/

View File

@ -11,23 +11,11 @@ export default {
name: "Basic",
isCategory: true,
children: [
{
_component: "@budibase/standard-components/embed",
icon: "ri-code-line",
name: "Embed",
description: "Embed content from 3rd party sources",
properties: {
design: {
...all,
},
settings: [{ label: "Embed", key: "embed", control: Input }],
},
},
{
_component: "@budibase/standard-components/container",
name: "Container",
description: "This component contains things within itself",
icon: "ri-layout-row-fill",
icon: "ri-layout-row-line",
commonProps: {},
children: [],
properties: {
@ -56,10 +44,22 @@ export default {
],
},
},
{
_component: "@budibase/standard-components/embed",
icon: "ri-code-line",
name: "Embed",
description: "Embed content from 3rd party sources",
properties: {
design: {
...all,
},
settings: [{ label: "Embed", key: "embed", control: Input }],
},
},
{
name: "Text",
description: "This is a simple text component",
icon: "ri-t-box-fill",
icon: "ri-t-box-line",
commonProps: {},
children: [
{
@ -123,7 +123,7 @@ export default {
{
name: "Input",
description: "These components handle user input.",
icon: "ri-edit-box-fill",
icon: "ri-edit-box-line",
commonProps: {},
children: [
{
@ -131,7 +131,7 @@ export default {
name: "Textfield",
description:
"A textfield component that allows the user to input text.",
icon: "ri-edit-box-fill",
icon: "ri-edit-box-line",
properties: {
design: { ...all },
settings: [
@ -149,7 +149,7 @@ export default {
_component: "@budibase/standard-components/checkbox",
name: "Checkbox",
description: "A selectable checkbox component",
icon: "ri-checkbox-fill",
icon: "ri-checkbox-line",
properties: {
design: { ...all },
settings: [{ label: "Label", key: "label", control: Input }],
@ -170,7 +170,7 @@ export default {
name: "Select",
description:
"A select component for choosing from different options",
icon: "ri-file-list-fill",
icon: "ri-file-list-line",
properties: {
design: { ...all },
settings: [],
@ -182,7 +182,7 @@ export default {
_component: "@budibase/standard-components/button",
name: "Button",
description: "A basic html button that is ready for styling",
icon: "ri-radio-button-fill",
icon: "ri-share-box-line",
children: [],
properties: {
design: {
@ -203,23 +203,23 @@ export default {
_component: "@budibase/standard-components/image",
name: "Image",
description: "A basic component for displaying images",
icon: "ri-image-fill",
icon: "ri-image-line",
children: [],
properties: {
design: { ...all },
settings: [{ label: "URL", key: "url", control: Input }],
},
},
{
_component: "@budibase/standard-components/icon",
name: "Icon",
description: "A basic component for displaying icons",
icon: "ri-sun-fill",
children: [],
properties: {
design: { ...all },
},
},
// {
// _component: "@budibase/standard-components/icon",
// name: "Icon",
// description: "A basic component for displaying icons",
// icon: "ri-sun-fill",
// children: [],
// properties: {
// design: { ...all },
// },
// },
{
_component: "@budibase/standard-components/link",
name: "Link",
@ -251,7 +251,7 @@ export default {
name: "Card",
description:
"A basic card component that can contain content and actions.",
icon: "ri-layout-bottom-fill",
icon: "ri-layout-bottom-line",
children: [],
properties: {
design: { ...all },
@ -283,34 +283,11 @@ export default {
],
},
},
{
name: "Login",
_component: "@budibase/standard-components/login",
description:
"A component that automatically generates a login screen for your app.",
icon: "ri-login-box-fill",
children: [],
properties: {
design: { ...all },
settings: [
{
label: "Name",
key: "name",
control: Input,
},
{
label: "Logo",
key: "logo",
control: Input,
},
],
},
},
{
name: "Table",
_component: "@budibase/standard-components/datatable",
description: "A component that generates a table from your data.",
icon: "ri-archive-drawer-fill",
icon: "ri-archive-drawer-line",
properties: {
design: { ...all },
settings: [
@ -319,6 +296,7 @@ export default {
{ label: "Border Color", key: "borderColor", control: Input },
{ label: "TH Color", key: "backgroundColor", control: Input },
{ label: "TH Font Color", key: "color", control: Input },
{ label: "Table", key: "model", control: ModelSelect }
],
},
children: [],
@ -326,17 +304,31 @@ export default {
{
name: "Form",
description: "A component that generates a form from your data.",
icon: "ri-file-edit-fill",
icon: "ri-file-edit-line",
commonProps: {},
children: [
{
_component: "@budibase/standard-components/dataform",
name: "Form Basic",
icon: "ri-file-edit-fill",
icon: "ri-file-edit-line",
properties: {
design: { ...all },
settings: [
{ label: "Model", key: "model", control: ModelSelect },
{
label: "Table",
key: "model",
control: ModelSelect,
},
{
label: "Title",
key: "title",
control: Input,
},
{
label: "Button Text",
key: "buttonText",
control: Input,
},
],
},
template: {
@ -348,15 +340,25 @@ export default {
{
_component: "@budibase/standard-components/dataformwide",
name: "Form Wide",
icon: "ri-file-edit-fill",
icon: "ri-file-edit-line",
properties: {
design: { ...all },
settings: [
{
label: "Model",
label: "Table",
key: "model",
control: ModelSelect,
},
{
label: "Title",
key: "title",
control: Input,
},
{
label: "Button Text",
key: "buttonText",
control: Input,
},
],
},
},
@ -366,11 +368,11 @@ export default {
name: "Chart",
_component: "@budibase/standard-components/datachart",
description: "Shiny chart",
icon: "ri-bar-chart-fill",
icon: "ri-bar-chart-line",
properties: {
design: { ...all },
settings: [
{ label: "Model", key: "model", control: ModelSelect },
{ label: "Table", key: "model", control: ModelSelect },
{
label: "Chart Type",
key: "type",
@ -394,36 +396,48 @@ export default {
},
children: [],
},
{
name: "Data List",
_component: "@budibase/standard-components/datalist",
description: "Shiny list",
icon: "ri-file-list-fill",
properties: {
design: { ...all },
settings: [{ label: "Model", key: "model", control: ModelSelect }],
},
children: [],
},
// {
// name: "Data List",
// _component: "@budibase/standard-components/datalist",
// description: "Shiny list",
// icon: "ri-file-list-line",
// properties: {
// design: { ...all },
// settings: [{ label: "Table", key: "model", control: ModelSelect }],
// },
// children: [],
// },
{
name: "List",
_component: "@budibase/standard-components/list",
description: "Shiny list",
icon: "ri-file-list-fill",
description: "Renders all children once per record, of a given table",
icon: "ri-file-list-line",
properties: {
design: { ...all },
settings: [{ label: "Model", key: "model", control: ModelSelect }],
settings: [{ label: "Table", key: "model", control: ModelSelect }],
},
children: [],
},
{
name: "Map",
_component: "@budibase/standard-components/datamap",
description: "Shiny map",
icon: "ri-map-pin-fill",
properties: { design: { ...all } },
name: "Record Detail",
_component: "@budibase/standard-components/recorddetail",
description:
"Loads a record, using an id from the URL, which can be used with {{ context }}, in children",
icon: "ri-profile-line",
properties: {
design: { ...all },
settings: [{ label: "Table", key: "model", control: ModelSelect }],
},
children: [],
},
// {
// name: "Map",
// _component: "@budibase/standard-components/datamap",
// description: "Shiny map",
// icon: "ri-map-pin-line",
// properties: { design: { ...all } },
// children: [],
// },
],
},
{
@ -432,10 +446,10 @@ export default {
children: [
{
_component: "##builtin/screenslot",
name: "Screenslot",
name: "Screen Slot",
description:
"This component is a placeholder for the rendering of a screen within a page.",
icon: "ri-crop-2-fill",
icon: "ri-crop-2-line",
properties: { design: { ...all } },
commonProps: {},
children: [],
@ -445,7 +459,7 @@ export default {
_component: "@budibase/standard-components/Navigation",
description:
"A component for handling the navigation within your app.",
icon: "ri-navigation-fill",
icon: "ri-navigation-line",
children: [],
properties: {
design: { ...all },
@ -457,6 +471,39 @@ export default {
],
},
},
{
name: "Login",
_component: "@budibase/standard-components/login",
description:
"A component that automatically generates a login screen for your app.",
icon: "ri-login-box-line",
children: [],
properties: {
design: { ...all },
settings: [
{
label: "Name",
key: "name",
control: Input,
},
{
label: "Logo",
key: "logo",
control: Input,
},
{
label: "Title",
key: "title",
control: Input,
},
{
label: "Button Text",
key: "buttonText",
control: Input,
},
],
},
},
],
},
],

View File

@ -28,7 +28,7 @@
</script>
<section>
<Button purple wide on:click{newWorkflow}>Create New Workflow</Button>
<Button purple wide on:click={newWorkflow}>Create New Workflow</Button>
<ul>
{#each $workflowStore.workflows as workflow}
<li
@ -91,7 +91,7 @@
}
.workflow-item.selected {
background: var(--blue-light);
background: var(--grey-2);
}
.new-workflow-button {

View File

@ -6,7 +6,7 @@ export const FIELDS = {
constraints: {
type: "string",
length: {},
presence: false,
presence: { allowEmpty: true },
},
},
NUMBER: {
@ -15,7 +15,7 @@ export const FIELDS = {
type: "number",
constraints: {
type: "number",
presence: false,
presence: { allowEmpty: true },
numericality: {},
},
},
@ -25,7 +25,7 @@ export const FIELDS = {
type: "boolean",
constraints: {
type: "boolean",
presence: false,
presence: { allowEmpty: true },
},
},
// OPTIONS: {
@ -34,7 +34,7 @@ export const FIELDS = {
// type: "options",
// constraints: {
// type: "string",
// presence: false,
// presence: { allowEmpty: true },
// },
// },
DATETIME: {
@ -44,7 +44,7 @@ export const FIELDS = {
constraints: {
type: "string",
length: {},
presence: false,
presence: { allowEmpty: true },
},
},
// IMAGE: {
@ -53,7 +53,7 @@ export const FIELDS = {
// type: "file",
// constraints: {
// type: "string",
// presence: false,
// presence: { allowEmpty: true },
// },
// },
// FILE: {
@ -62,11 +62,11 @@ export const FIELDS = {
// type: "file",
// constraints: {
// type: "string",
// presence: false,
// presence: { allowEmpty: true },
// },
// },
DATA_LINK: {
name: "Data Links",
LINKED_FIELDS: {
name: "Linked Fields",
icon: "ri-link",
type: "link",
modelId: null,
@ -84,16 +84,46 @@ export const BLOCKS = {
constraints: {
type: "string",
length: {},
presence: false,
presence: { allowEmpty: true },
},
},
COMPANY: {
name: "Company",
icon: "ri-store-line",
type: "string",
constraints: {
type: "string",
length: {},
presence: { allowEmpty: true },
},
},
EMAIL: {
name: "Email",
icon: "ri-mail-line",
type: "string",
constraints: {
type: "string",
length: {},
presence: { allowEmpty: true },
},
},
PHONE_NUMBER: {
name: "Phone Number",
icon: "ri-number-1",
name: "Phone No.",
icon: "ri-phone-line",
type: "number",
constraints: {
type: "number",
presence: false,
presence: { allowEmpty: true },
numericality: {},
},
},
VALUE: {
name: "Value",
icon: "ri-number-5",
type: "number",
constraints: {
type: "number",
presence: { allowEmpty: true },
numericality: {},
},
},
@ -103,7 +133,27 @@ export const BLOCKS = {
type: "boolean",
constraints: {
type: "boolean",
presence: false,
presence: { allowEmpty: true },
},
},
URL: {
name: "URL",
icon: "ri-link",
type: "string",
constraints: {
type: "string",
length: {},
presence: { allowEmpty: true },
},
},
IMAGE: {
name: "Image URL",
icon: "ri-image-line",
type: "string",
constraints: {
type: "string",
length: {},
presence: { allowEmpty: true },
},
},
// PRIORITY: {
@ -112,7 +162,7 @@ export const BLOCKS = {
// type: "options",
// constraints: {
// type: "string",
// presence: false,
// presence: { allowEmpty: true },
// inclusion: ["low", "medium", "high"],
// },
// },
@ -123,7 +173,7 @@ export const BLOCKS = {
constraints: {
type: "string",
length: {},
presence: false,
presence: { allowEmpty: true },
},
},
// AVATAR: {
@ -132,7 +182,7 @@ export const BLOCKS = {
// type: "image",
// constraints: {
// type: "string",
// presence: false,
// presence: { allowEmpty: true },
// },
// },
// PDF: {
@ -141,7 +191,7 @@ export const BLOCKS = {
// type: "file",
// constraints: {
// type: "string",
// presence: false,
// presence: { allowEmpty: true },
// },
// },
}

View File

@ -1,6 +1,6 @@
<script>
import Modal from "svelte-simple-modal"
import { store, workflowStore } from "builderStore"
import { store, workflowStore, backendUiStore } from "builderStore"
import SettingsLink from "components/settings/Link.svelte"
import { get } from "builderStore/api"
@ -20,6 +20,7 @@
const pkg = await res.json()
if (res.ok) {
backendUiStore.actions.reset()
await store.setPackage(pkg)
workflowStore.actions.fetch()
return pkg

View File

@ -22,7 +22,8 @@
<style>
.root {
height: 100%;
display: flex;
display: grid;
grid-template-columns: 300px minmax(0, 1fr) 300px;
background: var(--grey-1);
line-height: 1;
}

View File

@ -23,12 +23,10 @@
}
</script>
{#if selectedModel.schema && Object.keys(selectedModel.schema).length === 0}
<EmptyModel />
{:else if $backendUiStore.selectedDatabase._id && selectedModel.name}
{#if $backendUiStore.selectedDatabase._id && selectedModel.name}
<ModelDataTable />
{:else}
<i style="color: var(--grey-4)">create your first model to start building</i>
<i style="color: var(--grey-4)">create your first table to start building</i>
{/if}
<style>

View File

@ -22,5 +22,5 @@
</script>
{#if $backendUiStore.models.length === 0}
Please create a model
{:else}Please select a model{/if}
Please create a table
{:else}Please select a table{/if}

View File

@ -0,0 +1,2 @@
<!-- routify:options index=4 -->
<slot />

View File

@ -0,0 +1,90 @@
<script>
import { Button } from "@budibase/bbui"
import { store } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import api from "builderStore/api"
import Spinner from "components/common/Spinner.svelte"
import analytics from "../../../analytics"
let deployed = false
let loading = false
$: appId = $store.appId
async function deployApp() {
loading = true
const DEPLOY_URL = `/deploy`
try {
notifier.info("Starting Deployment..")
const response = await api.post(DEPLOY_URL)
const json = await response.json()
if (response.status !== 200) {
throw new Error()
}
notifier.success(`Your Deployment is Complete.`)
deployed = true
loading = false
analytics.captureEvent("web_app_deployment", {
appId,
})
} catch (err) {
analytics.captureException(err)
notifier.danger("Deployment unsuccessful. Please try again later.")
loading = false
}
}
</script>
<section>
<div>
<h4>It's time to shine!</h4>
{#if deployed}
<a target="_blank" href={`https://${appId}.app.budi.live/${appId}`}>
View App
</a>
{:else}
<Button secondary medium on:click={deployApp}>
Deploy App
{#if loading}
<Spinner ratio={'0.5'} />
{/if}
</Button>
{/if}
</div>
<img src="/_builder/assets/deploy-rocket.jpg" />
</section>
<style>
img {
width: 100%;
height: 100%;
}
h4 {
color: var(--white);
font-size: 18px;
font-weight: bold;
margin-bottom: 30px;
}
section {
position: relative;
}
div {
position: absolute;
display: flex;
text-align: center;
flex-direction: column;
align-items: center;
justify-content: center;
left: 0;
right: 0;
top: 20%;
margin-left: auto;
margin-right: auto;
width: 50%;
}
</style>

View File

@ -14,7 +14,9 @@
if ($leftover) {
// Get the correct screen children.
const screenChildren = $store.pages[$params.page]._screens.find(
screen => screen.props._instanceName === $params.screen
screen =>
screen.props._instanceName === $params.screen ||
screen.props._instanceName === decodeURIComponent($params.screen)
).props._children
findComponent(componentIds, screenChildren)
}
@ -36,15 +38,20 @@
// Loop through each ID
ids.forEach(id => {
// Find ID and select it
componentToSelect = currentChildren.find(child => child._id === id)
// Find ID
const component = currentChildren.find(child => child._id === id)
// If it does not exist, ignore (use last valid route)
if (!component) return
componentToSelect = component
// Update childrens array to selected components children
currentChildren = componentToSelect._children
})
// Select Component!
store.selectComponent(componentToSelect)
if (componentToSelect) store.selectComponent(componentToSelect)
}
</script>

View File

@ -27,7 +27,8 @@
.root {
height: 100%;
display: flex;
display: grid;
grid-template-columns: 300px minmax(0, 1fr) 300px;
background: var(--grey-1);
line-height: 1;
}

View File

@ -26,27 +26,39 @@
<div class="nav-section">
<div class="nav-section-title">Build</div>
<Link icon={AppsIcon} title="Apps" href="/" active />
<Link icon={SettingsIcon} title="Settings" href="/" />
<Link icon={UpdatesIcon} title="Updates" href="/" />
<Link icon={HostingIcon} title="Hosting" href="/" />
<Link
icon={HostingIcon}
title="Hosting"
href="https://portal.budi.live/" />
</div>
<div class="nav-section">
<div class="nav-section-title">Learn</div>
<Link icon={DocumentationIcon} title="Documentation" href="/" />
<Link icon={TutorialsIcon} title="Tutorials" href="/" />
<Link icon={CommunityIcon} title="Community" href="/" />
<Link
icon={DocumentationIcon}
title="Documentation"
href="https://docs.budibase.com/" />
<Link
icon={CommunityIcon}
title="Community"
href="https://forum.budibase.com/" />
</div>
<div class="nav-section">
<div class="nav-section-title">Contact</div>
<Link
icon={ContributionIcon}
title="Contribute to our product"
href="/" />
<Link icon={BugIcon} title="Report bug" href="/" />
<Link icon={EmailIcon} title="Email" href="/" />
<Link icon={TwitterIcon} title="Twitter" href="/" />
title="Contribute"
href="https://github.com/Budibase/budibase" />
<Link
icon={BugIcon}
title="Report bug"
href="https://github.com/Budibase/budibase/issues" />
<Link icon={EmailIcon} title="Email" href="mailto:hi@budibase.com" />
<Link
icon={TwitterIcon}
title="Twitter"
href="https://twitter.com/budibase" />
</div>
</div>
@ -59,7 +71,7 @@
<style>
.root {
display: grid;
grid-template-columns: 300px 1fr;
grid-template-columns: 260px 1fr;
height: 100%;
width: 100%;
background: var(--grey-1);
@ -67,6 +79,7 @@
.main {
grid-column: 2;
overflow: auto;
}
.ui-nav {
@ -75,7 +88,6 @@
padding: 20px;
display: flex;
flex-direction: column;
border-right: 1px solid var(--grey-4);
}
.home-logo {

View File

@ -8,6 +8,7 @@
import { get } from "builderStore/api"
import Spinner from "components/common/Spinner.svelte"
import CreateAppModal from "components/start/CreateAppModal.svelte"
import { Button } from "@budibase/bbui"
let promise = getApps()
@ -43,21 +44,17 @@
}
</script>
<div class="welcome">Welcome to Budibase</div>
<div class="header">
<div class="welcome">Welcome to the Budibase Beta</div>
<Button purple large on:click={showCreateAppModal}>Create New Web App</Button>
</div>
<div class="banner">
<img src="/_builder/assets/rocket.jpg" alt="rocket" />
<img src="/_builder/assets/orange-landscape.png" alt="rocket" />
<div class="banner-content">
Every accomplishment starts with a decision to try.
</div>
</div>
<div class="app-section-header">
<div class="app-section-title">Your Web Apps</div>
<button class="banner-button" type="button" on:click={showCreateAppModal}>
<i class="ri-add-circle-fill" />
Create New Web App
</button>
</div>
{#await promise}
<div class="spinner-container">
@ -70,11 +67,17 @@
{/await}
<style>
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 40px 80px 0px 80px;
}
.welcome {
font-size: 42px;
color: var(--ink);
font-weight: 700;
margin: 40px 0px 0px 80px;
}
.banner {
@ -108,48 +111,4 @@
align-items: center;
justify-content: center;
}
.banner-button {
background-color: var(--ink);
color: var(--white);
padding: 12px 24px;
border-radius: 5px;
border: var(--ink) 1px solid;
font-size: 16px;
font-weight: 400;
box-sizing: border-box;
align-items: center;
display: flex;
cursor: pointer;
transition: all 0.2s ease 0s;
overflow: hidden;
outline: none;
user-select: none;
white-space: nowrap;
}
.ri-add-circle-fill {
margin-right: 4px;
font-size: 24px;
}
.banner-button:hover {
background-color: var(--white);
color: var(--ink);
border: var(--grey-4) 1px solid;
}
.app-section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 40px 80px 0px 80px;
}
.app-section-title {
font-size: 20px;
color: var(--ink);
font-weight: 600;
margin-bottom: 20px;
}
</style>

View File

@ -1,6 +1,6 @@
{
"name": "budibase",
"version": "0.0.32",
"version": "0.1.12",
"description": "Budibase CLI",
"repository": "https://github.com/Budibase/Budibase",
"homepage": "https://www.budibase.com",
@ -17,7 +17,7 @@
"author": "Budibase",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@budibase/server": "^0.0.32",
"@budibase/server": "^0.1.12",
"@inquirer/password": "^0.0.6-alpha.0",
"chalk": "^2.4.2",
"dotenv": "^8.2.0",
@ -29,5 +29,5 @@
"uuid": "^7.0.3",
"yargs": "^14.2.0"
},
"gitHead": "b1f4f90927d9e494e513220ef060af28d2d42455"
"gitHead": "eff4fa93ca1db11b97b5fdedc0c488413e277eb8"
}

View File

@ -1,88 +1,7 @@
const inquirer = require("inquirer")
const { exists, readFile, writeFile, ensureDir } = require("fs-extra")
const chalk = require("chalk")
const { serverFileName, xPlatHomeDir } = require("../../common")
const { join } = require("path")
const Sqrl = require("squirrelly")
const uuid = require("uuid")
const { xPlatHomeDir } = require("../../common")
const initialiseBudibase = require("@budibase/server/src/utilities/initialiseBudibase")
module.exports = opts => {
return run(opts)
}
const run = async opts => {
try {
await ensureAppDir(opts)
await setEnvironmentVariables(opts)
await createClientDatabase(opts)
await createDevEnvFile(opts)
console.log(chalk.green("Budibase successfully initialised."))
} catch (error) {
console.error(`Error initialising Budibase: ${error.message}`)
}
}
const setEnvironmentVariables = async opts => {
if (opts.couchDbUrl) {
process.env.COUCH_DB_URL = opts.couchDbUrl
} else {
const dataDir = join(opts.dir, ".data")
await ensureDir(dataDir)
process.env.COUCH_DB_URL =
dataDir + (dataDir.endsWith("/") || dataDir.endsWith("\\") ? "" : "/")
}
}
const ensureAppDir = async opts => {
opts.dir = xPlatHomeDir(opts.dir)
await ensureDir(opts.dir)
}
const createClientDatabase = async opts => {
// cannot be a top level require as it
// will cause environment module to be loaded prematurely
const clientDb = require("@budibase/server/src/db/clientDb")
if (opts.clientId === "new") {
// cannot be a top level require as it
// will cause environment module to be loaded prematurely
const CouchDB = require("@budibase/server/src/db/client")
const existing = await CouchDB.allDbs()
let i = 0
let isExisting = true
while (isExisting) {
i += 1
opts.clientId = i.toString()
isExisting = existing.includes(clientDb.name(opts.clientId))
}
}
await clientDb.create(opts.clientId)
}
const createDevEnvFile = async opts => {
const destConfigFile = join(opts.dir, "./.env")
let createConfig = !(await exists(destConfigFile)) || opts.quiet
if (!createConfig) {
const answers = await inquirer.prompt([
{
type: "input",
name: "overwrite",
message: ".env already exists - overwrite? (N/y)",
},
])
createConfig = ["Y", "y", "yes"].includes(answers.overwrite)
}
if (createConfig) {
const template = await readFile(serverFileName(".env.template"), {
encoding: "utf8",
})
opts.adminSecret = uuid.v4()
opts.cookieKey1 = uuid.v4()
opts.cookieKey2 = uuid.v4()
const config = Sqrl.Render(template, opts)
await writeFile(destConfigFile, config, { flag: "w+" })
}
return initialiseBudibase(opts)
}

View File

@ -16,8 +16,10 @@ const run = async opts => {
await createEmptyAppPackage(opts)
exec(`cd ${join(opts.dir, opts.applicationId)} && npm install`)
console.log(chalk.green(`Budibase app ${opts.name} created!`))
process.exit()
} catch (error) {
console.error(chalk.red("Error creating new app", error))
process.exit(1)
}
}

View File

@ -1,14 +1,14 @@
const { xPlatHomeDir } = require("../../common")
const { resolve } = require("path")
module.exports = ({ dir }) => {
module.exports = async ({ dir }) => {
dir = xPlatHomeDir(dir)
process.env.BUDIBASE_DIR = resolve(dir)
require("dotenv").config({ path: resolve(dir, ".env") })
// dont make this a variable or top level require
// ti will cause environment module to be loaded prematurely
require("@budibase/server/src/app")().then(server => {
// it will cause environment module to be loaded prematurely
return require("@budibase/server/src/app")().then(server => {
server.on("close", () => console.log("Server Closed"))
console.log(`Budibase running on ${JSON.stringify(server.address())}`)
})

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/client",
"version": "0.0.32",
"version": "0.1.1",
"license": "MPL-2.0",
"main": "dist/budibase-client.js",
"module": "dist/budibase-client.esm.mjs",
@ -60,5 +60,5 @@
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^4.0.4"
},
"gitHead": "b1f4f90927d9e494e513220ef060af28d2d42455"
"gitHead": "e4e053cb6ff9a0ddc7115b44ccaa24b8ec41fb9a"
}

View File

@ -2,7 +2,7 @@ import { attachChildren } from "./render/attachChildren"
import { createTreeNode } from "./render/prepareRenderComponent"
import { screenRouter } from "./render/screenRouter"
import { createStateManager } from "./state/stateManager"
import { getAppId } from "./render/getAppId"
import { parseAppIdFromCookie } from "./render/getAppId"
export const createApp = ({
componentLibraries,
@ -38,7 +38,7 @@ export const createApp = ({
window,
})
const fallbackPath = window.location.pathname.replace(
getAppId(window.document.cookie),
parseAppIdFromCookie(window.document.cookie),
""
)
routeTo(currentUrl || fallbackPath)

View File

@ -1,6 +1,6 @@
import { createApp } from "./createApp"
import { builtins, builtinLibName } from "./render/builtinComponents"
import { getAppId } from "./render/getAppId"
import { parseAppIdFromCookie } from "./render/getAppId"
/**
* create a web application from static budibase definition files.
@ -9,7 +9,7 @@ import { getAppId } from "./render/getAppId"
export const loadBudibase = async opts => {
const _window = (opts && opts.window) || window
// const _localStorage = (opts && opts.localStorage) || localStorage
const appId = getAppId(_window.document.cookie)
const appId = parseAppIdFromCookie(_window.document.cookie)
const frontendDefinition = _window["##BUDIBASE_FRONTEND_DEFINITION##"]
const user = {}

View File

@ -30,34 +30,33 @@ export const attachChildren = initialiseOpts => (htmlElement, options) => {
}
}
const contextArray = Array.isArray(context) ? context : [context]
const childNodes = []
for (let childProps of treeNode.props._children) {
const { componentName, libName } = splitName(childProps._component)
if (!componentName || !libName) return
for (let context of contextArray) {
for (let childProps of treeNode.props._children) {
const { componentName, libName } = splitName(childProps._component)
const ComponentConstructor = componentLibraries[libName][componentName]
if (!componentName || !libName) return
const prepareNodes = ctx => {
const childNodesThisIteration = prepareRenderComponent({
props: childProps,
parentNode: treeNode,
ComponentConstructor,
htmlElement,
anchor,
context: ctx,
})
const ComponentConstructor = componentLibraries[libName][componentName]
for (let childNode of childNodesThisIteration) {
childNodes.push(childNode)
const prepareNodes = ctx => {
const childNodesThisIteration = prepareRenderComponent({
props: childProps,
parentNode: treeNode,
ComponentConstructor,
htmlElement,
anchor,
context: ctx,
})
for (let childNode of childNodesThisIteration) {
childNodes.push(childNode)
}
}
}
if (Array.isArray(context)) {
for (let singleCtx of context) {
prepareNodes(singleCtx)
}
} else {
prepareNodes(context)
}
}
@ -100,7 +99,10 @@ const areTreeNodesEqual = (children1, children2) => {
let isEqual = false
for (let i = 0; i < children1.length; i++) {
isEqual = deepEqual(children1[i].context, children2[i].context)
// same context and same children, then nothing has changed
isEqual =
deepEqual(children1[i].context, children2[i].context) &&
areTreeNodesEqual(children1[i].children, children2[i].children)
if (!isEqual) return false
if (isScreenSlot(children1[i].parentNode.props._component)) {
isEqual = deepEqual(children1[i].props, children2[i].props)

View File

@ -1,4 +1,4 @@
export const getAppId = docCookie => {
export const parseAppIdFromCookie = docCookie => {
const cookie =
docCookie.split(";").find(c => c.trim().startsWith("budibase:token")) ||
docCookie.split(";").find(c => c.trim().startsWith("builder:token"))

View File

@ -78,13 +78,14 @@ export const createTreeNode = () => ({
get destroy() {
const node = this
return () => {
if (node.unsubscribe) node.unsubscribe()
if (node.component && node.component.$destroy) node.component.$destroy()
if (node.children) {
// destroy children first - from leaf nodes up
for (let child of node.children) {
child.destroy()
}
}
if (node.unsubscribe) node.unsubscribe()
if (node.component && node.component.$destroy) node.component.$destroy()
for (let onDestroyItem of node.onDestroy) {
onDestroyItem()
}

View File

@ -1,6 +1,6 @@
import regexparam from "regexparam"
import { routerStore } from "../state/store"
import { getAppId } from "./getAppId"
import { appStore } from "../state/store"
import { parseAppIdFromCookie } from "./getAppId"
export const screenRouter = ({ screens, onScreenSelected, window }) => {
const makeRootedPath = url => {
@ -9,7 +9,7 @@ export const screenRouter = ({ screens, onScreenSelected, window }) => {
(window.location.hostname === "localhost" ||
window.location.hostname === "127.0.0.1")
) {
const appId = getAppId(window.document.cookie)
const appId = parseAppIdFromCookie(window.document.cookie)
if (url) {
if (url.startsWith(appId)) return url
return `/${appId}${url.startsWith("/") ? "" : "/"}${url}`
@ -49,20 +49,20 @@ export const screenRouter = ({ screens, onScreenSelected, window }) => {
})
}
routerStore.update(state => {
appStore.update(state => {
state["##routeParams"] = params
return state
})
const screenIndex = current !== -1 ? current : fallback
onScreenSelected(screens[screenIndex], _url)
try {
!url.state && history.pushState(_url, null, _url)
} catch (_) {
// ignoring an exception here as the builder runs an iframe, which does not like this
}
onScreenSelected(screens[screenIndex], _url)
}
function click(e) {

View File

@ -8,6 +8,7 @@ export const bbFactory = ({
store,
componentLibraries,
onScreenSlotRendered,
getCurrentState,
}) => {
const apiCall = method => (url, body) => {
return fetch(url, {
@ -53,6 +54,8 @@ export const bbFactory = ({
store: store,
api,
parent,
// these parameters are populated by screenRouter
routeParams: () => getCurrentState()["##routeParams"],
}
}
}

View File

@ -26,8 +26,14 @@ export const createStateManager = ({
routeTo,
}) => {
let handlerTypes = eventHandlers(routeTo)
let currentState
// creating a reference to the current state
// this avoids doing store.get() ... which is expensive on
// hot paths, according to the svelte docs.
// the state object reference never changes (although it's internals do)
// so this should work fine for us
let currentState
appStore.subscribe(s => (currentState = s))
const getCurrentState = () => currentState
const bb = bbFactory({

View File

@ -12,8 +12,8 @@
"dev:builder": "rollup -cw"
},
"devDependencies": {
"@budibase/client": "^0.0.32",
"@budibase/standard-components": "^0.0.32",
"@budibase/client": "^0.1.1",
"@budibase/standard-components": "^0.1.10",
"@material/button": "^4.0.0",
"@material/checkbox": "^4.0.0",
"@material/data-table": "4.0.0",
@ -50,9 +50,9 @@
"keywords": [
"svelte"
],
"version": "0.0.32",
"version": "0.1.10",
"license": "MIT",
"gitHead": "b1f4f90927d9e494e513220ef060af28d2d42455",
"gitHead": "eff4fa93ca1db11b97b5fdedc0c488413e277eb8",
"dependencies": {
"@material/card": "4.0.0"
}

View File

@ -12,4 +12,8 @@ JWT_SECRET={{cookieKey1}}
PORT=4001
# error level for koa-pino
LOG_LEVEL=error
LOG_LEVEL=error
DEPLOYMENT_CREDENTIALS_URL="https://dt4mpwwap8.execute-api.eu-west-1.amazonaws.com/prod/"
DEPLOYMENT_DB_URL="https://couchdb.budi.live:5984"
SENTRY_DSN=https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131

View File

@ -8,7 +8,7 @@
"type": "node",
"request": "launch",
"name": "Start Server",
"program": "${workspaceFolder}/../cli/bin/budi"
"program": "${workspaceFolder}/src/index.js"
},
{
"type": "node",

View File

@ -2,6 +2,9 @@ FROM node:12-alpine
WORKDIR /app
ENV CLOUD=1
ENV COUCH_DB_URL=https://couchdb.budi.live:5984
# copy files and install dependencies
COPY . ./
RUN yarn

Some files were not shown because too many files have changed in this diff Show More