Merge branch 'master' of github.com:Budibase/budibase into feature/opinionated-sql

This commit is contained in:
mike12345567 2021-06-15 20:00:52 +01:00
commit 3d9dced317
61 changed files with 1616 additions and 533 deletions

View File

@ -172,7 +172,7 @@ For more information, see [CONTRIBUTING.md](https://github.com/Budibase/budibase
## 📝 License ## 📝 License
Budibase is open-source. The builder is licensed [AGPL v3](https://www.gnu.org/licenses/agpl-3.0.en.html), the server is licensed [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html), and the client is licensed [MPL](https://directory.fsf.org/wiki/License:MPL-2.0). Budibase is open-source, licensed as [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). The client and component libraries are licensed as [MPL](https://directory.fsf.org/wiki/License:MPL-2.0) - so the apps that you build can be licensed however you like.
--- ---

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/auth", "name": "@budibase/auth",
"version": "0.9.45", "version": "0.9.48",
"description": "Authentication middlewares for budibase builder and apps", "description": "Authentication middlewares for budibase builder and apps",
"main": "src/index.js", "main": "src/index.js",
"author": "Budibase", "author": "Budibase",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "0.9.45", "version": "0.9.48",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",

View File

@ -5,6 +5,7 @@
export let disabled = false export let disabled = false
export let align = "left" export let align = "left"
export let portalTarget
let anchor let anchor
let dropdown let dropdown
@ -32,7 +33,7 @@
<div use:getAnchor on:click={openMenu}> <div use:getAnchor on:click={openMenu}>
<slot name="control" /> <slot name="control" />
</div> </div>
<Popover bind:this={dropdown} {anchor} {align}> <Popover bind:this={dropdown} {anchor} {align} {portalTarget}>
<Menu> <Menu>
<slot /> <slot />
</Menu> </Menu>

View File

@ -9,6 +9,7 @@
export let anchor export let anchor
export let align = "right" export let align = "right"
export let portalTarget
export const show = () => { export const show = () => {
dispatch("open") dispatch("open")
@ -30,7 +31,7 @@
</script> </script>
{#if open} {#if open}
<Portal> <Portal target={portalTarget}>
<div <div
tabindex="0" tabindex="0"
use:positionDropdown={{ anchor, align }} use:positionDropdown={{ anchor, align }}

View File

@ -29,7 +29,7 @@ context("Create Bindings", () => {
// The builder preview pages don't have a real URL, so all we can do // The builder preview pages don't have a real URL, so all we can do
// is check that we were able to bind to the property, and that the // is check that we were able to bind to the property, and that the
// component exists on the page // component exists on the page
cy.getComponent(componentId).should("have.text", "") cy.getComponent(componentId).should("have.text", "Placeholder text")
}) })
}) })

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "0.9.45", "version": "0.9.48",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -65,10 +65,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.45", "@budibase/bbui": "^0.9.48",
"@budibase/client": "^0.9.45", "@budibase/client": "^0.9.48",
"@budibase/colorpicker": "1.1.2", "@budibase/colorpicker": "1.1.2",
"@budibase/string-templates": "^0.9.45", "@budibase/string-templates": "^0.9.48",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",

View File

@ -6,9 +6,5 @@ export default {
} }
const createScreen = () => { const createScreen = () => {
return new Screen() return new Screen().instanceName("New Screen").json()
.mainType("div")
.component("@budibase/standard-components/container")
.instanceName("New Screen")
.json()
} }

View File

@ -28,8 +28,10 @@ function generateTitleContainer(table, formId) {
const createScreen = table => { const createScreen = table => {
const screen = new Screen() const screen = new Screen()
.component("@budibase/standard-components/container")
.instanceName(`${table.name} - New`) .instanceName(`${table.name} - New`)
.customProps({
hAlign: "center",
})
.route(newRowUrl(table)) .route(newRowUrl(table))
const form = makeMainForm() const form = makeMainForm()

View File

@ -149,9 +149,11 @@ const createScreen = table => {
provider.addChild(repeater) provider.addChild(repeater)
return new Screen() return new Screen()
.component("@budibase/standard-components/container")
.instanceName(`${table.name} - Detail`) .instanceName(`${table.name} - Detail`)
.route(rowDetailUrl(table)) .route(rowDetailUrl(table))
.customProps({
hAlign: "center",
})
.addChild(provider) .addChild(provider)
.json() .json()
} }

View File

@ -56,14 +56,15 @@ function generateTitleContainer(table) {
.text(table.name) .text(table.name)
return new Component("@budibase/standard-components/container") return new Component("@budibase/standard-components/container")
.type("div")
.normalStyle({ .normalStyle({
display: "flex",
"flex-direction": "row",
"justify-content": "space-between",
"align-items": "center",
"margin-bottom": "32px", "margin-bottom": "32px",
}) })
.customProps({
direction: "row",
hAlign: "stretch",
vAlign: "middle",
size: "shrink",
})
.instanceName("Title Container") .instanceName("Title Container")
.addChild(heading) .addChild(heading)
.addChild(newButton) .addChild(newButton)
@ -79,7 +80,8 @@ const createScreen = table => {
tableId: table._id, tableId: table._id,
type: "table", type: "table",
}, },
paginate: false, paginate: true,
limit: 8,
}) })
const spectrumTable = new Component("@budibase/standard-components/table") const spectrumTable = new Component("@budibase/standard-components/table")
@ -126,7 +128,6 @@ const createScreen = table => {
background: "white", background: "white",
"border-radius": "0.5rem", "border-radius": "0.5rem",
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
margin: "auto",
"margin-top": "20px", "margin-top": "20px",
"border-width": "2px", "border-width": "2px",
"border-color": "rgba(0, 0, 0, 0.1)", "border-color": "rgba(0, 0, 0, 0.1)",
@ -137,13 +138,17 @@ const createScreen = table => {
"padding-left": "48px", "padding-left": "48px",
"margin-bottom": "20px", "margin-bottom": "20px",
}) })
.type("div") .customProps({
direction: "column",
hAlign: "stretch",
vAlign: "top",
size: "shrink",
})
.instanceName("Container") .instanceName("Container")
.addChild(generateTitleContainer(table)) .addChild(generateTitleContainer(table))
.addChild(provider) .addChild(provider)
return new Screen() return new Screen()
.component("@budibase/standard-components/container")
.route(rowListUrl(table)) .route(rowListUrl(table))
.instanceName(`${table.name} - List`) .instanceName(`${table.name} - List`)
.addChild(mainContainer) .addChild(mainContainer)

View File

@ -8,7 +8,7 @@ export class Screen extends BaseStructure {
layoutId: "layout_private_master", layoutId: "layout_private_master",
props: { props: {
_id: uuid(), _id: uuid(),
_component: "", _component: "@budibase/standard-components/container",
_styles: { _styles: {
normal: {}, normal: {},
hover: {}, hover: {},
@ -17,6 +17,10 @@ export class Screen extends BaseStructure {
}, },
_children: [], _children: [],
_instanceName: "", _instanceName: "",
direction: "column",
hAlign: "stretch",
vAlign: "top",
size: "grow",
}, },
routing: { routing: {
route: "", route: "",
@ -41,11 +45,6 @@ export class Screen extends BaseStructure {
return this return this
} }
mainType(type) {
this._json.type = type
return this
}
route(route) { route(route) {
this._json.routing.route = route this._json.routing.route = route
return this return this
@ -60,4 +59,11 @@ export class Screen extends BaseStructure {
this._json.props._instanceName = name this._json.props._instanceName = name
return this return this
} }
customProps(props) {
for (let key of Object.keys(props)) {
this._json.props[key] = props[key]
}
return this
}
} }

View File

@ -35,13 +35,11 @@ export function makeLinkComponent(tableName) {
export function makeMainForm() { export function makeMainForm() {
return new Component("@budibase/standard-components/form") return new Component("@budibase/standard-components/form")
.type("div")
.normalStyle({ .normalStyle({
width: "700px", width: "700px",
padding: "0px", padding: "0px",
"border-radius": "0.5rem", "border-radius": "0.5rem",
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
margin: "auto",
"margin-top": "20px", "margin-top": "20px",
"padding-top": "48px", "padding-top": "48px",
"padding-bottom": "48px", "padding-bottom": "48px",
@ -79,11 +77,16 @@ export function makeBreadcrumbContainer(tableName, text, capitalise = false) {
.instanceName("Identifier") .instanceName("Identifier")
return new Component("@budibase/standard-components/container") return new Component("@budibase/standard-components/container")
.type("div")
.normalStyle({ .normalStyle({
"font-size": "14px", "font-size": "14px",
color: "#757575", color: "#757575",
}) })
.customProps({
direction: "row",
hAlign: "left",
vAlign: "middle",
size: "shrink",
})
.instanceName("Breadcrumbs") .instanceName("Breadcrumbs")
.addChild(link) .addChild(link)
.addChild(arrowText) .addChild(arrowText)
@ -149,15 +152,16 @@ export function makeTitleContainer(title) {
.text(title) .text(title)
return new Component("@budibase/standard-components/container") return new Component("@budibase/standard-components/container")
.type("div")
.normalStyle({ .normalStyle({
display: "flex",
"flex-direction": "row",
"justify-content": "space-between",
"align-items": "center",
"margin-top": "32px", "margin-top": "32px",
"margin-bottom": "32px", "margin-bottom": "32px",
}) })
.customProps({
direction: "row",
hAlign: "stretch",
vAlign: "middle",
size: "shrink",
})
.instanceName("Title Container") .instanceName("Title Container")
.addChild(heading) .addChild(heading)
} }

View File

@ -104,6 +104,7 @@
options={$roles} options={$roles}
getOptionLabel={role => role.name} getOptionLabel={role => role.name}
getOptionValue={role => role._id} getOptionValue={role => role._id}
disabled={!creating}
/> />
{#each customSchemaKeys as [key, meta]} {#each customSchemaKeys as [key, meta]}
{#if !meta.autocolumn} {#if !meta.autocolumn}

View File

@ -70,9 +70,16 @@
{ once: true } { once: true }
) )
// Add listener to select components // Add listener for events sent by cliebt library in preview
iframe.contentWindow.addEventListener("bb-select-component", data => { iframe.contentWindow.addEventListener("bb-event", event => {
store.actions.components.select({ _id: data.detail }) const { type, data } = event.detail
if (type === "select-component") {
store.actions.components.select({ _id: data.id })
} else if (type === "update-prop") {
store.actions.components.updateProp(data.prop, data.value)
} else {
console.log(data)
}
}) })
}) })
</script> </script>
@ -94,12 +101,12 @@
overflow: hidden; overflow: hidden;
margin: auto; margin: auto;
height: 100%; height: 100%;
background-color: white;
} }
.component-container iframe { .component-container iframe {
border: 0; border: 0;
left: 0; left: 0;
top: 0; top: 0;
width: 100%; width: 100%;
background-color: transparent;
} }
</style> </style>

View File

@ -14,10 +14,14 @@ export default `
<style> <style>
html, html,
body { body {
height: 100%;
width: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
body {
padding: 2px;
} }
*, *,

View File

@ -194,7 +194,6 @@
padding: var(--spacing-l) 40px var(--spacing-xl) 40px; padding: var(--spacing-l) 40px var(--spacing-xl) 40px;
} }
.preview-content { .preview-content {
background: var(--background);
box-shadow: 0 0 12px rgba(0, 0, 0, 0.05); box-shadow: 0 0 12px rgba(0, 0, 0, 0.05);
flex: 1 1 auto; flex: 1 1 auto;
} }

View File

@ -33,12 +33,16 @@
role: {}, role: {},
} }
$: defaultRoleId = $userFetch?.data?.builder?.global ? "ADMIN" : ""
// Merge the Apps list and the roles response to get something that makes sense for the table // Merge the Apps list and the roles response to get something that makes sense for the table
$: appList = Object.keys($apps?.data).map(id => ({ $: appList = Object.keys($apps?.data).map(id => {
...$apps?.data?.[id], const role = $userFetch?.data?.roles?.[id] || defaultRoleId
_id: id, return {
role: [$userFetch?.data?.roles?.[id]], ...$apps?.data?.[id],
})) _id: id,
role: [role],
}
})
let selectedApp let selectedApp
const userFetch = fetchData(`/api/admin/users/${userId}`) const userFetch = fetchData(`/api/admin/users/${userId}`)

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "0.9.45", "version": "0.9.48",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "0.9.45", "version": "0.9.48",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -18,13 +18,14 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/string-templates": "^0.9.45", "@budibase/bbui": "^0.9.48",
"@budibase/standard-components": "^0.9.48",
"@budibase/string-templates": "^0.9.48",
"regexparam": "^1.3.0", "regexparam": "^1.3.0",
"shortid": "^2.2.15", "shortid": "^2.2.15",
"svelte-spa-router": "^3.0.5" "svelte-spa-router": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@budibase/standard-components": "^0.9.45",
"@rollup/plugin-commonjs": "^18.0.0", "@rollup/plugin-commonjs": "^18.0.0",
"@rollup/plugin-node-resolve": "^11.2.1", "@rollup/plugin-node-resolve": "^11.2.1",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",

View File

@ -14,6 +14,9 @@
builderStore, builderStore,
} from "../store" } from "../store"
import { TableNames, ActionTypes } from "../constants" import { TableNames, ActionTypes } from "../constants"
import SettingsBar from "./preview/SettingsBar.svelte"
import SelectionIndicator from "./preview/SelectionIndicator.svelte"
import HoverIndicator from "./preview/HoverIndicator.svelte"
// Provide contexts // Provide contexts
setContext("sdk", SDK) setContext("sdk", SDK)
@ -54,18 +57,46 @@
</script> </script>
{#if dataLoaded && $screenStore.activeLayout} {#if dataLoaded && $screenStore.activeLayout}
<div lang="en" dir="ltr" class="spectrum spectrum--medium spectrum--light"> <div
id="spectrum-root"
lang="en"
dir="ltr"
class="spectrum spectrum--medium spectrum--light"
>
<Provider key="user" data={$authStore} {actions}> <Provider key="user" data={$authStore} {actions}>
<Component definition={$screenStore.activeLayout.props} /> <div id="app-root">
<Component instance={$screenStore.activeLayout.props} />
</div>
<NotificationDisplay /> <NotificationDisplay />
<!-- Key block needs to be outside the if statement or it breaks -->
{#key $builderStore.selectedComponentId}
{#if $builderStore.inBuilder}
<SettingsBar />
{/if}
{/key}
<!--
We don't want to key these by componentID as they control their own
re-mounting to avoid flashes.
-->
{#if $builderStore.inBuilder}
<SelectionIndicator />
<HoverIndicator />
{/if}
</Provider> </Provider>
</div> </div>
{/if} {/if}
<style> <style>
div { #spectrum-root {
background: transparent;
height: 100%; height: 100%;
width: 100%;
overflow: hidden;
}
#app-root {
height: 100%;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
position: relative; position: relative;
} }
</style> </style>

View File

@ -6,8 +6,10 @@
import { enrichProps, propsAreSame } from "../utils/componentProps" import { enrichProps, propsAreSame } from "../utils/componentProps"
import { builderStore } from "../store" import { builderStore } from "../store"
import { hashString } from "../utils/hash" import { hashString } from "../utils/hash"
import Manifest from "@budibase/standard-components/manifest.json"
import { Placeholder } from "@budibase/standard-components"
export let definition = {} export let instance = {}
// Props that will be passed to the component instance // Props that will be passed to the component instance
let componentProps let componentProps
@ -23,25 +25,39 @@
// Get contexts // Get contexts
const context = getContext("context") const context = getContext("context")
const insideScreenslot = !!getContext("screenslot")
// Create component context // Create component context
const componentStore = writable({}) const componentStore = writable({})
setContext("component", componentStore) setContext("component", componentStore)
// Extract component definition info // Extract component instance info
$: constructor = getComponentConstructor(definition._component) $: constructor = getComponentConstructor(instance._component)
$: children = definition._children || [] $: definition = getComponentDefinition(instance._component)
$: id = definition._id $: children = instance._children || []
$: updateComponentProps(definition, $context) $: id = instance._id
$: styles = definition._styles $: name = instance._instanceName
$: transition = definition._transition $: empty =
!children.length &&
definition?.hasChildren &&
definition?.showEmptyState !== false &&
$builderStore.inBuilder
$: updateComponentProps(instance, $context)
$: selected =
$builderStore.inBuilder &&
$builderStore.selectedComponentId === instance._id
$: interactive = $builderStore.previewType === "layout" || insideScreenslot
// Update component context // Update component context
$: componentStore.set({ $: componentStore.set({
id, id,
children: children.length, children: children.length,
styles: { ...styles, id }, styles: { ...instance._styles, id, empty, interactive },
transition, empty,
transition: instance._transition,
selected,
props: componentProps,
name,
}) })
// Gets the component constructor for the specified component // Gets the component constructor for the specified component
@ -54,14 +70,20 @@
return ComponentLibrary[name] return ComponentLibrary[name]
} }
const getComponentDefinition = component => {
const prefix = "@budibase/standard-components/"
const type = component?.replace(prefix, "")
return type ? Manifest[type] : null
}
// Enriches any string component props using handlebars // Enriches any string component props using handlebars
const updateComponentProps = (definition, context) => { const updateComponentProps = (instance, context) => {
// Record the timestamp so we can reference it after enrichment // Record the timestamp so we can reference it after enrichment
latestUpdateTime = Date.now() latestUpdateTime = Date.now()
const enrichmentTime = latestUpdateTime const enrichmentTime = latestUpdateTime
// Enrich props with context // Enrich props with context
const enrichedProps = enrichProps(definition, context) const enrichedProps = enrichProps(instance, context)
// Abandon this update if a newer update has started // Abandon this update if a newer update has started
if (enrichmentTime !== latestUpdateTime) { if (enrichmentTime !== latestUpdateTime) {
@ -94,14 +116,29 @@
} }
</script> </script>
{#if constructor && componentProps} <div
class={`component ${id}`}
data-type={interactive ? "component" : ""}
data-id={id}
data-name={name}
>
{#key propsHash} {#key propsHash}
<svelte:component this={constructor} {...componentProps}> {#if constructor && componentProps}
{#if children.length} <svelte:component this={constructor} {...componentProps}>
{#each children as child (child._id)} {#if children.length}
<svelte:self definition={child} /> {#each children as child (child._id)}
{/each} <svelte:self instance={child} />
{/if} {/each}
</svelte:component> {:else if empty}
<Placeholder />
{/if}
</svelte:component>
{/if}
{/key} {/key}
{/if} </div>
<style>
.component {
display: contents;
}
</style>

View File

@ -1,12 +1,12 @@
<script> <script>
import { getContext } from "svelte" import { setContext, getContext } from "svelte"
import Router from "svelte-spa-router" import Router from "svelte-spa-router"
import { routeStore } from "../store" import { routeStore } from "../store"
import Screen from "./Screen.svelte" import Screen from "./Screen.svelte"
import { onMount } from "svelte"
const { styleable } = getContext("sdk") const { styleable } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
setContext("screenslot", true)
// Only wrap this as an array to take advantage of svelte keying, // Only wrap this as an array to take advantage of svelte keying,
// to ensure the svelte-spa-router is fully remounted when route config // to ensure the svelte-spa-router is fully remounted when route config
@ -41,5 +41,6 @@
<style> <style>
div { div {
position: relative; position: relative;
overflow-x: auto;
} }
</style> </style>

View File

@ -22,6 +22,6 @@
<!-- Ensure to fully remount when screen changes --> <!-- Ensure to fully remount when screen changes -->
{#key screenDefinition?._id} {#key screenDefinition?._id}
<Provider key="url" data={params}> <Provider key="url" data={params}>
<Component definition={screenDefinition} /> <Component instance={screenDefinition} />
</Provider> </Provider>
{/key} {/key}

View File

@ -0,0 +1,32 @@
<script>
import { onMount, onDestroy } from "svelte"
import IndicatorSet from "./IndicatorSet.svelte"
import { builderStore } from "../../store"
let componentId
$: zIndex = componentId === $builderStore.selectedComponentId ? 900 : 920
const onMouseOver = e => {
const element = e.target.closest("[data-type='component']")
const newId = element?.dataset?.id
if (newId !== componentId) {
componentId = newId
}
}
const onMouseLeave = () => {
componentId = null
}
onMount(() => {
document.addEventListener("mouseover", onMouseOver)
document.body.addEventListener("mouseleave", onMouseLeave)
})
onDestroy(() => {
document.removeEventListener("mouseover", onMouseOver)
document.body.removeEventListener("mouseleave", onMouseLeave)
})
</script>
<IndicatorSet {componentId} color="rgb(120, 170, 244)" transition {zIndex} />

View File

@ -0,0 +1,61 @@
<script>
import { fade } from "svelte/transition"
export let top
export let left
export let width
export let height
export let text
export let color
export let zIndex
export let transition = false
</script>
<div
in:fade={{
delay: transition ? 50 : 0,
duration: transition ? 130 : 0,
}}
out:fade={{ duration: transition ? 130 : 0 }}
class="indicator"
style="top: {top}px; left: {left}px; width: {width}px; height: {height}px; --color: {color}; --zIndex: {zIndex};"
>
{#if text}
<div class="text" class:flipped={top < 22}>
{text}
</div>
{/if}
</div>
<style>
.indicator {
position: fixed;
z-index: var(--zIndex);
border: 2px solid var(--color);
pointer-events: none;
border-radius: 4px;
}
.text {
background-color: var(--color);
color: white;
position: absolute;
top: 0;
left: -2px;
height: 20px;
padding: 0 8px 2px 8px;
transform: translateY(-100%);
font-size: 11px;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
white-space: nowrap;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.flipped {
transform: translateY(0%);
top: -2px;
}
</style>

View File

@ -0,0 +1,118 @@
<script>
import { onMount, onDestroy } from "svelte"
import Indicator from "./Indicator.svelte"
import { domDebounce } from "../../utils/domDebounce"
export let componentId
export let color
export let transition
export let zIndex
let indicators = []
let interval
let text
$: visibleIndicators = indicators.filter(x => x.visible)
let updating = false
let observers = []
let callbackCount = 0
let nextIndicators = []
const createIntersectionCallback = idx => entries => {
if (callbackCount >= observers.length) {
return
}
nextIndicators[idx].visible = entries[0].isIntersecting
if (++callbackCount === observers.length) {
indicators = nextIndicators
updating = false
}
}
const updatePosition = () => {
if (updating) {
return
}
// Sanity check
if (!componentId) {
indicators = []
return
}
// Reset state
updating = true
callbackCount = 0
observers.forEach(o => o.disconnect())
observers = []
nextIndicators = []
// Determine next set of indicators
const parents = document.getElementsByClassName(componentId)
if (parents.length) {
text = parents[0].dataset.name
}
// Batch reads to minimize reflow
const scrollX = window.scrollX
const scrollY = window.scrollY
// Extract valid children
// Sanity limit of 100 active indicators
const children = Array.from(parents)
.map(parent => parent?.childNodes?.[0])
.filter(child => child != null)
.slice(0, 100)
// If there aren't any nodes then reset
if (!children.length) {
indicators = []
updating = false
}
children.forEach((child, idx) => {
const callback = createIntersectionCallback(idx)
const threshold = children.length > 1 ? 1 : 0
const observer = new IntersectionObserver(callback, { threshold })
observer.observe(child)
observers.push(observer)
const elBounds = child.getBoundingClientRect()
nextIndicators.push({
top: elBounds.top + scrollY - 2,
left: elBounds.left + scrollX - 2,
width: elBounds.width + 4,
height: elBounds.height + 4,
visible: false,
})
})
}
const debouncedUpdate = domDebounce(updatePosition)
onMount(() => {
debouncedUpdate()
interval = setInterval(debouncedUpdate, 100)
document.addEventListener("scroll", debouncedUpdate, true)
})
onDestroy(() => {
clearInterval(interval)
document.removeEventListener("scroll", debouncedUpdate, true)
observers.forEach(o => o.disconnect())
})
</script>
{#key componentId}
{#each visibleIndicators as indicator, idx}
<Indicator
top={indicator.top}
left={indicator.left}
width={indicator.width}
height={indicator.height}
text={idx === 0 ? text : null}
{transition}
{zIndex}
{color}
/>
{/each}
{/key}

View File

@ -0,0 +1,11 @@
<script>
import { builderStore } from "../../store"
import IndicatorSet from "./IndicatorSet.svelte"
</script>
<IndicatorSet
componentId={$builderStore.selectedComponentId}
color="rgb(66, 133, 244)"
zIndex="910"
transition
/>

View File

@ -0,0 +1,131 @@
<script>
import { onMount, onDestroy } from "svelte"
import SettingsButton from "./SettingsButton.svelte"
import { builderStore } from "../../store"
import { domDebounce } from "../../utils/domDebounce"
const verticalOffset = 28
const horizontalOffset = 2
let top = 0
let left = 0
let interval
let self
let measured = false
$: definition = $builderStore.selectedComponentDefinition
$: showBar = definition?.showSettingsBar
$: settings = definition?.settings?.filter(setting => setting.showInBar) ?? []
const updatePosition = () => {
if (!showBar) {
return
}
const id = $builderStore.selectedComponentId
const parent = document.getElementsByClassName(id)?.[0]
const element = parent?.childNodes?.[0]
if (element && self) {
// Batch reads to minimize reflow
const elBounds = element.getBoundingClientRect()
const width = self.offsetWidth
const height = self.offsetHeight
const { scrollX, scrollY, innerWidth } = window
// Vertically, always render above unless no room, then render inside
let newTop = elBounds.top + scrollY - verticalOffset - height
if (newTop < 0) {
newTop = elBounds.top + scrollY + verticalOffset
}
// Horizontally, try to center first.
// Failing that, render to left edge of component.
// Failing that, render to right edge of component,
// Failing that, render to window left edge and accept defeat.
let elCenter = elBounds.left + scrollX + elBounds.width / 2
let newLeft = elCenter - width / 2
if (newLeft < 0 || newLeft + width > innerWidth) {
newLeft = elBounds.left + scrollX - horizontalOffset
if (newLeft < 0 || newLeft + width > innerWidth) {
newLeft = elBounds.right + scrollX - width + horizontalOffset
if (newLeft < 0 || newLeft + width > innerWidth) {
newLeft = horizontalOffset
}
}
}
// Only update state when things changes to minimize renders
if (Math.round(newTop) !== Math.round(top)) {
top = newTop
}
if (Math.round(newLeft) !== Math.round(left)) {
left = newLeft
}
measured = true
}
}
const debouncedUpdate = domDebounce(updatePosition)
onMount(() => {
debouncedUpdate()
interval = setInterval(debouncedUpdate, 100)
document.addEventListener("scroll", debouncedUpdate, true)
})
onDestroy(() => {
clearInterval(interval)
document.removeEventListener("scroll", debouncedUpdate, true)
})
</script>
{#if showBar}
<div
class="bar"
style="top: {top}px; left: {left}px;"
bind:this={self}
class:visible={measured}
>
{#each settings as setting, idx}
{#if setting.type === "select"}
{#each setting.options as option}
<SettingsButton
prop={setting.key}
value={option.value}
icon={option.barIcon}
title={option.barTitle}
/>
{/each}
{/if}
{#if idx < settings.length - 1}
<div class="divider" />
{/if}
{/each}
</div>
{/if}
<style>
.bar {
display: flex;
position: absolute;
z-index: 930;
padding: 6px 8px;
opacity: 0;
flex-direction: row;
background: var(--background);
justify-content: center;
align-items: center;
border-radius: 4px;
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.2);
gap: 2px;
transition: opacity 0.13s ease-in-out;
}
.visible {
opacity: 1;
}
.divider {
flex: 0 0 1px;
align-self: stretch;
margin: 0 4px;
background-color: var(--spectrum-global-color-gray-300);
}
</style>

View File

@ -0,0 +1,50 @@
<script>
import { Icon } from "@budibase/bbui"
import { builderStore } from "../../store"
export let prop
export let value
export let icon
export let title
export let rotate = false
export let bool = false
$: currentValue = $builderStore.selectedComponent?.[prop]
$: active = prop && (bool ? !!currentValue : currentValue === value)
</script>
<div
{title}
class:rotate
class:active
on:click={() => {
if (prop) {
const newValue = bool ? !currentValue : value
builderStore.actions.updateProp(prop, newValue)
}
}}
>
<Icon name={icon} size="S" />
</div>
<style>
div {
padding: 6px;
border-radius: 2px;
color: var(--spectrum-global-color-gray-700);
display: flex;
transition: color 0.13s ease-in-out, background-color 0.13s ease-in-out;
}
div:hover {
background-color: var(--spectrum-global-color-gray-200);
cursor: pointer;
}
.active,
.active:hover {
background-color: rgba(13, 102, 208, 0.1);
color: var(--spectrum-global-color-blue-600);
}
.rotate {
transform: rotate(90deg);
}
</style>

View File

@ -1,4 +1,32 @@
import { writable } from "svelte/store" import { writable, derived } from "svelte/store"
import Manifest from "@budibase/standard-components/manifest.json"
const dispatchEvent = (type, data) => {
window.dispatchEvent(
new CustomEvent("bb-event", {
detail: { type, data },
})
)
}
const findComponentById = (component, componentId) => {
if (!component || !componentId) {
return null
}
if (component._id === componentId) {
return component
}
if (!component._children?.length) {
return null
}
for (let child of component._children) {
const result = findComponentById(child, componentId)
if (result) {
return result
}
}
return null
}
const createBuilderStore = () => { const createBuilderStore = () => {
const initialState = { const initialState = {
@ -10,18 +38,35 @@ const createBuilderStore = () => {
previewId: null, previewId: null,
previewType: null, previewType: null,
} }
const store = writable(initialState) const writableStore = writable(initialState)
const derivedStore = derived(writableStore, $state => {
// Derive the selected component instance and definition
const { layout, screen, previewType, selectedComponentId } = $state
const asset = previewType === "layout" ? layout : screen
const component = findComponentById(asset?.props, selectedComponentId)
const prefix = "@budibase/standard-components/"
const type = component?._component?.replace(prefix, "")
const definition = type ? Manifest[type] : null
return {
...$state,
selectedComponent: component,
selectedComponentDefinition: definition,
}
})
const actions = { const actions = {
selectComponent: id => { selectComponent: id => {
if (id) { if (id) {
window.dispatchEvent( dispatchEvent("select-component", { id })
new CustomEvent("bb-select-component", { detail: id })
)
} }
}, },
updateProp: (prop, value) => {
dispatchEvent("update-prop", { prop, value })
},
} }
return { return {
...store, ...writableStore,
subscribe: derivedStore.subscribe,
actions, actions,
} }
} }

View File

@ -0,0 +1,12 @@
export const domDebounce = callback => {
let active = false
return e => {
if (!active) {
window.requestAnimationFrame(() => {
callback(e)
active = false
})
active = true
}
}
}

View File

@ -14,25 +14,6 @@ const buildStyleString = (styleObject, customStyles) => {
return str + (customStyles || "") return str + (customStyles || "")
} }
/**
* Applies styles to enrich the builder preview.
* Applies styles to highlight the selected component, and allows pointer
* events for any selectable components (overriding the blanket ban on pointer
* events in the iframe HTML).
*/
const addBuilderPreviewStyles = (node, styleString, componentId) => {
if (componentId === get(builderStore).selectedComponentId) {
const style = window.getComputedStyle(node)
const property = style?.display === "table-row" ? "outline" : "border"
return (
styleString +
`;${property}: 2px solid #4285f4 !important; border-radius: 4px !important;`
)
} else {
return styleString
}
}
/** /**
* Svelte action to apply correct component styles. * Svelte action to apply correct component styles.
* This also applies handlers for selecting components from the builder preview. * This also applies handlers for selecting components from the builder preview.
@ -44,9 +25,17 @@ export const styleable = (node, styles = {}) => {
// Creates event listeners and applies initial styles // Creates event listeners and applies initial styles
const setupStyles = (newStyles = {}) => { const setupStyles = (newStyles = {}) => {
// Use empty state styles as base styles if required, but let them, get
// overridden by any user specified styles
let baseStyles = {}
if (newStyles.empty) {
baseStyles.border = "2px dashed var(--grey-5)"
baseStyles.padding = "var(--spacing-l)"
}
const componentId = newStyles.id const componentId = newStyles.id
const customStyles = newStyles.custom || "" const customStyles = newStyles.custom || ""
const normalStyles = newStyles.normal || {} const normalStyles = { ...baseStyles, ...newStyles.normal }
const hoverStyles = { const hoverStyles = {
...normalStyles, ...normalStyles,
...(newStyles.hover || {}), ...(newStyles.hover || {}),
@ -54,7 +43,7 @@ export const styleable = (node, styles = {}) => {
// Applies a style string to a DOM node // Applies a style string to a DOM node
const applyStyles = styleString => { const applyStyles = styleString => {
node.style = addBuilderPreviewStyles(node, styleString, componentId) node.style = styleString
node.dataset.componentId = componentId node.dataset.componentId = componentId
} }
@ -71,7 +60,9 @@ export const styleable = (node, styles = {}) => {
// Handler to select a component in the builder when clicking it in the // Handler to select a component in the builder when clicking it in the
// builder preview // builder preview
selectComponent = event => { selectComponent = event => {
builderStore.actions.selectComponent(componentId) if (newStyles.interactive) {
builderStore.actions.selectComponent(componentId)
}
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
return false return false

View File

@ -23,6 +23,113 @@
chalk "^2.0.0" chalk "^2.0.0"
js-tokens "^4.0.0" js-tokens "^4.0.0"
<<<<<<< HEAD
=======
"@budibase/bbui@^0.9.46":
version "0.9.46"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.46.tgz#3109666b618daa65b29d1c7c45549420c62e6489"
integrity sha512-PRW8kR9+QrMiom6hVzisMYd268dj03ojC0ruzEkDhKMONg2I021ST62hzKXdb7zh5LgoYXtapmM9qsKwoHfkPg==
dependencies:
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
"@spectrum-css/actionbutton" "^1.0.1"
"@spectrum-css/actiongroup" "^1.0.1"
"@spectrum-css/avatar" "^3.0.2"
"@spectrum-css/button" "^3.0.1"
"@spectrum-css/buttongroup" "^3.0.2"
"@spectrum-css/checkbox" "^3.0.2"
"@spectrum-css/dialog" "^3.0.1"
"@spectrum-css/divider" "^1.0.1"
"@spectrum-css/dropzone" "^3.0.2"
"@spectrum-css/fieldgroup" "^3.0.2"
"@spectrum-css/fieldlabel" "^3.0.1"
"@spectrum-css/icon" "^3.0.1"
"@spectrum-css/illustratedmessage" "^3.0.2"
"@spectrum-css/inputgroup" "^3.0.2"
"@spectrum-css/label" "^2.0.10"
"@spectrum-css/link" "^3.1.1"
"@spectrum-css/menu" "^3.0.1"
"@spectrum-css/modal" "^3.0.1"
"@spectrum-css/pagination" "^3.0.3"
"@spectrum-css/picker" "^1.0.1"
"@spectrum-css/popover" "^3.0.1"
"@spectrum-css/progressbar" "^1.0.2"
"@spectrum-css/progresscircle" "^1.0.2"
"@spectrum-css/radio" "^3.0.2"
"@spectrum-css/search" "^3.0.2"
"@spectrum-css/sidenav" "^3.0.2"
"@spectrum-css/statuslight" "^3.0.2"
"@spectrum-css/switch" "^1.0.2"
"@spectrum-css/table" "^3.0.1"
"@spectrum-css/tabs" "^3.0.1"
"@spectrum-css/tags" "^3.0.2"
"@spectrum-css/textfield" "^3.0.1"
"@spectrum-css/toast" "^3.0.1"
"@spectrum-css/tooltip" "^3.0.3"
"@spectrum-css/treeview" "^3.0.2"
"@spectrum-css/typography" "^3.0.1"
"@spectrum-css/underlay" "^2.0.9"
"@spectrum-css/vars" "^3.0.1"
dayjs "^1.10.4"
svelte-flatpickr "^3.1.0"
svelte-portal "^1.0.0"
"@budibase/handlebars-helpers@^0.11.4":
version "0.11.4"
resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.4.tgz#8acfa2ee84134f7be4b2906e710fce6d25c5d000"
integrity sha512-AJNJYlJnxZmn9QJ8tBl8nrm4YxbwHP4AR0pbiVGK+EoOylkNBlUnZ/QDL1VyqM5fTkAE/Z2IZVLKrrG3kxuWLA==
dependencies:
arr-flatten "^1.1.0"
array-sort "^0.1.4"
define-property "^1.0.0"
extend-shallow "^3.0.2"
"falsey" "^0.3.2"
for-in "^1.0.2"
for-own "^1.0.0"
get-object "^0.2.0"
get-value "^2.0.6"
handlebars "^4.0.11"
handlebars-utils "^1.0.6"
has-value "^1.0.0"
helper-date "^1.0.1"
helper-markdown "^1.0.0"
helper-md "^0.2.2"
html-tag "^2.0.0"
is-even "^1.0.0"
is-glob "^4.0.0"
is-number "^4.0.0"
kind-of "^6.0.0"
logging-helpers "^1.0.0"
micromatch "^3.1.4"
relative "^3.0.2"
striptags "^3.1.0"
to-gfm-code-block "^0.1.1"
year "^0.2.1"
"@budibase/standard-components@^0.9.46":
version "0.9.46"
resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.46.tgz#a31a253ca51a2029c3aaf5d8aca5c953358e1d67"
integrity sha512-QjW4tukMw4Xa477wGTle2UPz85ygodQ3KG+WEdPAWKq7j0IDv0Fad0oDmWtzLvGxxB+AiRbEnM6T1QV6X1ItCA==
dependencies:
"@budibase/bbui" "^0.9.46"
"@spectrum-css/page" "^3.0.1"
"@spectrum-css/vars" "^3.0.1"
apexcharts "^3.22.1"
dayjs "^1.10.5"
svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0"
"@budibase/string-templates@^0.9.46":
version "0.9.46"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.46.tgz#e43f87513977879a892ae52f3941d3320cb9ff88"
integrity sha512-yOVS7Y/QLATj31QuBu8KP78Oyzhs60V09JEQKa7n4vRP8TBemcev/LFShln8Iiv70YZSpdJviguQuJ6Ow0aUNA==
dependencies:
"@budibase/handlebars-helpers" "^0.11.4"
dayjs "^1.10.4"
handlebars "^4.7.6"
handlebars-utils "^1.0.6"
lodash "^4.17.20"
>>>>>>> 81e794065761192883767a80679d7d94d67afcc2
"@rollup/plugin-commonjs@^18.0.0": "@rollup/plugin-commonjs@^18.0.0":
version "18.0.0" version "18.0.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-18.0.0.tgz#50dc7518b5aa9e66a270e529ea85115d269825c4" resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-18.0.0.tgz#50dc7518b5aa9e66a270e529ea85115d269825c4"

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.9.45", "version": "0.9.48",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/electron.js", "main": "src/electron.js",
"repository": { "repository": {
@ -55,9 +55,9 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.45", "@budibase/auth": "^0.9.48",
"@budibase/client": "^0.9.45", "@budibase/client": "^0.9.48",
"@budibase/string-templates": "^0.9.45", "@budibase/string-templates": "^0.9.48",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",
"@koa/router": "8.0.0", "@koa/router": "8.0.0",
"@sendgrid/mail": "7.1.1", "@sendgrid/mail": "7.1.1",
@ -110,7 +110,7 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.14.3", "@babel/core": "^7.14.3",
"@babel/preset-env": "^7.14.4", "@babel/preset-env": "^7.14.4",
"@budibase/standard-components": "^0.9.45", "@budibase/standard-components": "^0.9.48",
"@jest/test-sequencer": "^24.8.0", "@jest/test-sequencer": "^24.8.0",
"babel-jest": "^27.0.2", "babel-jest": "^27.0.2",
"docker-compose": "^0.23.6", "docker-compose": "^0.23.6",

View File

@ -4,7 +4,6 @@ const {
getUserMetadataParams, getUserMetadataParams,
} = require("../../db/utils") } = require("../../db/utils")
const { InternalTables } = require("../../db/utils") const { InternalTables } = require("../../db/utils")
const { addAppRoleToUser } = require("../../utilities/workerRequests")
const { getGlobalUsers } = require("../../utilities/global") const { getGlobalUsers } = require("../../utilities/global")
const { getFullUser } = require("../../utilities/users") const { getFullUser } = require("../../utilities/users")
@ -53,9 +52,6 @@ exports.updateMetadata = async function (ctx) {
const appId = ctx.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
const user = removeGlobalProps(ctx.request.body) const user = removeGlobalProps(ctx.request.body)
if (user.roleId) {
await addAppRoleToUser(ctx, appId, user.roleId, user._id)
}
const metadata = { const metadata = {
tableId: InternalTables.USER_METADATA, tableId: InternalTables.USER_METADATA,
...user, ...user,

View File

@ -23,9 +23,8 @@ const EMPTY_LAYOUT = {
"justify-content": "flex-start", "justify-content": "flex-start",
"align-items": "stretch", "align-items": "stretch",
"max-width": "100%", "max-width": "100%",
"margin-left": "20px",
"margin-right": "20px",
width: "1400px", width: "1400px",
padding: "20px",
}, },
hover: {}, hover: {},
active: {}, active: {},
@ -34,24 +33,19 @@ const EMPTY_LAYOUT = {
_children: [], _children: [],
}, },
], ],
type: "div",
_styles: { _styles: {
active: {}, active: {},
hover: {}, hover: {},
normal: { normal: {
display: "flex",
"flex-direction": "column",
"align-items": "center",
"justify-content": "flex-start",
"margin-right": "auto",
"margin-left": "auto",
"min-height": "100%", "min-height": "100%",
"background-image": "#f5f5f5", "background-image": "#f5f5f5",
}, },
selected: {}, selected: {},
}, },
className: "", direction: "column",
onLoad: [], hAlign: "center",
vAlign: "top",
size: "grow",
}, },
} }
@ -72,10 +66,6 @@ const BASE_LAYOUTS = [
_component: "@budibase/standard-components/container", _component: "@budibase/standard-components/container",
_styles: { _styles: {
normal: { normal: {
display: "flex",
"flex-direction": "row",
"justify-content": "flex-start",
"align-items": "flex-start",
background: "#fff", background: "#fff",
width: "100%", width: "100%",
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
@ -84,9 +74,6 @@ const BASE_LAYOUTS = [
active: {}, active: {},
selected: {}, selected: {},
}, },
className: "",
onLoad: [],
type: "div",
_instanceName: "Header", _instanceName: "Header",
_children: [ _children: [
{ {
@ -95,8 +82,6 @@ const BASE_LAYOUTS = [
_styles: { _styles: {
normal: { normal: {
"max-width": "1400px", "max-width": "1400px",
"margin-left": "auto",
"margin-right": "auto",
padding: "20px", padding: "20px",
"font-weight": "400", "font-weight": "400",
"font-size": "16px", "font-size": "16px",
@ -143,6 +128,10 @@ const BASE_LAYOUTS = [
], ],
}, },
], ],
direction: "row",
hAlign: "center",
vAlign: "middle",
size: "shrink",
}, },
{ {
_id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967", _id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967",
@ -155,8 +144,6 @@ const BASE_LAYOUTS = [
"justify-content": "flex-start", "justify-content": "flex-start",
"align-items": "stretch", "align-items": "stretch",
"max-width": "100%", "max-width": "100%",
"margin-left": "20px",
"margin-right": "20px",
width: "1400px", width: "1400px",
padding: "20px", padding: "20px",
}, },
@ -167,24 +154,19 @@ const BASE_LAYOUTS = [
_children: [], _children: [],
}, },
], ],
type: "div",
_styles: { _styles: {
active: {}, active: {},
hover: {}, hover: {},
normal: { normal: {
display: "flex",
"flex-direction": "column",
"align-items": "center",
"justify-content": "flex-start",
"margin-right": "auto",
"margin-left": "auto",
"min-height": "100%", "min-height": "100%",
background: "#f5f5f5", background: "#f5f5f5",
}, },
selected: {}, selected: {},
}, },
className: "", direction: "column",
onLoad: [], hAlign: "center",
vAlign: "top",
size: "grow",
}, },
}, },
{ {
@ -209,8 +191,6 @@ const BASE_LAYOUTS = [
"justify-content": "flex-start", "justify-content": "flex-start",
"align-items": "stretch", "align-items": "stretch",
"max-width": "100%", "max-width": "100%",
"margin-left": "20px",
"margin-right": "20px",
width: "1400px", width: "1400px",
padding: "20px", padding: "20px",
}, },
@ -221,24 +201,19 @@ const BASE_LAYOUTS = [
_children: [], _children: [],
}, },
], ],
type: "div",
_styles: { _styles: {
active: {}, active: {},
hover: {}, hover: {},
normal: { normal: {
display: "flex",
"flex-direction": "column",
"align-items": "center",
"justify-content": "center",
"margin-right": "auto",
"margin-left": "auto",
"min-height": "100%", "min-height": "100%",
background: "#f5f5f5", background: "#f5f5f5",
}, },
selected: {}, selected: {},
}, },
className: "", direction: "column",
onLoad: [], hAlign: "center",
vAlign: "top",
size: "grow",
}, },
}, },
] ]

View File

@ -9,19 +9,12 @@ exports.createHomeScreen = () => ({
_id: "d834fea2-1b3e-4320-ab34-f9009f5ecc59", _id: "d834fea2-1b3e-4320-ab34-f9009f5ecc59",
_component: "@budibase/standard-components/container", _component: "@budibase/standard-components/container",
_styles: { _styles: {
normal: { normal: {},
flex: "1 1 auto",
display: "flex",
"flex-direction": "column",
"justify-content": "flex-start",
"align-items": "stretch",
},
hover: {}, hover: {},
active: {}, active: {},
selected: {}, selected: {},
}, },
_transition: "fade", _transition: "fade",
type: "div",
_children: [ _children: [
{ {
_id: "ef60083f-4a02-4df3-80f3-a0d3d16847e7", _id: "ef60083f-4a02-4df3-80f3-a0d3d16847e7",
@ -41,6 +34,10 @@ exports.createHomeScreen = () => ({
}, },
], ],
_instanceName: "Home", _instanceName: "Home",
direction: "column",
hAlign: "stretch",
vAlign: "top",
size: "grow",
}, },
routing: { routing: {
route: "/", route: "/",

View File

@ -12,14 +12,14 @@ exports.updateAppRole = (appId, user) => {
if (!user.roles) { if (!user.roles) {
return user return user
} }
if (user.builder && user.builder.global) {
// always use the deployed app
user.roleId = user.roles[getDeployedAppID(appId)]
// if a role wasn't found then either set as admin (builder) or public (everyone else)
if (!user.roleId && user.builder && user.builder.global) {
user.roleId = BUILTIN_ROLE_IDS.ADMIN user.roleId = BUILTIN_ROLE_IDS.ADMIN
} else { } else if (!user.roleId) {
// always use the deployed app user.roleId = BUILTIN_ROLE_IDS.PUBLIC
user.roleId = user.roles[getDeployedAppID(appId)]
if (!user.roleId) {
user.roleId = BUILTIN_ROLE_IDS.PUBLIC
}
} }
delete user.roles delete user.roles
return user return user

View File

@ -6,7 +6,117 @@
"hasChildren": true, "hasChildren": true,
"styleable": true, "styleable": true,
"transitionable": true, "transitionable": true,
"settings": [] "showSettingsBar": true,
"settings": [
{
"type": "select",
"label": "Direction",
"key": "direction",
"showInBar": true,
"options": [
{
"label": "Column",
"value": "column",
"barIcon": "ViewRow",
"barTitle": "Column layout"
},
{
"label": "Row",
"value": "row",
"barIcon": "ViewColumn",
"barTitle": "Row layout"
}
],
"defaultValue": "column"
},
{
"type": "select",
"label": "Horiz. Align",
"key": "hAlign",
"showInBar": true,
"options": [
{
"label": "Left",
"value": "left",
"barIcon": "AlignLeft",
"barTitle": "Align left"
},
{
"label": "Center",
"value": "center",
"barIcon": "AlignCenter",
"barTitle": "Align center"
},
{
"label": "Right",
"value": "right",
"barIcon": "AlignRight",
"barTitle": "Align right"
},
{
"label": "Stretch",
"value": "stretch",
"barIcon": "MoveLeftRight",
"barTitle": "Align stretched horizontally"
}
],
"defaultValue": "stretch"
},
{
"type": "select",
"label": "Vert. Align",
"key": "vAlign",
"showInBar": "true",
"options": [
{
"label": "Top",
"value": "top",
"barIcon": "AlignTop",
"barTitle": "Align top"
},
{
"label": "Middle",
"value": "middle",
"barIcon": "AlignMiddle",
"barTitle": "Align middle"
},
{
"label": "Bottom",
"value": "bottom",
"barIcon": "AlignBottom",
"barTitle": "Align bottom"
},
{
"label": "Stretch",
"value": "stretch",
"barIcon": "MoveUpDown",
"barTitle": "Align stretched vertically"
}
],
"defaultValue": "top"
},
{
"type": "select",
"label": "Size",
"key": "size",
"showInBar": true,
"options": [
{
"label": "Shrink",
"value": "shrink",
"barIcon": "Minimize",
"barTitle": "Shrink container"
},
{
"label": "Grow",
"value": "grow",
"barIcon": "Maximize",
"barTitle": "Grow container"
}
],
"defaultValue": "shrink"
}
]
}, },
"screenslot": { "screenslot": {
"name": "Screenslot", "name": "Screenslot",
@ -53,7 +163,7 @@
"type": "text", "type": "text",
"label": "Empty Text", "label": "Empty Text",
"key": "noRowsMessage", "key": "noRowsMessage",
"defaultValue": "No rows found." "defaultValue": "No rows found"
}, },
{ {
"type": "filter", "type": "filter",
@ -1438,6 +1548,7 @@
"icon": "Table", "icon": "Table",
"styleable": true, "styleable": true,
"hasChildren": true, "hasChildren": true,
"showEmptyState": false,
"settings": [ "settings": [
{ {
"type": "dataProvider", "type": "dataProvider",

View File

@ -29,11 +29,11 @@
"keywords": [ "keywords": [
"svelte" "svelte"
], ],
"version": "0.9.45", "version": "0.9.48",
"license": "MIT", "license": "MIT",
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc", "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc",
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.45", "@budibase/bbui": "^0.9.48",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",
"apexcharts": "^3.22.1", "apexcharts": "^3.22.1",

View File

@ -1,13 +1,87 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
const { styleable, transition } = getContext("sdk") const { styleable, transition, builderStore } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
export let direction
export let hAlign
export let vAlign
export let size
$: directionClass = direction ? `valid-container direction-${direction}` : ""
$: hAlignClass = hAlign ? `hAlign-${hAlign}` : ""
$: vAlignClass = vAlign ? `vAlign-${vAlign}` : ""
$: sizeClass = size ? `size-${size}` : ""
</script> </script>
<div <div
class={[directionClass, hAlignClass, vAlignClass, sizeClass].join(" ")}
in:transition={{ type: $component.transition }} in:transition={{ type: $component.transition }}
use:styleable={$component.styles} use:styleable={$component.styles}
> >
<slot /> <slot />
</div> </div>
<style>
.valid-container {
display: flex;
max-width: 100%;
}
.valid-container :global([data-type="component"] > *) {
max-width: 100%;
}
.direction-row {
flex-direction: row;
}
.direction-column {
flex-direction: column;
}
/* Grow containers inside a row need 0 width 0 so that they ignore content */
/* The nested selector for data-type is the wrapper around all components */
.direction-row :global(> [data-type="component"] > .size-grow) {
width: 0;
}
.size-shrink {
flex: 0 1 auto;
}
.size-grow {
flex: 1 1 auto;
}
.direction-row.hAlign-left,
.direction-column.vAlign-top {
justify-content: flex-start;
}
.direction-row.hAlign-center,
.direction-column.vAlign-middle {
justify-content: center;
}
.direction-row.hAlign-right,
.direction-column.vAlign-bottom {
justify-content: flex-end;
}
.direction-row.hAlign-stretch,
.direction-column.vAlign-stretch {
justify-content: space-between;
}
.direction-row.vAlign-top,
.direction-column.hAlign-left {
align-items: flex-start;
}
.direction-row.vAlign-middle,
.direction-column.hAlign-center {
align-items: center;
}
.direction-row.vAlign-bottom,
.direction-column.hAlign-right {
align-items: flex-end;
}
.direction-row.vAlign-stretch,
.direction-column.hAlign-stretch {
align-items: stretch;
}
</style>

View File

@ -7,6 +7,7 @@
luceneSort, luceneSort,
luceneLimit, luceneLimit,
} from "./lucene" } from "./lucene"
import Placeholder from "./Placeholder.svelte"
export let dataSource export let dataSource
export let filter export let filter
@ -22,7 +23,8 @@
let loading = false let loading = false
// Loading flag for the initial load // Loading flag for the initial load
let loaded = false // Mark as loaded if we have no datasource so we don't stall forever
let loaded = !dataSource
let schemaLoaded = false let schemaLoaded = false
// Provider state // Provider state
@ -87,6 +89,7 @@
// bindings, but are used internally by other components // bindings, but are used internally by other components
id: $component?.id, id: $component?.id,
state: { query }, state: { query },
loaded,
} }
const getSortType = (schema, sortColumn) => { const getSortType = (schema, sortColumn) => {
@ -231,7 +234,11 @@
<ProgressCircle /> <ProgressCircle />
</div> </div>
{:else} {:else}
<slot /> {#if !$component.children}
<Placeholder />
{:else}
<slot />
{/if}
{#if paginate && internalTable} {#if paginate && internalTable}
<div class="pagination"> <div class="pagination">
<Pagination <Pagination

View File

@ -1,25 +1,30 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
const { styleable } = getContext("sdk") const { styleable, builderStore } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
export let type export let type
export let text = "" export let text
$: placeholder = $builderStore.inBuilder && !text
$: componentText = $builderStore.inBuilder
? text || "Placeholder text"
: text || ""
</script> </script>
{#if type === "h1"} {#if type === "h1"}
<h1 use:styleable={$component.styles}>{text}</h1> <h1 class:placeholder use:styleable={$component.styles}>{componentText}</h1>
{:else if type === "h2"} {:else if type === "h2"}
<h2 use:styleable={$component.styles}>{text}</h2> <h2 class:placeholder use:styleable={$component.styles}>{componentText}</h2>
{:else if type === "h3"} {:else if type === "h3"}
<h3 use:styleable={$component.styles}>{text}</h3> <h3 class:placeholder use:styleable={$component.styles}>{componentText}</h3>
{:else if type === "h4"} {:else if type === "h4"}
<h4 use:styleable={$component.styles}>{text}</h4> <h4 class:placeholder use:styleable={$component.styles}>{componentText}</h4>
{:else if type === "h5"} {:else if type === "h5"}
<h5 use:styleable={$component.styles}>{text}</h5> <h5 class:placeholder use:styleable={$component.styles}>{componentText}</h5>
{:else if type === "h6"} {:else if type === "h6"}
<h6 use:styleable={$component.styles}>{text}</h6> <h6 class:placeholder use:styleable={$component.styles}>{componentText}</h6>
{/if} {/if}
<style> <style>
@ -31,4 +36,9 @@
h6 { h6 {
white-space: pre-wrap; white-space: pre-wrap;
} }
.placeholder {
font-style: italic;
color: var(--grey-6);
}
</style> </style>

View File

@ -65,15 +65,11 @@
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
} gap: 16px;
.nav__menu > * {
margin-right: 16px;
} }
:global(.nav__menu > a) { :global(.nav__menu > a) {
font-size: 1.5em; font-size: 1.5em;
text-decoration: none; text-decoration: none;
margin-right: 16px;
} }
</style> </style>

View File

@ -0,0 +1,21 @@
<script>
import { getContext } from "svelte"
const { builderStore } = getContext("sdk")
const component = getContext("component")
export let text = $component.name || "Placeholder"
</script>
{#if $builderStore.inBuilder}
<div>
{text}
</div>
{/if}
<style>
div {
color: var(--grey-6);
font-size: var(--font-size-s);
}
</style>

View File

@ -1,5 +1,6 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import Placeholder from "./Placeholder.svelte"
export let dataProvider export let dataProvider
export let noRowsMessage export let noRowsMessage
@ -9,37 +10,32 @@
const context = getContext("context") const context = getContext("context")
$: rows = dataProvider?.rows ?? [] $: rows = dataProvider?.rows ?? []
$: loaded = dataProvider?.loaded ?? false $: loaded = dataProvider?.loaded ?? true
</script> </script>
<div use:styleable={$component.styles}> <div use:styleable={$component.styles}>
{#if rows.length > 0} {#if $component.empty}
{#if $component.children === 0 && $builderStore.inBuilder} <Placeholder />
<p><i class="ri-image-line" />Add some components to display.</p> {:else if rows.length > 0}
{:else} {#each rows as row}
{#each rows as row} <Provider data={row}>
<Provider data={row}> <slot />
<slot /> </Provider>
</Provider> {/each}
{/each}
{/if}
{:else if loaded && noRowsMessage} {:else if loaded && noRowsMessage}
<p><i class="ri-list-check-2" />{noRowsMessage}</p> <div class="noRows"><i class="ri-list-check-2" />{noRowsMessage}</div>
{/if} {/if}
</div> </div>
<style> <style>
p { .noRows {
margin: 0 var(--spacing-m);
background-color: var(--grey-2);
color: var(--grey-6); color: var(--grey-6);
font-size: var(--font-size-s); font-size: var(--font-size-s);
padding: var(--spacing-l); padding: var(--spacing-l);
border-radius: var(--border-radius-s);
display: grid; display: grid;
place-items: center; place-items: center;
} }
p i { .noRows i {
margin-bottom: var(--spacing-m); margin-bottom: var(--spacing-m);
font-size: 1.5rem; font-size: 1.5rem;
color: var(--grey-5); color: var(--grey-5);

View File

@ -7,7 +7,7 @@
export let imageUrl = "" export let imageUrl = ""
export let heading = "" export let heading = ""
export let subheading = "" export let subheading = ""
export let destinationUrl = "" export let destinationUrl = "/"
$: showImage = !!imageUrl $: showImage = !!imageUrl
</script> </script>

View File

@ -1,17 +1,29 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
const { styleable } = getContext("sdk") const { styleable, builderStore } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
export let text = "" export let text
$: placeholder = $builderStore.inBuilder && !text
$: componentText = $builderStore.inBuilder
? text || "Placeholder text"
: text || ""
</script> </script>
<p use:styleable={$component.styles}>{text}</p> <p use:styleable={$component.styles} class:placeholder>
{componentText}
</p>
<style> <style>
p { p {
display: inline-block; display: inline-block;
white-space: pre-wrap; white-space: pre-wrap;
} }
.placeholder {
font-style: italic;
color: var(--grey-6);
}
</style> </style>

View File

@ -1,6 +1,7 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { chart } from "svelte-apexcharts" import { chart } from "svelte-apexcharts"
import Placeholder from "../Placeholder.svelte"
const { styleable, builderStore } = getContext("sdk") const { styleable, builderStore } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
@ -11,8 +12,8 @@
{#if options} {#if options}
<div use:chart={options} use:styleable={$component.styles} /> <div use:chart={options} use:styleable={$component.styles} />
{:else if $builderStore.inBuilder} {:else if $builderStore.inBuilder}
<div class="placeholder" use:styleable={$component.styles}> <div use:styleable={$component.styles}>
Use the settings panel to build your chart. <Placeholder text="Use the settings panel to build your chart" />
</div> </div>
{/if} {/if}
@ -24,7 +25,4 @@
div :global(.apexcharts-yaxis-label, .apexcharts-xaxis-label) { div :global(.apexcharts-yaxis-label, .apexcharts-xaxis-label) {
fill: #aaa; fill: #aaa;
} }
div.placeholder {
padding: 10px;
}
</style> </style>

View File

@ -1,5 +1,5 @@
<script> <script>
import Placeholder from "./Placeholder.svelte" import Placeholder from "../Placeholder.svelte"
import FieldGroupFallback from "./FieldGroupFallback.svelte" import FieldGroupFallback from "./FieldGroupFallback.svelte"
import { getContext } from "svelte" import { getContext } from "svelte"
@ -36,6 +36,7 @@
<FieldGroupFallback> <FieldGroupFallback>
<div class="spectrum-Form-item" use:styleable={$component.styles}> <div class="spectrum-Form-item" use:styleable={$component.styles}>
<label <label
class:hidden={!label}
for={$fieldState?.fieldId} for={$fieldState?.fieldId}
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelPositionClass}`} class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelPositionClass}`}
> >
@ -43,15 +44,15 @@
</label> </label>
<div class="spectrum-Form-itemField"> <div class="spectrum-Form-itemField">
{#if !formContext} {#if !formContext}
<Placeholder>Form components need to be wrapped in a Form</Placeholder> <Placeholder text="Form components need to be wrapped in a form" />
{:else if !fieldState} {:else if !fieldState}
<Placeholder> <Placeholder
Add the Field setting to start using your component text="Add the Field setting to start using your component"
</Placeholder> />
{:else if fieldSchema?.type && fieldSchema?.type !== type} {:else if fieldSchema?.type && fieldSchema?.type !== type}
<Placeholder> <Placeholder
This Field setting is the wrong data type for this component text="This Field setting is the wrong data type for this component"
</Placeholder> />
{:else} {:else}
<slot /> <slot />
{#if $fieldState.error} {#if $fieldState.error}
@ -66,6 +67,9 @@
label { label {
white-space: nowrap; white-space: nowrap;
} }
label.hidden {
padding: 0;
}
.spectrum-Form-itemField { .spectrum-Form-itemField {
position: relative; position: relative;

View File

@ -1,8 +1,6 @@
<script> <script>
import { setContext, getContext, onMount } from "svelte" import { getContext } from "svelte"
import { writable, get } from "svelte/store" import InnerForm from "./InnerForm.svelte"
import { createValidatorFromConstraints } from "./validation"
import { generateID } from "../helpers"
export let dataSource export let dataSource
export let theme export let theme
@ -10,14 +8,7 @@
export let disabled = false export let disabled = false
export let actionType = "Create" export let actionType = "Create"
const component = getContext("component")
const context = getContext("context") const context = getContext("context")
const { styleable, API, Provider, ActionTypes } = getContext("sdk")
let loaded = false
let schema
let table
let fieldMap = {}
// Returns the closes data context which isn't a built in context // Returns the closes data context which isn't a built in context
const getInitialValues = (type, dataSource, context) => { const getInitialValues = (type, dataSource, context) => {
@ -41,167 +32,19 @@
return closestContext return closestContext
} }
// Use the closest data context as the initial form values $: initialValues = getInitialValues(actionType, dataSource, $context)
const initialValues = getInitialValues(actionType, dataSource, $context) $: resetKey = JSON.stringify(initialValues)
// Form state contains observable data about the form
const formState = writable({ values: initialValues, errors: {}, valid: true })
// Form API contains functions to control the form
const formApi = {
registerField: (field, defaultValue = null, fieldDisabled = false) => {
if (!field) {
return
}
// Auto columns are always disabled
const isAutoColumn = !!schema?.[field]?.autocolumn
// Create validation function based on field schema
const constraints = schema?.[field]?.constraints
const validate = createValidatorFromConstraints(constraints, field, table)
// Construct field object
fieldMap[field] = {
fieldState: makeFieldState(
field,
defaultValue,
disabled || fieldDisabled || isAutoColumn
),
fieldApi: makeFieldApi(field, defaultValue, validate),
fieldSchema: schema?.[field] ?? {},
}
// Set initial value
const initialValue = get(fieldMap[field].fieldState).value
formState.update(state => ({
...state,
values: {
...state.values,
[field]: initialValue,
},
}))
return fieldMap[field]
},
validate: () => {
const fields = Object.keys(fieldMap)
fields.forEach(field => {
const { fieldApi } = fieldMap[field]
fieldApi.validate()
})
return get(formState).valid
},
}
// Provide both form API and state to children
setContext("form", { formApi, formState, dataSource })
// Action context to pass to children
$: actions = [{ type: ActionTypes.ValidateForm, callback: formApi.validate }]
// Creates an API for a specific field
const makeFieldApi = (field, defaultValue, validate) => {
const setValue = (value, skipCheck = false) => {
const { fieldState } = fieldMap[field]
// Skip if the value is the same
if (!skipCheck && get(fieldState).value === value) {
return
}
const newValue = value == null ? defaultValue : value
const newError = validate ? validate(newValue) : null
// Update field state
fieldState.update(state => {
state.value = newValue
state.error = newError
return state
})
// Update form state
formState.update(state => {
state.values = { ...state.values, [field]: newValue }
if (newError) {
state.errors = { ...state.errors, [field]: newError }
} else {
delete state.errors[field]
}
state.valid = Object.keys(state.errors).length === 0
return state
})
return !newError
}
return {
setValue,
validate: () => {
const { fieldState } = fieldMap[field]
setValue(get(fieldState).value, true)
},
}
}
// Creates observable state data about a specific field
const makeFieldState = (field, defaultValue, fieldDisabled) => {
return writable({
field,
fieldId: `id-${generateID()}`,
value: initialValues[field] ?? defaultValue,
error: null,
disabled: fieldDisabled,
})
}
// Fetches the form schema from this form's dataSource, if one exists
const fetchSchema = async () => {
if (!dataSource?.tableId) {
schema = {}
table = null
} else {
table = await API.fetchTableDefinition(dataSource?.tableId)
if (table) {
if (dataSource?.type === "query") {
schema = {}
const params = table.parameters || []
params.forEach(param => {
schema[param.name] = { ...param, type: "string" }
})
} else {
schema = table.schema || {}
}
}
}
loaded = true
}
// Load the form schema on mount
onMount(fetchSchema)
</script> </script>
<Provider {#key resetKey}
{actions} <InnerForm
data={{ ...$formState.values, tableId: dataSource?.tableId }} {dataSource}
> {theme}
<div {size}
lang="en" {disabled}
dir="ltr" {actionType}
use:styleable={$component.styles} {initialValues}
class={`spectrum ${size || "spectrum--medium"} ${
theme || "spectrum--light"
}`}
> >
{#if loaded} <slot />
<slot /> </InnerForm>
{/if} {/key}
</div>
</Provider>
<style>
div {
padding: 20px;
position: relative;
background-color: var(--spectrum-alias-background-color-secondary);
}
</style>

View File

@ -0,0 +1,185 @@
<script>
import { setContext, getContext, onMount } from "svelte"
import { writable, get } from "svelte/store"
import { createValidatorFromConstraints } from "./validation"
import { generateID } from "../helpers"
export let dataSource
export let theme
export let size
export let disabled = false
export let actionType = "Create"
export let initialValues
const component = getContext("component")
const context = getContext("context")
const { styleable, API, Provider, ActionTypes } = getContext("sdk")
let loaded = false
let schema
let table
let fieldMap = {}
// Form state contains observable data about the form
const formState = writable({ values: initialValues, errors: {}, valid: true })
// Form API contains functions to control the form
const formApi = {
registerField: (field, defaultValue = null, fieldDisabled = false) => {
if (!field) {
return
}
// Auto columns are always disabled
const isAutoColumn = !!schema?.[field]?.autocolumn
// Create validation function based on field schema
const constraints = schema?.[field]?.constraints
const validate = createValidatorFromConstraints(constraints, field, table)
// Construct field object
fieldMap[field] = {
fieldState: makeFieldState(
field,
defaultValue,
disabled || fieldDisabled || isAutoColumn
),
fieldApi: makeFieldApi(field, defaultValue, validate),
fieldSchema: schema?.[field] ?? {},
}
// Set initial value
const initialValue = get(fieldMap[field].fieldState).value
formState.update(state => ({
...state,
values: {
...state.values,
[field]: initialValue,
},
}))
return fieldMap[field]
},
validate: () => {
const fields = Object.keys(fieldMap)
fields.forEach(field => {
const { fieldApi } = fieldMap[field]
fieldApi.validate()
})
return get(formState).valid
},
}
// Provide both form API and state to children
setContext("form", { formApi, formState, dataSource })
// Action context to pass to children
const actions = [
{ type: ActionTypes.ValidateForm, callback: formApi.validate },
]
// Creates an API for a specific field
const makeFieldApi = (field, defaultValue, validate) => {
const setValue = (value, skipCheck = false) => {
const { fieldState } = fieldMap[field]
// Skip if the value is the same
if (!skipCheck && get(fieldState).value === value) {
return
}
const newValue = value == null ? defaultValue : value
const newError = validate ? validate(newValue) : null
// Update field state
fieldState.update(state => {
state.value = newValue
state.error = newError
return state
})
// Update form state
formState.update(state => {
state.values = { ...state.values, [field]: newValue }
if (newError) {
state.errors = { ...state.errors, [field]: newError }
} else {
delete state.errors[field]
}
state.valid = Object.keys(state.errors).length === 0
return state
})
return !newError
}
return {
setValue,
validate: () => {
const { fieldState } = fieldMap[field]
setValue(get(fieldState).value, true)
},
}
}
// Creates observable state data about a specific field
const makeFieldState = (field, defaultValue, fieldDisabled) => {
return writable({
field,
fieldId: `id-${generateID()}`,
value: initialValues[field] ?? defaultValue,
error: null,
disabled: fieldDisabled,
})
}
// Fetches the form schema from this form's dataSource, if one exists
const fetchSchema = async () => {
if (!dataSource?.tableId) {
schema = {}
table = null
} else {
table = await API.fetchTableDefinition(dataSource?.tableId)
if (table) {
if (dataSource?.type === "query") {
schema = {}
const params = table.parameters || []
params.forEach(param => {
schema[param.name] = { ...param, type: "string" }
})
} else {
schema = table.schema || {}
}
}
}
loaded = true
}
// Load the form schema on mount
onMount(fetchSchema)
</script>
<Provider
{actions}
data={{ ...$formState.values, tableId: dataSource?.tableId }}
>
<div
lang="en"
dir="ltr"
use:styleable={$component.styles}
class={`spectrum ${size || "spectrum--medium"} ${
theme || "spectrum--light"
}`}
>
{#if loaded}
<slot />
{/if}
</div>
</Provider>
<style>
div {
padding: 20px;
position: relative;
background-color: var(--spectrum-alias-background-color-secondary);
}
</style>

View File

@ -1,30 +0,0 @@
<script>
import { getContext } from "svelte"
const { builderStore } = getContext("sdk")
</script>
{#if $builderStore.inBuilder}
<div>
<slot />
</div>
{/if}
<style>
div {
height: var(
--spectrum-alias-item-height-m,
var(--spectrum-global-dimension-size-400)
);
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
color: var(--spectrum-global-color-gray-600);
font-size: var(
--spectrum-alias-item-text-size-m,
var(--spectrum-global-dimension-font-size-100)
);
font-style: var(--spectrum-global-font-style-italic, italic);
font-weight: var(--spectrum-global-font-weight-regular, 400);
}
</style>

View File

@ -8,30 +8,6 @@ export const capitalise = string => {
return string.substring(0, 1).toUpperCase() + string.substring(1) return string.substring(0, 1).toUpperCase() + string.substring(1)
} }
/**
* Svelte action to set CSS variables on a DOM node.
*
* @param node
* @param props
*/
export const cssVars = (node, props) => {
Object.entries(props).forEach(([key, value]) => {
node.style.setProperty(`--${key}`, value)
})
return {
update(new_props) {
Object.entries(new_props).forEach(([key, value]) => {
node.style.setProperty(`--${key}`, value)
delete props[key]
})
Object.keys(props).forEach(name => node.style.removeProperty(`--${name}`))
props = new_props
},
}
}
/** /**
* Generates a short random ID. * Generates a short random ID.
* This is "nanoid" but rollup was derping attempting to bundle it, so the * This is "nanoid" but rollup was derping attempting to bundle it, so the

View File

@ -10,6 +10,10 @@ import "@spectrum-css/page/dist/index-vars.css"
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js" import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js"
loadSpectrumIcons() loadSpectrumIcons()
// Non user-facing components
export { default as Placeholder } from "./Placeholder.svelte"
// User facing components
export { default as container } from "./Container.svelte" export { default as container } from "./Container.svelte"
export { default as dataprovider } from "./DataProvider.svelte" export { default as dataprovider } from "./DataProvider.svelte"
export { default as screenslot } from "./ScreenSlot.svelte" export { default as screenslot } from "./ScreenSlot.svelte"

View File

@ -2,6 +2,59 @@
# yarn lockfile v1 # yarn lockfile v1
"@adobe/spectrum-css-workflow-icons@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.1.tgz#7e2cb3fcfb5c8b12d7275afafbb6ec44913551b4"
integrity sha512-uVgekyBXnOVkxp+CUssjN/gefARtudZC8duEn1vm0lBQFwGRZFlDEzU1QC+aIRWCrD1Z8OgRpmBYlSZ7QS003w==
"@budibase/bbui@^0.9.41":
version "0.9.45"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.45.tgz#c37e84ffbf99daf0ebaa5c19b9109e7222463591"
integrity sha512-+wojKap0kieYoeCqhah1V9EswVbR+Pg1k9Nlw1n/zTsuGtjA0kUH652yKjz7zNzZgFm+s5oYS1CoPieIMehhSw==
dependencies:
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
"@spectrum-css/actionbutton" "^1.0.1"
"@spectrum-css/actiongroup" "^1.0.1"
"@spectrum-css/avatar" "^3.0.2"
"@spectrum-css/button" "^3.0.1"
"@spectrum-css/buttongroup" "^3.0.2"
"@spectrum-css/checkbox" "^3.0.2"
"@spectrum-css/dialog" "^3.0.1"
"@spectrum-css/divider" "^1.0.1"
"@spectrum-css/dropzone" "^3.0.2"
"@spectrum-css/fieldgroup" "^3.0.2"
"@spectrum-css/fieldlabel" "^3.0.1"
"@spectrum-css/icon" "^3.0.1"
"@spectrum-css/illustratedmessage" "^3.0.2"
"@spectrum-css/inputgroup" "^3.0.2"
"@spectrum-css/label" "^2.0.10"
"@spectrum-css/link" "^3.1.1"
"@spectrum-css/menu" "^3.0.1"
"@spectrum-css/modal" "^3.0.1"
"@spectrum-css/pagination" "^3.0.3"
"@spectrum-css/picker" "^1.0.1"
"@spectrum-css/popover" "^3.0.1"
"@spectrum-css/progressbar" "^1.0.2"
"@spectrum-css/progresscircle" "^1.0.2"
"@spectrum-css/radio" "^3.0.2"
"@spectrum-css/search" "^3.0.2"
"@spectrum-css/sidenav" "^3.0.2"
"@spectrum-css/statuslight" "^3.0.2"
"@spectrum-css/switch" "^1.0.2"
"@spectrum-css/table" "^3.0.1"
"@spectrum-css/tabs" "^3.0.1"
"@spectrum-css/tags" "^3.0.2"
"@spectrum-css/textfield" "^3.0.1"
"@spectrum-css/toast" "^3.0.1"
"@spectrum-css/tooltip" "^3.0.3"
"@spectrum-css/treeview" "^3.0.2"
"@spectrum-css/typography" "^3.0.1"
"@spectrum-css/underlay" "^2.0.9"
"@spectrum-css/vars" "^3.0.1"
dayjs "^1.10.4"
svelte-flatpickr "^3.1.0"
svelte-portal "^1.0.0"
"@rollup/pluginutils@^4.1.0": "@rollup/pluginutils@^4.1.0":
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.0.tgz#0dcc61c780e39257554feb7f77207dceca13c838" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.0.tgz#0dcc61c780e39257554feb7f77207dceca13c838"
@ -10,31 +63,218 @@
estree-walker "^2.0.1" estree-walker "^2.0.1"
picomatch "^2.2.2" picomatch "^2.2.2"
"@spectrum-css/page@^3.0.1": "@spectrum-css/actionbutton@^1.0.1":
version "3.0.1" version "1.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.1.tgz#5e1c3dd5b1a1ee591f9d636b75f03665f542d846" resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.3.tgz#8f7342a69b303c5acdcfa0a59f5e9267b9f3cb30"
integrity sha512-LAlKF8km5BlsGPpZ2SNtwKOQIHn1lz0X93aczGZVZceOg73O4gyeoT5cx4vi1z+KtBRY5VMDWx3XgGtUwwjqwA== integrity sha512-P9qoCPSiZ1SB6ZYqK5hub0vGty00YYqonQE0KTjtb1i+T1nYR/87vWqVPERx9j63uhgZncjwFYaThTvRqye7eQ==
dependencies:
"@spectrum-css/vars" "^3.0.1"
"@spectrum-css/vars@^3.0.1": "@spectrum-css/actiongroup@^1.0.1":
version "3.0.1" version "1.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.1.tgz#561fd69098f896a647242dd8d6108af603bfa31e" resolved "https://registry.yarnpkg.com/@spectrum-css/actiongroup/-/actiongroup-1.0.3.tgz#4713ce65e6f5c72c404a7b638fbc3b4fd7e3874f"
integrity sha512-l4oRcCOqInChYXZN6OQhpe3isk6l4OE6Ys8cgdlsiKp53suNoQxyyd9p/eGRbCjZgH3xQ8nK0t4DHa7QYC0S6w== integrity sha512-NlB9Q4ZlWixXxymoPIYG6g2hkwAGKFodHWPFfxHD8ddkjXWRB9G2akUP7cfsJ4DcYn4VisUORCOYQwqDiSmboQ==
"@spectrum-css/avatar@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/avatar/-/avatar-3.0.2.tgz#4f1826223eae330e64b6d3cc899e9bc2e98dac95"
integrity sha512-wEczvSqxttTWSiL3cOvXV/RmGRwSkw2w6+slcHhnf0kb7ovymMM+9oz8vvEpEsSeo5u598bc+7ktrKFpAd6soQ==
"@spectrum-css/button@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.3.tgz#2df1efaab6c7e0b3b06cb4b59e1eae59c7f1fc84"
integrity sha512-6CnLPqqtaU/PcSSIGeGRi0iFIIxIUByYLKFO6zn5NEUc12KQ28dJ4PLwB6WBa0L8vRoAGlnWWH2ZZweTijbXgg==
"@spectrum-css/buttongroup@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/buttongroup/-/buttongroup-3.0.3.tgz#719d868845ac9d2c4f939c1b9f6044507902d5aa"
integrity sha512-eXl8U4QWMWXqyTu654FdQdEGnmczgOYlpIFSHyCMVjhtPqZp2xwnLFiGh6LKw+bLio6eeOZ0L+vpk1GcoYqgkw==
"@spectrum-css/checkbox@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/checkbox/-/checkbox-3.0.3.tgz#8577067fc8b97e4609f92bd242364937a533a7bb"
integrity sha512-QVG9uMHq+lh70Dh6mDNnY+vEvNz2p7VC6xgLfDYfijp2qeiqYPq72fQK6p/SiyqPk96ZACzNRwgeROU6Xf6Wgg==
"@spectrum-css/dialog@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/dialog/-/dialog-3.0.3.tgz#7715a4ea435e753afb623d99ca5917ed1bcd6f34"
integrity sha512-AhmKgfRIVyTe3ABiJ8lLUQL34VB/H6fN16na2LlbDRJvyRMzkdN1Xf2i6U3f4OMd3qQ8Gm5xat4BvMxIQPBAUQ==
"@spectrum-css/divider@^1.0.1":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/divider/-/divider-1.0.3.tgz#639e2ebaa0834efa40f42397668bbd5c153ea385"
integrity sha512-Zy4Rn40w8UtzMh3wx/U9+CepSCpm1aOCGftHgWDub0XZuVTzh0c1WwyzTuLCx2Hf21z5VRGNiDh8bGEEzSbtNA==
dependencies:
"@spectrum-css/vars" "^3.0.2"
"@spectrum-css/dropzone@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/dropzone/-/dropzone-3.0.3.tgz#aee71697a2c195947599d7541b858c3c198741dc"
integrity sha512-ujrswdtB6bHigklyHsm6zomFd6rUnKJ3xVVRjroVF4+ouK4DxK5tX0iVd0EW6AOfOjx4Cc28uyFot3fpxp+MQw==
"@spectrum-css/fieldgroup@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/fieldgroup/-/fieldgroup-3.0.3.tgz#85d85da048d08200f25ceab378026dd2b11e0bb2"
integrity sha512-wXUXTXN1CPnR7M4Ltd+3uh7BfcNGUV1+Xe0/h0tCpq/j+S2Sd4xo7/pUMdU19sIDrAAtpEFp1tt+nouHcU5HGQ==
"@spectrum-css/fieldlabel@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.3.tgz#f73c04d20734d4718ffb620dc46458904685b449"
integrity sha512-nEvIkEXCD5n4fW67Unq6Iu7VXoauEd/JGpfTY02VsC5p4FJLnwKfPDbJUuUsqClAxqw7nAsmXVKtn4zQFf5yPQ==
"@spectrum-css/icon@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.3.tgz#5c612822380927087aebd526855d82ed2c3e2cba"
integrity sha512-hyloKOejPCXhP3MBNsm3SjR9j8xT1R1S19p32KW/0Qhj+VMUtfyEPmevyFptpn7wcovQccdl/vZVIVDuML/imw==
"@spectrum-css/illustratedmessage@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/illustratedmessage/-/illustratedmessage-3.0.2.tgz#6a480be98b027e050b086e7899e40d87adb0a8c0"
integrity sha512-dqnE8X27bGcO0HN8+dYx8O4o0dNNIAqeivOzDHhe2El+V4dTzMrNIerF6G0NLm3GjVf6XliwmitsZK+K6FmbtA==
"@spectrum-css/inputgroup@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.3.tgz#00c9a370ddc2c55cf0f37dd6069faa9501fd7eb5"
integrity sha512-FqRJTiLL7jiGfzDVXWUGVLqHryJjCcqQIrqAi+Tq0oenapzsBe2qc/zIrKgh2wtMI+NTIBLXTECvij3L1HlqAg==
"@spectrum-css/label@^2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@spectrum-css/label/-/label-2.0.10.tgz#2368651d7636a19385b5d300cdf6272db1916001"
integrity sha512-xCbtEiQkZIlLdWFikuw7ifDCC21DOC/KMgVrrVJHXFc4KRQe9LTZSqmGF3tovm+CSq1adE59mYoTbojVQ9YuEQ==
"@spectrum-css/link@^3.1.1":
version "3.1.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.3.tgz#b0e560a7e0acdb4a2b329b6669696aa3438f5993"
integrity sha512-8Pmy5d73MwKcATTPaj+fSrZYnIw7GmfX40AvpC1xx5LauOxsbUb4AVNp1kn2H8rrOBmxF7czyhoBBhEiv66QUg==
"@spectrum-css/menu@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.3.tgz#46a9b221bb5f470a2f8a934bdfd512d84d2fdc4d"
integrity sha512-qKA9J/MrikNKIpCEHsAazG2vY3im5tjGCmo6p9Pdnu8/aQMsiuZDHZayukeCttJUZkrb9guDVL9OIHlK5RZvcQ==
"@spectrum-css/modal@^3.0.1":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/modal/-/modal-3.0.2.tgz#58b6621cab65f90788d310374f40df1f7090473f"
integrity sha512-YnIivJhoaao7Otu+HV7sgebPyFbO6sd/oMvTN/Rb2wwgnaMnIIuIRdGandSrcgotN2uNgs+P0knG6mv/xA1/dg==
"@spectrum-css/page@^3.0.1":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.2.tgz#8f0c03d25f5565fb13115541a8fcaf0e1d3a8ee0"
integrity sha512-lCXWjonLwYBg8FHUEkiFX0Mmfk+9Uivgvxq0DTulPlWrJcULTwjaOiY28/YBz7Fy1wuv/0KORbkPRALpYldBZg==
dependencies:
"@spectrum-css/vars" "^3.0.2"
"@spectrum-css/pagination@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/pagination/-/pagination-3.0.3.tgz#b204c3ada384c4af751a354bc428346d82eeea65"
integrity sha512-OJ/v9GeNXJOZ9Yr9LDBYPrR2NCiLOWP9wANT/a5sqFuugRnQbn/HYMnRp9TBxwpDY6ihaPo0T/wi7kLiAJFdDw==
"@spectrum-css/picker@^1.0.1":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.3.tgz#21379bcf8ae94277deeb6ad65dcd9e2bbfacb487"
integrity sha512-oHLGxBx5BwVCSGo7/T1C9PTHX1+/5AmVjyLiTJ4UoIdSJmOERw9YcRZbcGZgBJNWbxcjr4TyGtwj1EcSjEy97w==
"@spectrum-css/popover@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.3.tgz#6fb69873474fb968afb738eacb9e121f93e83a09"
integrity sha512-KvmXv4TV19FBx39KfmgVlDYtvtBqv/8RRK7RRLDDHGViTxZtShjVsVpwIgfkfgn4iJztCnXpWzFqRXWUu2XCpQ==
"@spectrum-css/progressbar@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/progressbar/-/progressbar-1.0.3.tgz#f70bcc38a2a21cff2f422ec825724ebbb9455e67"
integrity sha512-vJHplefUuy8+NjCw1X7fLbqHVGNVBpvGFXNAeaIj4SFf4ygxiUq/5c9iRhhsCQixEsJlfD/b7BnGXU7BUDkr6Q==
"@spectrum-css/progresscircle@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/progresscircle/-/progresscircle-1.0.2.tgz#258ea9170fb70f795edda03e38a61d93bef4487c"
integrity sha512-JLULpyzjIY95lzlWR1yE1gv4l1K6p+scQ+edmuZZUHBzwM3pUtkvHJmUlA9TYdResUYW6Uka60VRdY6lZ8gnFQ==
"@spectrum-css/radio@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/radio/-/radio-3.0.3.tgz#25c3bc5e9c30a8a8ae728717b7c7fb736cdae640"
integrity sha512-LaLGfz/eGNR2iyqouXYILIA+pKRqF769iPdwM0REm5RpWvMQDD7rPZ/kWlg18owjaFsyllEp25gEjmhRJIIVOw==
"@spectrum-css/search@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/search/-/search-3.0.3.tgz#3415dc106aca0d5dd996e87084a1b47c2b95a882"
integrity sha512-kdLpKTt0obljuhS1q1tukujRlvSs8sBwij76D4Qp8KpMzwePfZyvv1kYzuWPNZfTeISxWsmyZ6Wxd1uvzjn+UA==
"@spectrum-css/sidenav@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/sidenav/-/sidenav-3.0.3.tgz#132141fbd2500a927c312fa4e1d712c438b3d597"
integrity sha512-cQ+CgwjxGftrcc79i1XnGd66QTl7H7zopSU0UTV4Qq7hvqfrjjWxfZ6b+3tezrrhNlDope1ff9o8sm67PsPXtg==
"@spectrum-css/statuslight@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/statuslight/-/statuslight-3.0.2.tgz#dc54b6cd113413dcdb909c486b5d7bae60db65c5"
integrity sha512-xodB8g8vGJH20XmUj9ZsPlM1jHrGeRbvmVXkz0q7YvQrYAhim8pP3W+XKKZAletPFAuu8cmUOc6SWn6i4X4z6w==
"@spectrum-css/switch@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/switch/-/switch-1.0.2.tgz#f0b4c69271964573e02b08e90998096e49e1de44"
integrity sha512-zqmHpgWPNg1gEwdUNFYV3CBX5JaeALfIqcJIxE0FLZqr9d1C4+oLE0ItIFzt1bwr4bFAOmkEpvtiY+amluzGxQ==
"@spectrum-css/table@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/table/-/table-3.0.3.tgz#7f7f19905ef3275cbf907ce3a5818e63c30b2caf"
integrity sha512-nxwzVjLPsXoY/v4sdxOVYLcC+cEbGgJyLcLclT5LT9MGSbngFeUMJzzVR4EvehzuN4dH7hrATG7Mbuq29Mf0Hg==
"@spectrum-css/tabs@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/tabs/-/tabs-3.0.3.tgz#51dd6f168c897b0fdc3a7e9f901df7bd2288b4fc"
integrity sha512-iLP1I72bJWz9APdQB1jiw+pOv5a7N+hYOCJvRoc56Us/hJKVzowkyGRe3uH+8v36nCG9bHxiAQNLoU8eXisVrg==
"@spectrum-css/tags@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/tags/-/tags-3.0.3.tgz#fc76d2735cdc442de91b7eb3bee49a928c0767ac"
integrity sha512-SL8vPxVDfWcY5VdIuyl0TImEXcOU1I7yCyXkk7MudMwfnYs81FaIyY32hFV9OHj0Tz/36UzRzc7AVMSuRQ53pw==
"@spectrum-css/textfield@^3.0.1":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.2.tgz#907f62d2dc82852dd6236a820be99e252b531631"
integrity sha512-nkFgAb0cP4jUodkUBErMNfyF78jJLtgL1Mrr9/rvGpGobo10IAbb8zZY4CkZ64o8XmMy/85+wZTKcx+KHatqpg==
"@spectrum-css/toast@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/toast/-/toast-3.0.3.tgz#97c1527384707600832ecda35643ed304615250f"
integrity sha512-CjLeaMs+cjUXojCCRtbj0YkD2BoZW16kjj2o5omkEpUTjA34IJ8xJ1a+CCtDILWekhXvN0MBN4sbumcnwcnx8w==
"@spectrum-css/tooltip@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/tooltip/-/tooltip-3.0.3.tgz#26b8ca3b3d30e29630244d85eb4fc11d0c841281"
integrity sha512-ztRF7WW1FzyNavXBRc+80z67UoOrY9wl3cMYsVD3MpDnyxdzP8cjza1pCcolKBaFqRTcQKkxKw3GWtGICRKR5A==
"@spectrum-css/treeview@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/treeview/-/treeview-3.0.3.tgz#aeda5175158b9f8d7529cb2b394428eb2a428046"
integrity sha512-D5gGzZC/KtRArdx86Mesc9+99W9nTbUOeyYGqoJoAfJSOttoT6Tk5CrDvlCmAqjKf5rajemAkGri1ChqvUIwkw==
"@spectrum-css/typography@^3.0.1":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.2.tgz#ea3ca0a60e18064527819d48c8c4364cab4fcd38"
integrity sha512-5ZOLmQe0edzsDMyhghUd4hBb5uxGsFrxzf+WasfcUw9klSfTsRZ09n1BsaaWbgrLjlMQ+EEHS46v5VNo0Ms2CA==
"@spectrum-css/underlay@^2.0.9":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@spectrum-css/underlay/-/underlay-2.0.10.tgz#8b75b646605a311850f6620caa18d4996cd64ed7"
integrity sha512-PmsmkzeGD/rY4pp3ILXHt9w8BW7uaEqXe08hQRS7rGki7wqCpG4mE0/8N3yEcA3QxWQclmG9gdkg5uz6wMmYzA==
"@spectrum-css/vars@^3.0.1", "@spectrum-css/vars@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.2.tgz#ea9062c3c98dfc6ba59e5df14a03025ad8969999"
integrity sha512-vzS9KqYXot4J3AEER/u618MXWAS+IoMvYMNrOoscKiLLKYQWenaueakUWulFonToPd/9vIpqtdbwxznqrK5qDw==
"@sveltejs/vite-plugin-svelte@^1.0.0-next.5": "@sveltejs/vite-plugin-svelte@^1.0.0-next.5":
version "1.0.0-next.5" version "1.0.0-next.10"
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.5.tgz#8cf608f7a3c33dfa5b648397aae1ba90e6a4883f" resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.10.tgz#15526067ea2edd334420b1e14602e29b370be25e"
integrity sha512-RVjafsqziWwnQm8VEy2y0qNaugNDvRd8tTaCt9rjgQkqaS/BDiyDCluXxA28PRC+ddZjvwUeq9k+0EfbLVObfg== integrity sha512-ImvxbhPePm2hWNTKBSA3LHAYGwiEjHjvvgfPLXm4R87sfZ+BMXql9jBmDpzUC/URBLT4BB3Jxos/i523qkJBHg==
dependencies: dependencies:
"@rollup/pluginutils" "^4.1.0" "@rollup/pluginutils" "^4.1.0"
chalk "^4.1.0" chalk "^4.1.1"
debug "^4.3.2" debug "^4.3.2"
hash-sum "^2.0.0" hash-sum "^2.0.0"
require-relative "^0.8.7" require-relative "^0.8.7"
slash "^3.0.0" slash "^4.0.0"
source-map "^0.7.3" source-map "^0.7.3"
svelte-hmr "^0.13.3" svelte-hmr "^0.14.2"
ansi-styles@^4.1.0: ansi-styles@^4.1.0:
version "4.3.0" version "4.3.0"
@ -44,9 +284,9 @@ ansi-styles@^4.1.0:
color-convert "^2.0.1" color-convert "^2.0.1"
apexcharts@^3.19.2, apexcharts@^3.22.1: apexcharts@^3.19.2, apexcharts@^3.22.1:
version "3.26.0" version "3.27.1"
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.26.0.tgz#a78abc108b2e1b3086a738f0ec7c98e292f4a14b" resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.27.1.tgz#b0e6dd3b3ace028f29b32fcd88e19a2420a18089"
integrity sha512-zdYHs3k3tgmCn1BpYLj7rhGEndBYF33Pq1+g0ora37xAr+3act5CJrpdXM2jx2boVUyXgavoSp6sa8WpK7RkSA== integrity sha512-2pfw3pxeWhI0ap5lfxyfGNGoGScfEwfc8XnTpbnzgRdr1AOH5JJN9hh3MvfwrC9TQQfJYC2TZc8P/q9qXUj1bQ==
dependencies: dependencies:
svg.draggable.js "^2.2.2" svg.draggable.js "^2.2.2"
svg.easing.js "^2.0.0" svg.easing.js "^2.0.0"
@ -55,10 +295,10 @@ apexcharts@^3.19.2, apexcharts@^3.22.1:
svg.resize.js "^1.4.3" svg.resize.js "^1.4.3"
svg.select.js "^3.0.1" svg.select.js "^3.0.1"
chalk@^4.1.0: chalk@^4.1.1:
version "4.1.0" version "4.1.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
dependencies: dependencies:
ansi-styles "^4.1.0" ansi-styles "^4.1.0"
supports-color "^7.1.0" supports-color "^7.1.0"
@ -80,7 +320,7 @@ colorette@^1.2.2:
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
dayjs@^1.10.5: dayjs@^1.10.4, dayjs@^1.10.5:
version "1.10.5" version "1.10.5"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.5.tgz#5600df4548fc2453b3f163ebb2abbe965ccfb986" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.5.tgz#5600df4548fc2453b3f163ebb2abbe965ccfb986"
integrity sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g== integrity sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g==
@ -92,10 +332,10 @@ debug@^4.3.2:
dependencies: dependencies:
ms "2.1.2" ms "2.1.2"
esbuild@^0.9.3: esbuild@^0.12.5:
version "0.9.7" version "0.12.8"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.9.7.tgz#ea0d639cbe4b88ec25fbed4d6ff00c8d788ef70b" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.8.tgz#ac90da77cb3bfbf49ab815200bcef7ffe1a3348f"
integrity sha512-VtUf6aQ89VTmMLKrWHYG50uByMF4JQlVysb8dmg6cOgW8JnFCipmz7p+HNBl+RR3LLCuBxFGVauAe2wfnF9bLg== integrity sha512-sx/LwlP/SWTGsd9G4RlOPrXnIihAJ2xwBUmzoqe2nWwbXORMQWtAGNJNYLBJJqa3e9PWvVzxdrtyFZJcr7D87g==
estree-walker@^2.0.1: estree-walker@^2.0.1:
version "2.0.2" version "2.0.2"
@ -135,9 +375,9 @@ hash-sum@^2.0.0:
integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg== integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==
is-core-module@^2.2.0: is-core-module@^2.2.0:
version "2.2.0" version "2.4.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1"
integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==
dependencies: dependencies:
has "^1.0.3" has "^1.0.3"
@ -146,29 +386,29 @@ ms@2.1.2:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
nanoid@^3.1.22: nanoid@^3.1.23:
version "3.1.22" version "3.1.23"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
path-parse@^1.0.6: path-parse@^1.0.6:
version "1.0.6" version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
picomatch@^2.2.2: picomatch@^2.2.2:
version "2.2.2" version "2.3.0"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
postcss@^8.2.1: postcss@^8.3.0:
version "8.2.9" version "8.3.0"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.9.tgz#fd95ff37b5cee55c409b3fdd237296ab4096fba3" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.0.tgz#b1a713f6172ca427e3f05ef1303de8b65683325f"
integrity sha512-b+TmuIL4jGtCHtoLi+G/PisuIl9avxs8IZMSmlABRwNz5RLUUACrC+ws81dcomz1nRezm5YPdXiMEzBEKgYn+Q== integrity sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ==
dependencies: dependencies:
colorette "^1.2.2" colorette "^1.2.2"
nanoid "^3.1.22" nanoid "^3.1.23"
source-map "^0.6.1" source-map-js "^0.6.2"
require-relative@^0.8.7: require-relative@^0.8.7:
version "0.8.7" version "0.8.7"
@ -184,21 +424,21 @@ resolve@^1.19.0:
path-parse "^1.0.6" path-parse "^1.0.6"
rollup@^2.38.5: rollup@^2.38.5:
version "2.44.0" version "2.51.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.44.0.tgz#8da324d1c4fd12beef9ae6e12f4068265b6d95eb" resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.51.1.tgz#87bcd4095fe79b14c9bec0edc7ffa44e4827f793"
integrity sha512-rGSF4pLwvuaH/x4nAS+zP6UNn5YUDWf/TeEU5IoXSZKBbKRNTCI3qMnYXKZgrC0D2KzS2baiOZt1OlqhMu5rnQ== integrity sha512-8xfDbAtBleXotb6qKEHWuo/jkn94a9dVqGc7Rwl3sqspCVlnCfbRek7ldhCARSi7h32H0xR4QThm1t9zHN+3uw==
optionalDependencies: optionalDependencies:
fsevents "~2.3.1" fsevents "~2.3.1"
slash@^3.0.0: slash@^4.0.0:
version "3.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
source-map@^0.6.1: source-map-js@^0.6.2:
version "0.6.1" version "0.6.2"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
source-map@^0.7.3: source-map@^0.7.3:
version "0.7.3" version "0.7.3"
@ -220,16 +460,21 @@ svelte-apexcharts@^1.0.2:
apexcharts "^3.19.2" apexcharts "^3.19.2"
svelte-flatpickr@^3.1.0: svelte-flatpickr@^3.1.0:
version "3.1.0" version "3.1.1"
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.1.0.tgz#ad83588430dbd55196a1a258b8ba27e7f9c1ee37" resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.1.1.tgz#c79da72a4acab252ed39bac8168f30f79db3ff80"
integrity sha512-zKyV+ukeVuJ8CW0Ing3T19VSekc4bPkou/5Riutt1yATrLvSsanNqcgqi7Q5IePvIoOF9GJ5OtHvn1qK9Wx9BQ== integrity sha512-z/sV/AMTqyybeWPCjoWYUOyuMmu9qX2c2f2cRqYos9K9sM5L3CqagtvfVy7GZQrnEYe+K7l/gGDCAyxJNp2+gA==
dependencies: dependencies:
flatpickr "^4.5.2" flatpickr "^4.5.2"
svelte-hmr@^0.13.3: svelte-hmr@^0.14.2:
version "0.13.3" version "0.14.4"
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.13.3.tgz#fba5739b477ea44caf70e542a24a4352bee2b897" resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.14.4.tgz#b7ef2bfeef23916e0e912828c50645ca572ac355"
integrity sha512-gagW62pLQ2lULmvNA3pIZu9pBCYOaGu3rQikUOv6Nokz5VxUgT9/mQLfMxj9phDEKHCg/lgr3i6PkqZDbO9P2Q== integrity sha512-kItFF7vqzStckSigoFmMnxJpTOdB9TWnQAW6Js+yAB4277tLbJIIE5KBlGHNmJNpA7MguqidsPB27Uw5UzQPCA==
svelte-portal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
svelte@^3.38.2: svelte@^3.38.2:
version "3.38.2" version "3.38.2"
@ -292,12 +537,12 @@ svg.select.js@^3.0.1:
svg.js "^2.6.5" svg.js "^2.6.5"
vite@^2.1.5: vite@^2.1.5:
version "2.1.5" version "2.3.7"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.1.5.tgz#4857da441c62f7982c83cbd5f42a00330f20c9c1" resolved "https://registry.yarnpkg.com/vite/-/vite-2.3.7.tgz#3023892419367465e1af1739578f8663d04243b2"
integrity sha512-tYU5iaYeUgQYvK/CNNz3tiJ8vYqPWfCE9IQ7K0iuzYovWw7lzty7KRYGWwV3CQPh0NKxWjOczAqiJsCL0Xb+Og== integrity sha512-Y0xRz11MPYu/EAvzN94+FsOZHbSvO6FUvHv127CyG7mV6oDoay2bw+g5y9wW3Blf8OY3chaz3nc/DcRe1IQ3Nw==
dependencies: dependencies:
esbuild "^0.9.3" esbuild "^0.12.5"
postcss "^8.2.1" postcss "^8.3.0"
resolve "^1.19.0" resolve "^1.19.0"
rollup "^2.38.5" rollup "^2.38.5"
optionalDependencies: optionalDependencies:

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "0.9.45", "version": "0.9.48",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.9.45", "version": "0.9.48",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {
@ -21,8 +21,8 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.45", "@budibase/auth": "^0.9.48",
"@budibase/string-templates": "^0.9.45", "@budibase/string-templates": "^0.9.48",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"aws-sdk": "^2.811.0", "aws-sdk": "^2.811.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",

View File

@ -3,6 +3,9 @@ const { EmailTemplatePurpose } = require("../../../constants")
const nodemailer = require("nodemailer") const nodemailer = require("nodemailer")
const fetch = require("node-fetch") const fetch = require("node-fetch")
// need a longer timeout for getting these
jest.setTimeout(30000)
describe("/api/admin/email", () => { describe("/api/admin/email", () => {
let request = setup.getRequest() let request = setup.getRequest()
let config = setup.getConfig() let config = setup.getConfig()