@@ -98,6 +105,7 @@
}
.spectrum-Modal {
+ background: var(--background);
overflow: visible;
max-height: none;
margin: 40px 0;
@@ -106,4 +114,7 @@
--spectrum-global-dimension-size-100
);
}
+ :global(.spectrum--lightest .spectrum-Modal.inline) {
+ border: var(--border-light);
+ }
diff --git a/packages/bbui/src/Table/CellRenderer.svelte b/packages/bbui/src/Table/CellRenderer.svelte
index d2198fac01..34113202b4 100644
--- a/packages/bbui/src/Table/CellRenderer.svelte
+++ b/packages/bbui/src/Table/CellRenderer.svelte
@@ -23,7 +23,7 @@
}
$: type = schema?.type ?? "string"
$: customRenderer = customRenderers?.find(x => x.column === schema?.name)
- $: renderer = customRenderer?.component ?? typeMap[type]
+ $: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer
{#if renderer && (customRenderer || (value != null && value !== ""))}
diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte
index e74a3a7e7d..048ded2b5b 100644
--- a/packages/bbui/src/Table/Table.svelte
+++ b/packages/bbui/src/Table/Table.svelte
@@ -214,7 +214,7 @@
>
- {#if sortedRows?.length}
+ {#if fields.length}
{#if showEditColumn}
@@ -269,7 +269,7 @@
{/if}
- {#if sortedRows?.length}
+ {#if sortedRows?.length && fields.length}
{#each sortedRows as row, idx}
toggleSelectRow(row)}
@@ -316,15 +316,25 @@
{/each}
{:else}
-
+
+ {#if showEditColumn}
+
+ {/if}
+ {#each fields as field}
+
+ {/each}
+
+
{/if}
@@ -347,7 +357,7 @@
overflow: auto;
}
.container.quiet {
- border: none !important;
+ border: none;
}
table {
width: 100%;
@@ -381,7 +391,7 @@
z-index: 2;
background-color: var(--spectrum-alias-background-color-secondary);
border-bottom: 1px solid
- var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
+ var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
}
.spectrum-Table-headCell-content {
white-space: nowrap;
@@ -396,7 +406,34 @@
text-overflow: ellipsis;
}
+ .placeholder-row {
+ position: relative;
+ height: 150px;
+ }
+ .placeholder-row td {
+ border-top: none !important;
+ border-bottom: none !important;
+ }
+ .placeholder-offset {
+ width: 1px;
+ }
.placeholder {
+ top: 0;
+ height: 100%;
+ left: 0;
+ width: 100%;
+ position: absolute;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ }
+ .placeholder.has-fields {
+ top: var(--header-height);
+ height: calc(100% - var(--header-height));
+ }
+
+ .placeholder-content {
padding: 20px;
display: flex;
flex-direction: column;
@@ -407,12 +444,13 @@
var(--spectrum-alias-text-color)
);
}
- .placeholder div {
+ .placeholder-content div {
margin-top: 10px;
font-size: var(
--spectrum-table-cell-text-size,
var(--spectrum-alias-font-size-default)
);
+ text-align: center;
}
tbody {
@@ -431,17 +469,17 @@
td {
padding-top: 0;
padding-bottom: 0;
- border-bottom: none !important;
+ border-bottom: none;
border-top: 1px solid
- var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
- border-radius: 0 !important;
+ var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
+ border-radius: 0;
}
tr:first-child td {
- border-top: none !important;
+ border-top: none;
}
tr:last-child td {
border-bottom: 1px solid
- var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
+ var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
}
td.spectrum-Table-cell--divider {
width: 1px;
diff --git a/packages/bbui/yarn.lock b/packages/bbui/yarn.lock
index 5dcac027b9..e9bad2e162 100644
--- a/packages/bbui/yarn.lock
+++ b/packages/bbui/yarn.lock
@@ -2407,10 +2407,10 @@ svelte-portal@^1.0.0:
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
-svelte@^3.37.0:
- version "3.37.0"
- resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.37.0.tgz#dc7cd24bcc275cdb3f8c684ada89e50489144ccd"
- integrity sha512-TRF30F4W4+d+Jr2KzUUL1j8Mrpns/WM/WacxYlo5MMb2E5Qy2Pk1Guj6GylxsW9OnKQl1tnF8q3hG/hQ3h6VUA==
+svelte@^3.38.2:
+ version "3.38.2"
+ resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.38.2.tgz#55e5c681f793ae349b5cc2fe58e5782af4275ef5"
+ integrity sha512-q5Dq0/QHh4BLJyEVWGe7Cej5NWs040LWjMbicBGZ+3qpFWJ1YObRmUDZKbbovddLC9WW7THTj3kYbTOFmU9fbg==
svgo@^1.0.0:
version "1.3.2"
diff --git a/packages/builder/package.json b/packages/builder/package.json
index f8b55a4c65..6422d8f686 100644
--- a/packages/builder/package.json
+++ b/packages/builder/package.json
@@ -90,7 +90,7 @@
"@babel/preset-env": "^7.13.12",
"@babel/runtime": "^7.13.10",
"@rollup/plugin-replace": "^2.4.2",
- "@roxi/routify": "2.15.1",
+ "@roxi/routify": "2.18.0",
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.5",
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/svelte": "^3.0.0",
@@ -106,7 +106,7 @@
"rollup": "^2.44.0",
"rollup-plugin-copy": "^3.4.0",
"start-server-and-test": "^1.12.1",
- "svelte": "^3.37.0",
+ "svelte": "^3.38.2",
"svelte-jester": "^1.3.2",
"vite": "^2.1.5"
},
diff --git a/packages/builder/src/actions.js b/packages/builder/src/actions.js
index f465513f21..c39f004af0 100644
--- a/packages/builder/src/actions.js
+++ b/packages/builder/src/actions.js
@@ -1,9 +1,10 @@
export const gradient = (node, config = {}) => {
const defaultConfig = {
- points: 10,
- saturation: 0.8,
- lightness: 0.75,
- softness: 0.8,
+ points: 12,
+ saturation: 0.85,
+ lightness: 0.7,
+ softness: 0.9,
+ seed: null,
}
// Applies a gradient background
@@ -13,42 +14,67 @@ export const gradient = (node, config = {}) => {
...config,
}
const { saturation, lightness, softness, points } = config
+ const seed = config.seed || Math.random().toString(32).substring(2)
- // Generates a random number between min and max
- const rand = (min, max) => {
- return Math.round(min + Math.random() * (max - min))
+ // Hash function which returns a fixed hash between specified limits
+ // for a given seed and a given version
+ const rangeHash = (seed, min = 0, max = 100, version = 0) => {
+ const range = max - min
+ let hash = range + version
+ for (let i = 0; i < seed.length * 2 + version; i++) {
+ hash = (hash << 5) - hash + seed.charCodeAt(i % seed.length)
+ hash = ((hash & hash) % range) + version
+ }
+ return min + (hash % range)
}
// Generates a random HSL colour using the options specified
- const randomHSL = () => {
+ const randomHSL = (seed, version, alpha = 1) => {
const lowerSaturation = Math.min(100, saturation * 100)
const upperSaturation = Math.min(100, (saturation + 0.2) * 100)
const lowerLightness = Math.min(100, lightness * 100)
const upperLightness = Math.min(100, (lightness + 0.2) * 100)
- const hue = rand(0, 360)
- const sat = `${rand(lowerSaturation, upperSaturation)}%`
- const light = `${rand(lowerLightness, upperLightness)}%`
- return `hsl(${hue},${sat},${light})`
+ const hue = rangeHash(seed, 0, 360, version)
+ const sat = `${rangeHash(
+ seed,
+ lowerSaturation,
+ upperSaturation,
+ version
+ )}%`
+ const light = `${rangeHash(
+ seed,
+ lowerLightness,
+ upperLightness,
+ version
+ )}%`
+ return `hsla(${hue},${sat},${light},${alpha})`
}
// Generates a radial gradient stop point
- const randomGradientPoint = () => {
+ const randomGradientPoint = (seed, version) => {
const lowerTransparency = Math.min(100, softness * 100)
const upperTransparency = Math.min(100, (softness + 0.2) * 100)
- const transparency = rand(lowerTransparency, upperTransparency)
+ const transparency = rangeHash(
+ seed,
+ lowerTransparency,
+ upperTransparency,
+ version
+ )
return (
- `radial-gradient(` +
- `at ${rand(10, 90)}% ${rand(10, 90)}%,` +
- `${randomHSL()} 0,` +
+ `radial-gradient(at ` +
+ `${rangeHash(seed, 0, 100, version)}% ` +
+ `${rangeHash(seed, 0, 100, version + 1)}%,` +
+ `${randomHSL(seed, version, saturation)} 0,` +
`transparent ${transparency}%)`
)
}
- let css = `opacity:0.9;background-color:${randomHSL()};background-image:`
+ let css = `opacity:0.9;background:${randomHSL(seed, 0, 0.7)};`
+ css += "background-image:"
for (let i = 0; i < points - 1; i++) {
- css += `${randomGradientPoint()},`
+ css += `${randomGradientPoint(seed, i)},`
}
- css += `${randomGradientPoint()};`
+ css += `${randomGradientPoint(seed, points)};`
node.style = css
}
diff --git a/packages/builder/src/components/common/ConfigChecklist.svelte b/packages/builder/src/components/common/ConfigChecklist.svelte
index 9652222ba2..b784656d23 100644
--- a/packages/builder/src/components/common/ConfigChecklist.svelte
+++ b/packages/builder/src/components/common/ConfigChecklist.svelte
@@ -1,22 +1,12 @@
-
+
{body}
diff --git a/packages/builder/src/components/start/AppCard.svelte b/packages/builder/src/components/start/AppCard.svelte
index 9a14efd81b..60f29cebae 100644
--- a/packages/builder/src/components/start/AppCard.svelte
+++ b/packages/builder/src/components/start/AppCard.svelte
@@ -7,56 +7,49 @@
ActionMenu,
MenuItem,
Link,
- notifications,
} from "@budibase/bbui"
- import download from "downloadjs"
import { gradient } from "actions"
+ import { url } from "@roxi/routify"
- export let name
- export let _id
-
- let appExportLoading = false
-
- async function exportApp() {
- appExportLoading = true
- try {
- download(
- `/api/backups/export?appId=${_id}&appname=${encodeURIComponent(name)}`
- )
- notifications.success("App export complete")
- } catch (err) {
- console.error(err)
- notifications.error("App export failed")
- } finally {
- appExportLoading = false
- }
- }
+ export let app
+ export let exportApp
+ export let deleteApp
-
-
-
-
-
- {name}
-
-
-
-
- Export
-
-
-
-
- Edited {Math.floor(1 + Math.random() * 10)} months ago
-
- {#if Math.random() > 0.5}
-
- {/if}
-
-
+
+
+
+
+
+
+ {app.name}
+
+
+
+
+ exportApp(app)} icon="Download">
+ Export
+
+ deleteApp(app)} icon="Delete">
+ Delete
+
+
+
+
+
+ Edited {Math.floor(1 + Math.random() * 10)} months ago
+
+ {#if Math.random() > 0.5}
+
+ {/if}
+
+
+
diff --git a/packages/builder/src/components/start/AppRow.svelte b/packages/builder/src/components/start/AppRow.svelte
new file mode 100644
index 0000000000..ce534336ca
--- /dev/null
+++ b/packages/builder/src/components/start/AppRow.svelte
@@ -0,0 +1,80 @@
+
+
+
+
+ Edited {Math.round(Math.random() * 10 + 1)} months ago
+
+
+ {#if Math.random() < 0.33}
+
+ Open
+ {:else if Math.random() < 0.33}
+
+ Locked by Will Wheaton
+ {:else}
+
+ Locked by you
+ {/if}
+
+
+
openApp(app)} size="S" secondary>Open
+
+
+ exportApp(app)} icon="Download">Export
+ deleteApp(app)} icon="Delete">Delete
+
+
+
+
diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte
index 3cbdaa3f34..c30cdfd449 100644
--- a/packages/builder/src/components/start/CreateAppModal.svelte
+++ b/packages/builder/src/components/start/CreateAppModal.svelte
@@ -1,58 +1,50 @@
-
-
-
-
- Get started with Budibase
-
-
- {#each steps as component, i (i)}
-
-
-
- {/each}
-
-
-
- {#if submitting}
-
-
- Creating your app...
-
+
+ {#if template}
+ {
+ $values.file = e.detail?.[0]
+ $touched.file = true
+ }}
+ />
{/if}
-
-
-
+
+ Give your new app a name, and choose which groups have access (paid plans
+ only).
+
+ ($touched.name = true)}
+ label="Name"
+ />
+
+
diff --git a/packages/builder/src/components/start/Indicator.svelte b/packages/builder/src/components/start/Indicator.svelte
deleted file mode 100644
index b77d2589fd..0000000000
--- a/packages/builder/src/components/start/Indicator.svelte
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
- {#if done}
-
-
-
- {:else}{step}{/if}
-
-
-
-
diff --git a/packages/builder/src/components/start/Steps/Info.svelte b/packages/builder/src/components/start/Steps/Info.svelte
deleted file mode 100644
index 42cd5e1e2f..0000000000
--- a/packages/builder/src/components/start/Steps/Info.svelte
+++ /dev/null
@@ -1,121 +0,0 @@
-
-
-
- {#if template?.fromFile}
-
Import your Web App
- {:else}
-
Create your Web App
- {/if}
- {#if template?.fromFile}
-
-
Import File
-
-
-
- {#if file}{file.name}{:else}Import{/if}
-
-
-
- {:else if template}
-
- Selected Template
- {template.name}
-
- {/if}
-
($touched.applicationName = true)}
- bind:value={$values.applicationName}
- label="Web App Name"
- placeholder="Enter name of your web application"
- error={$touched.applicationName && $errors.applicationName}
- />
-
-
-
diff --git a/packages/builder/src/components/start/Steps/User.svelte b/packages/builder/src/components/start/Steps/User.svelte
deleted file mode 100644
index b51e5faaff..0000000000
--- a/packages/builder/src/components/start/Steps/User.svelte
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
- What's your role for this app?
- option.label}
- getOptionValue={option => option.value}
- error={$errors.roleId}
- />
-
-
-
diff --git a/packages/builder/src/components/start/Steps/index.js b/packages/builder/src/components/start/Steps/index.js
deleted file mode 100644
index a98357629e..0000000000
--- a/packages/builder/src/components/start/Steps/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as Info } from "./Info.svelte"
-export { default as User } from "./User.svelte"
diff --git a/packages/builder/src/pages/builder/app/[application]/_reset.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte
similarity index 100%
rename from packages/builder/src/pages/builder/app/[application]/_reset.svelte
rename to packages/builder/src/pages/builder/app/[application]/_layout.svelte
diff --git a/packages/builder/src/pages/builder/portal/_layout.svelte b/packages/builder/src/pages/builder/portal/_layout.svelte
index 5019edb56e..ad624972e1 100644
--- a/packages/builder/src/pages/builder/portal/_layout.svelte
+++ b/packages/builder/src/pages/builder/portal/_layout.svelte
@@ -17,9 +17,6 @@
import { auth } from "stores/backend"
import BuilderSettingsModal from "components/start/BuilderSettingsModal.svelte"
- organisation.init()
- apps.load()
-
let orgName
let orgLogo
let user
@@ -32,7 +29,10 @@
user = { name: "John Doe" }
}
- onMount(getInfo)
+ onMount(() => {
+ organisation.init()
+ getInfo()
+ })
let menu = [
{ title: "Apps", href: "/builder/portal/apps" },
diff --git a/packages/builder/src/pages/builder/portal/apps/_layout.svelte b/packages/builder/src/pages/builder/portal/apps/_layout.svelte
deleted file mode 100644
index c87ab149ba..0000000000
--- a/packages/builder/src/pages/builder/portal/apps/_layout.svelte
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte
index 5062248e11..ff60750f36 100644
--- a/packages/builder/src/pages/builder/portal/apps/index.svelte
+++ b/packages/builder/src/pages/builder/portal/apps/index.svelte
@@ -8,18 +8,31 @@
ButtonGroup,
Select,
Modal,
+ ModalContent,
+ Page,
+ notifications,
+ Body,
} from "@budibase/bbui"
- import AppList from "components/start/AppList.svelte"
import CreateAppModal from "components/start/CreateAppModal.svelte"
- import api from "builderStore/api"
+ import api, { del } from "builderStore/api"
import analytics from "analytics"
import { onMount } from "svelte"
+ import { apps } from "stores/portal"
+ import download from "downloadjs"
+ import { goto } from "@roxi/routify"
+ import ConfirmDialog from "components/common/ConfirmDialog.svelte"
+ import AppCard from "components/start/AppCard.svelte"
+ import AppRow from "components/start/AppRow.svelte"
let layout = "grid"
- let modal
let template
+ let appToDelete
+ let creationModal
+ let deletionModal
+ let creatingApp = false
+ let loaded = false
- async function checkKeys() {
+ const checkKeys = async () => {
const response = await api.get(`/api/keys/`)
const keys = await response.json()
if (keys.userId) {
@@ -27,55 +40,146 @@
}
}
- function initiateAppImport() {
- template = { fromFile: true }
- modal.show()
+ const initiateAppCreation = () => {
+ creationModal.show()
+ creatingApp = true
}
- onMount(checkKeys)
+ const initiateAppImport = () => {
+ template = { fromFile: true }
+ creationModal.show()
+ creatingApp = true
+ }
+
+ const stopAppCreation = () => {
+ template = null
+ creatingApp = false
+ }
+
+ const openApp = app => {
+ $goto(`../../app/${app._id}`)
+ }
+
+ const exportApp = app => {
+ try {
+ download(
+ `/api/backups/export?appId=${app._id}&appname=${encodeURIComponent(
+ app.name
+ )}`
+ )
+ notifications.success("App export complete")
+ } catch (err) {
+ console.error(err)
+ notifications.error("App export failed")
+ }
+ }
+
+ const deleteApp = app => {
+ appToDelete = app
+ deletionModal.show()
+ }
+
+ const confirmDeleteApp = async () => {
+ if (!appToDelete) {
+ return
+ }
+ await del(`/api/applications/${appToDelete?._id}`)
+ await apps.load()
+ appToDelete = null
+ }
+
+ onMount(async () => {
+ checkKeys()
+ await apps.load()
+ loaded = true
+ })
-
-
- Apps
-
- Import app
- Create new app
-
-
-
-
-
-
-
- (layout = "grid")}
- selected={layout === "grid"}
- quiet
- icon="ClassicGridView"
- />
- (layout = "table")}
- selected={layout === "table"}
- quiet
- icon="ViewRow"
- />
-
-
- {#if layout === "grid"}
-
- {:else}
- Table view.
+
+ {#if $apps.length}
+
+
+ Apps
+
+ Import app
+ Create new app
+
+
+
+
+
+
+
+ (layout = "grid")}
+ selected={layout === "grid"}
+ quiet
+ icon="ClassicGridView"
+ />
+ (layout = "table")}
+ selected={layout === "table"}
+ quiet
+ icon="ViewRow"
+ />
+
+
+
+ {#each $apps as app, idx (app._id)}
+
+ {/each}
+
+
{/if}
-
+ {#if !$apps.length && !creatingApp && loaded}
+
+
+
+
+ Import app
+
+
+ The purpose of the Budibase builder is to help you build beautiful,
+ powerful applications quickly and easily.
+
+
+
+
+ {/if}
+
(template = null)}
+ on:hide={stopAppCreation}
>
+
+ Are you sure you want to delete the app {appToDelete?.name} ?
+
diff --git a/packages/builder/src/pages/builder/portal/email/TemplateBindings.svelte b/packages/builder/src/pages/builder/portal/email/TemplateBindings.svelte
new file mode 100644
index 0000000000..8719bf6338
--- /dev/null
+++ b/packages/builder/src/pages/builder/portal/email/TemplateBindings.svelte
@@ -0,0 +1,15 @@
+
+
+
+ {#each bindings as binding}
+ onBindingClick(binding)}>
+ {binding.name}
+ {binding.description}
+
+ {/each}
+
diff --git a/packages/builder/src/pages/builder/portal/email/TemplateLink.svelte b/packages/builder/src/pages/builder/portal/email/TemplateLink.svelte
new file mode 100644
index 0000000000..fe140bafc4
--- /dev/null
+++ b/packages/builder/src/pages/builder/portal/email/TemplateLink.svelte
@@ -0,0 +1,17 @@
+
+
+{value}
+
+
diff --git a/packages/builder/src/pages/builder/portal/email/[template].svelte b/packages/builder/src/pages/builder/portal/email/[template].svelte
new file mode 100644
index 0000000000..317accad60
--- /dev/null
+++ b/packages/builder/src/pages/builder/portal/email/[template].svelte
@@ -0,0 +1,140 @@
+
+
+
+ $goto("./")}>
+
+ Back
+
+
+
+ Email Template: {template}
+
+ Save
+
+
+
+
+
{
+ selectedTemplate.contents = e.detail.value
+ }}
+ value={selectedTemplate.contents}
+ />
+
+ Bindings
+
+
+
+
+
+
+
+
+
+
+
+
+ {@html selectedTemplate.contents}
+
+
+
+
+
+
diff --git a/packages/builder/src/pages/builder/portal/email/index.svelte b/packages/builder/src/pages/builder/portal/email/index.svelte
index 9f37425a80..f14073571c 100644
--- a/packages/builder/src/pages/builder/portal/email/index.svelte
+++ b/packages/builder/src/pages/builder/portal/email/index.svelte
@@ -1,5 +1,8 @@
@@ -144,8 +143,8 @@
From email address
- Save
+ Save
@@ -155,26 +154,17 @@
Budibase comes out of the box with ready-made email templates to help
with user onboarding. Please refrain from changing the links.
-