1
0
Fork 0
mirror of synced 2024-07-07 23:35:49 +12:00

Merge branch 'develop' of github.com:Budibase/budibase into default-field-values

This commit is contained in:
Andrew Kingston 2021-08-04 17:21:47 +01:00
commit c3dde3e5f4
66 changed files with 526 additions and 246 deletions

View file

@ -0,0 +1,14 @@
---
name: Feature Request
about: Request a new budibase feature or enhancement
title: ''
labels: enhancement
assignees: ''
---
**Describe the feature request**
A clear and concise description of what the feature request.
**Screenshots**
If applicable, add screenshots to help explain your problem.

View file

@ -57,7 +57,7 @@
- **Open source and extensible.** Budibase is open-source - licensed as GPL v3. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience. - **Open source and extensible.** Budibase is open-source - licensed as GPL v3. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience.
- **Load data or start from scratch.** Budibase pulls in data from multiple sources, including MongoDB, CouchDB, PostgreSQL, mySQL, Airtable, S3, DyanmoDB, or a REST API. And unlike other platforms, with Budibase you can start from scratch and create business apps with no data sources. [Request new data sources](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). - **Load data or start from scratch.** Budibase pulls in data from multiple sources, including MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB, or a REST API. And unlike other platforms, with Budibase you can start from scratch and create business apps with no data sources. [Request new data sources](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
- **Design and build apps with powerful pre-made components.** Budibase comes out of the box with beautifully designed, powerful components which you can use like building blocks to build your UI. We also expose a lot of your favourite CSS styling options so you can go that extra creative mile. [Request new component](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). - **Design and build apps with powerful pre-made components.** Budibase comes out of the box with beautifully designed, powerful components which you can use like building blocks to build your UI. We also expose a lot of your favourite CSS styling options so you can go that extra creative mile. [Request new component](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).

View file

@ -1,5 +1,5 @@
{ {
"version": "0.9.87-alpha.7", "version": "0.9.96",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/auth", "name": "@budibase/auth",
"version": "0.9.87-alpha.7", "version": "0.9.96",
"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

@ -66,6 +66,7 @@ module.exports = (noAuthPatterns = [], opts) => {
} }
} }
if (error) { if (error) {
console.error("Auth Error", error)
// remove the cookie as the user does not exist anymore // remove the cookie as the user does not exist anymore
clearCookie(ctx, Cookies.Auth) clearCookie(ctx, Cookies.Auth)
} else { } else {

View file

@ -6,7 +6,6 @@ const { authError } = require("./utils")
const { newid } = require("../../hashing") const { newid } = require("../../hashing")
const { createASession } = require("../../security/sessions") const { createASession } = require("../../security/sessions")
const { getGlobalUserByEmail } = require("../../utils") const { getGlobalUserByEmail } = require("../../utils")
const fetch = require("node-fetch")
/** /**
* Common authentication logic for third parties. e.g. OAuth, OIDC. * Common authentication logic for third parties. e.g. OAuth, OIDC.

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.87-alpha.7", "version": "0.9.96",
"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

@ -70,6 +70,7 @@
> >
<div class="modal-wrapper" on:mousedown|self={cancel}> <div class="modal-wrapper" on:mousedown|self={cancel}>
<div class="modal-inner-wrapper" on:mousedown|self={cancel}> <div class="modal-inner-wrapper" on:mousedown|self={cancel}>
<slot name="outside" />
<div <div
use:focusFirstInput use:focusFirstInput
class="spectrum-Modal is-open" class="spectrum-Modal is-open"
@ -93,6 +94,7 @@
z-index: 999; z-index: 999;
overflow: auto; overflow: auto;
overflow-x: hidden; overflow-x: hidden;
background: rgba(0, 0, 0, 0.75);
} }
.modal-wrapper { .modal-wrapper {
@ -112,6 +114,7 @@
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;
width: 0; width: 0;
position: relative;
} }
.spectrum-Modal { .spectrum-Modal {
@ -122,6 +125,7 @@
--spectrum-dialog-confirm-border-radius: var( --spectrum-dialog-confirm-border-radius: var(
--spectrum-global-dimension-size-100 --spectrum-global-dimension-size-100
); );
max-width: 100%;
} }
:global(.spectrum--lightest .spectrum-Modal.inline) { :global(.spectrum--lightest .spectrum-Modal.inline) {
border: var(--border-light); border: var(--border-light);

View file

@ -15,6 +15,7 @@
export let showCloseIcon = true export let showCloseIcon = true
export let onConfirm = undefined export let onConfirm = undefined
export let disabled = false export let disabled = false
export let showDivider = true
const { hide, cancel } = getContext(Context.Modal) const { hide, cancel } = getContext(Context.Modal)
let loading = false let loading = false
@ -41,11 +42,17 @@
aria-modal="true" aria-modal="true"
> >
<div class="spectrum-Dialog-grid"> <div class="spectrum-Dialog-grid">
<h1 class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader"> {#if title}
{title} <h1
</h1> class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader"
class:noDivider={!showDivider}
<Divider size="M" /> >
{title}
</h1>
{#if showDivider}
<Divider size="M" />
{/if}
{/if}
<!-- TODO: Remove content-grid class once Layout components are in bbui --> <!-- TODO: Remove content-grid class once Layout components are in bbui -->
<section class="spectrum-Dialog-content content-grid"> <section class="spectrum-Dialog-content content-grid">
<slot /> <slot />
@ -72,8 +79,8 @@
</div> </div>
{/if} {/if}
{#if showCloseIcon} {#if showCloseIcon}
<div class="close-icon" on:click={hide}> <div class="close-icon">
<Icon hoverable name="Close" /> <Icon hoverable name="Close" on:click={cancel} />
</div> </div>
{/if} {/if}
</div> </div>
@ -96,6 +103,9 @@
.spectrum-Dialog-heading { .spectrum-Dialog-heading {
font-family: var(--font-sans); font-family: var(--font-sans);
} }
.spectrum-Dialog-heading.noDivider {
margin-bottom: 12px;
}
.spectrum-Dialog-buttonGroup { .spectrum-Dialog-buttonGroup {
gap: var(--spectrum-global-dimension-static-size-200); gap: var(--spectrum-global-dimension-static-size-200);

View file

@ -0,0 +1,20 @@
<script>
export let type = "info"
export let icon = "Info"
export let message = ""
</script>
<div class="spectrum-Toast spectrum-Toast--{type}">
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Toast-typeIcon"
focusable="false"
aria-hidden="true"
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
<div class="spectrum-Toast-body">
<div class="spectrum-Toast-content">{message || ""}</div>
</div>
</div>

View file

@ -2,30 +2,16 @@
import "@spectrum-css/toast/dist/index-vars.css" import "@spectrum-css/toast/dist/index-vars.css"
import Portal from "svelte-portal" import Portal from "svelte-portal"
import { flip } from "svelte/animate" import { flip } from "svelte/animate"
import { fly } from "svelte/transition"
import { notifications } from "../Stores/notifications" import { notifications } from "../Stores/notifications"
import Notification from "./Notification.svelte"
import { fly } from "svelte/transition"
</script> </script>
<Portal target=".modal-container"> <Portal target=".modal-container">
<div class="notifications"> <div class="notifications">
{#each $notifications as { type, icon, message, id } (id)} {#each $notifications as { type, icon, message, id } (id)}
<div <div animate:flip transition:fly={{ y: -30 }}>
animate:flip <Notification {type} {icon} {message} />
transition:fly={{ y: -30 }}
class="spectrum-Toast spectrum-Toast--{type} notification-offset"
>
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Toast-typeIcon"
focusable="false"
aria-hidden="true"
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
<div class="spectrum-Toast-body">
<div class="spectrum-Toast-content">{message}</div>
</div>
</div> </div>
{/each} {/each}
</div> </div>
@ -34,7 +20,7 @@
<style> <style>
.notifications { .notifications {
position: fixed; position: fixed;
top: 10px; top: 20px;
left: 0; left: 0;
right: 0; right: 0;
margin: 0 auto; margin: 0 auto;
@ -45,8 +31,6 @@
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
pointer-events: none; pointer-events: none;
} gap: 10px;
.notification-offset {
margin-bottom: 10px;
} }
</style> </style>

View file

@ -38,6 +38,7 @@ export { default as MenuItem } from "./Menu/Item.svelte"
export { default as Modal } from "./Modal/Modal.svelte" export { default as Modal } from "./Modal/Modal.svelte"
export { default as ModalContent } from "./Modal/ModalContent.svelte" export { default as ModalContent } from "./Modal/ModalContent.svelte"
export { default as NotificationDisplay } from "./Notification/NotificationDisplay.svelte" export { default as NotificationDisplay } from "./Notification/NotificationDisplay.svelte"
export { default as Notification } from "./Notification/Notification.svelte"
export { default as SideNavigation } from "./SideNavigation/Navigation.svelte" export { default as SideNavigation } from "./SideNavigation/Navigation.svelte"
export { default as SideNavigationItem } from "./SideNavigation/Item.svelte" export { default as SideNavigationItem } from "./SideNavigation/Item.svelte"
export { default as DatePicker } from "./Form/DatePicker.svelte" export { default as DatePicker } from "./Form/DatePicker.svelte"

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "0.9.87-alpha.7", "version": "0.9.96",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -65,10 +65,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.87-alpha.7", "@budibase/bbui": "^0.9.96",
"@budibase/client": "^0.9.87-alpha.7", "@budibase/client": "^0.9.96",
"@budibase/colorpicker": "1.1.2", "@budibase/colorpicker": "1.1.2",
"@budibase/string-templates": "^0.9.87-alpha.7", "@budibase/string-templates": "^0.9.96",
"@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

@ -70,7 +70,7 @@ export const getFrontendStore = () => {
url: application.url, url: application.url,
layouts, layouts,
screens, screens,
theme: application.theme, theme: application.theme || "spectrum--light",
hasAppPackage: true, hasAppPackage: true,
appInstance: application.instance, appInstance: application.instance,
clientLibPath, clientLibPath,

View file

@ -18,8 +18,8 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let bindingDrawer let bindingDrawer
$: tempValue = Array.isArray(value) ? value : []
$: readableValue = runtimeToReadableBinding(bindings, value) $: readableValue = runtimeToReadableBinding(bindings, value)
$: tempValue = readableValue
const handleClose = () => { const handleClose = () => {
onChange(tempValue) onChange(tempValue)
@ -56,7 +56,7 @@
slot="body" slot="body"
value={readableValue} value={readableValue}
close={handleClose} close={handleClose}
on:update={event => (tempValue = event.detail)} on:change={event => (tempValue = event.detail)}
bindableProperties={bindings} bindableProperties={bindings}
/> />
</Drawer> </Drawer>

View file

@ -24,7 +24,7 @@
<div> <div>
<Select <Select
value={$store.theme || "spectrum--light"} value={$store.theme}
options={themeOptions} options={themeOptions}
placeholder={null} placeholder={null}
on:change={e => store.actions.theme.save(e.detail)} on:change={e => store.actions.theme.save(e.detail)}

View file

@ -47,6 +47,18 @@ export default `
return return
} }
// Parse received message
// If parsing fails, just ignore and wait for the next message
let parsed
try {
parsed = JSON.parse(event.data)
} catch (error) {
// Ignore
}
if (!parsed) {
return
}
// Extract data from message // Extract data from message
const { const {
selectedComponentId, selectedComponentId,
@ -55,7 +67,7 @@ export default `
previewType, previewType,
appId, appId,
theme theme
} = JSON.parse(event.data) } = parsed
// Set some flags so the app knows we're in the builder // Set some flags so the app knows we're in the builder
window["##BUDIBASE_IN_BUILDER##"] = true window["##BUDIBASE_IN_BUILDER##"] = true

View file

@ -11,6 +11,7 @@
export let componentDefinition export let componentDefinition
export let componentInstance export let componentInstance
export let assetInstance export let assetInstance
export let bindings
const layoutDefinition = [] const layoutDefinition = []
const screenDefinition = [ const screenDefinition = [
@ -65,6 +66,7 @@
options: setting.options, options: setting.options,
placeholder: setting.placeholder, placeholder: setting.placeholder,
}} }}
{bindings}
/> />
{/if} {/if}
{/each} {/each}

View file

@ -4,6 +4,7 @@
import ConditionalUIDrawer from "./PropertyControls/ConditionalUIDrawer.svelte" import ConditionalUIDrawer from "./PropertyControls/ConditionalUIDrawer.svelte"
export let componentInstance export let componentInstance
export let bindings
let tempValue let tempValue
let drawer let drawer
@ -32,5 +33,5 @@
Show, hide and update components in response to conditions being met. Show, hide and update components in response to conditions being met.
</svelte:fragment> </svelte:fragment>
<Button cta slot="buttons" on:click={() => save()}>Save</Button> <Button cta slot="buttons" on:click={() => save()}>Save</Button>
<ConditionalUIDrawer slot="body" bind:conditions={tempValue} /> <ConditionalUIDrawer slot="body" bind:conditions={tempValue} {bindings} />
</Drawer> </Drawer>

View file

@ -4,6 +4,7 @@
export let componentDefinition export let componentDefinition
export let componentInstance export let componentInstance
export let bindings
const getStyles = def => { const getStyles = def => {
if (!def?.styles?.length) { if (!def?.styles?.length) {
@ -29,6 +30,7 @@
columns={style.columns} columns={style.columns}
properties={style.settings} properties={style.settings}
{componentInstance} {componentInstance}
{bindings}
/> />
{/each} {/each}
{/if} {/if}

View file

@ -1,27 +1,45 @@
<script> <script>
import { store, selectedComponent } from "builderStore" import { store, selectedComponent, currentAsset } from "builderStore"
import { Tabs, Tab } from "@budibase/bbui" import { Tabs, Tab } from "@budibase/bbui"
import ScreenSettingsSection from "./ScreenSettingsSection.svelte" import ScreenSettingsSection from "./ScreenSettingsSection.svelte"
import ComponentSettingsSection from "./ComponentSettingsSection.svelte" import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
import DesignSection from "./DesignSection.svelte" import DesignSection from "./DesignSection.svelte"
import CustomStylesSection from "./CustomStylesSection.svelte" import CustomStylesSection from "./CustomStylesSection.svelte"
import ConditionalUISection from "./ConditionalUISection.svelte" import ConditionalUISection from "./ConditionalUISection.svelte"
import { getBindableProperties } from "builderStore/dataBinding"
$: componentInstance = $selectedComponent $: componentInstance = $selectedComponent
$: componentDefinition = store.actions.components.getDefinition( $: componentDefinition = store.actions.components.getDefinition(
$selectedComponent?._component $selectedComponent?._component
) )
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
</script> </script>
<Tabs selected="Settings" noPadding> <Tabs selected="Settings" noPadding>
<Tab title="Settings"> <Tab title="Settings">
<div class="container"> <div class="container">
{#key componentInstance?._id} {#key componentInstance?._id}
<ScreenSettingsSection {componentInstance} {componentDefinition} /> <ScreenSettingsSection
<ComponentSettingsSection {componentInstance} {componentDefinition} /> {componentInstance}
<DesignSection {componentInstance} {componentDefinition} /> {componentDefinition}
<CustomStylesSection {componentInstance} {componentDefinition} /> {bindings}
<ConditionalUISection {componentInstance} {componentDefinition} /> />
<ComponentSettingsSection
{componentInstance}
{componentDefinition}
{bindings}
/>
<DesignSection {componentInstance} {componentDefinition} {bindings} />
<CustomStylesSection
{componentInstance}
{componentDefinition}
{bindings}
/>
<ConditionalUISection
{componentInstance}
{componentDefinition}
{bindings}
/>
{/key} {/key}
</div> </div>
</Tab> </Tab>

View file

@ -13,12 +13,12 @@
import { generate } from "shortid" import { generate } from "shortid"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene" import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
import { getBindableProperties } from "builderStore/dataBinding" import { selectedComponent, store } from "builderStore"
import { currentAsset, selectedComponent, store } from "builderStore"
import { getComponentForSettingType } from "./componentSettings" import { getComponentForSettingType } from "./componentSettings"
import PropertyControl from "./PropertyControl.svelte" import PropertyControl from "./PropertyControl.svelte"
export let conditions = [] export let conditions = []
export let bindings = []
const flipDurationMs = 150 const flipDurationMs = 150
const actionOptions = [ const actionOptions = [
@ -64,10 +64,6 @@
value: setting.key, value: setting.key,
} }
}) })
$: bindableProperties = getBindableProperties(
$currentAsset,
$store.selectedComponentId
)
$: conditions.forEach(link => { $: conditions.forEach(link => {
if (!link.id) { if (!link.id) {
link.id = generate() link.id = generate()
@ -194,6 +190,7 @@
placeholder: getSettingDefinition(condition.setting) placeholder: getSettingDefinition(condition.setting)
.placeholder, .placeholder,
}} }}
{bindings}
/> />
{:else} {:else}
<Select disabled placeholder=" " /> <Select disabled placeholder=" " />
@ -201,7 +198,7 @@
{/if} {/if}
<div>IF</div> <div>IF</div>
<DrawerBindableInput <DrawerBindableInput
bindings={bindableProperties} {bindings}
placeholder="Value" placeholder="Value"
value={condition.newValue} value={condition.newValue}
on:change={e => (condition.newValue = e.detail)} on:change={e => (condition.newValue = e.detail)}
@ -222,7 +219,7 @@
{#if ["string", "number"].includes(condition.valueType)} {#if ["string", "number"].includes(condition.valueType)}
<DrawerBindableInput <DrawerBindableInput
disabled={condition.noValue} disabled={condition.noValue}
bindings={bindableProperties} {bindings}
placeholder="Value" placeholder="Value"
value={condition.referenceValue} value={condition.referenceValue}
on:change={e => (condition.referenceValue = e.detail)} on:change={e => (condition.referenceValue = e.detail)}

View file

@ -1,8 +1,5 @@
<script> <script>
import { import { getDataProviderComponents } from "builderStore/dataBinding"
getBindableProperties,
getDataProviderComponents,
} from "builderStore/dataBinding"
import { import {
Button, Button,
Popover, Popover,
@ -31,6 +28,7 @@
export let value = {} export let value = {}
export let otherSources export let otherSources
export let showAllQueries export let showAllQueries
export let bindings = []
$: text = value?.label ?? "Choose an option" $: text = value?.label ?? "Choose an option"
$: tables = $tablesStore.list.map(m => ({ $: tables = $tablesStore.list.map(m => ({
@ -60,10 +58,6 @@
parameters: query.parameters, parameters: query.parameters,
type: "query", type: "query",
})) }))
$: bindableProperties = getBindableProperties(
$currentAsset,
$store.selectedComponentId
)
$: dataProviders = getDataProviderComponents( $: dataProviders = getDataProviderComponents(
$currentAsset, $currentAsset,
$store.selectedComponentId $store.selectedComponentId
@ -75,13 +69,13 @@
type: "provider", type: "provider",
schema: provider.schema, schema: provider.schema,
})) }))
$: queryBindableProperties = bindableProperties.map(property => ({ $: queryBindableProperties = bindings.map(property => ({
...property, ...property,
category: property.type === "instance" ? "Component" : "Table", category: property.type === "instance" ? "Component" : "Table",
label: property.readableBinding, label: property.readableBinding,
path: property.readableBinding, path: property.readableBinding,
})) }))
$: links = bindableProperties $: links = bindings
.filter(x => x.fieldSchema?.type === "link") .filter(x => x.fieldSchema?.type === "link")
.map(property => { .map(property => {
return { return {
@ -138,7 +132,7 @@
bind:customParams={value.queryParams} bind:customParams={value.queryParams}
parameters={queries.find(query => query._id === value._id) parameters={queries.find(query => query._id === value._id)
.parameters} .parameters}
bindings={queryBindableProperties} {bindings}
/> />
{/if} {/if}
<IntegrationQueryEditor <IntegrationQueryEditor

View file

@ -13,10 +13,10 @@
import { generate } from "shortid" import { generate } from "shortid"
const flipDurationMs = 150 const flipDurationMs = 150
const EVENT_TYPE_KEY = "##eventHandlerType" const EVENT_TYPE_KEY = "##eventHandlerType"
export let actions export let actions
export let bindings = []
// dndzone needs an id on the array items, so this adds some temporary ones. // dndzone needs an id on the array items, so this adds some temporary ones.
$: { $: {
@ -121,6 +121,7 @@
<svelte:component <svelte:component
this={selectedActionComponent} this={selectedActionComponent}
parameters={selectedAction.parameters} parameters={selectedAction.parameters}
{bindings}
/> />
</div> </div>
{/if} {/if}

View file

@ -9,6 +9,7 @@
export let value = [] export let value = []
export let name export let name
export let bindings
let drawer let drawer
@ -57,5 +58,5 @@
Define what actions to run. Define what actions to run.
</svelte:fragment> </svelte:fragment>
<Button cta slot="buttons" on:click={saveEventData}>Save</Button> <Button cta slot="buttons" on:click={saveEventData}>Save</Button>
<EventEditor slot="body" bind:actions={value} eventType={name} /> <EventEditor slot="body" bind:actions={value} eventType={name} {bindings} />
</Drawer> </Drawer>

View file

@ -0,0 +1,17 @@
<script>
import { Body } from "@budibase/bbui"
</script>
<div class="root">
<Body size="S">This action doesn't require any additional settings.</Body>
<Body size="S">
This action won't do anything if there isn't a screen modal open.
</Body>
</div>
<style>
.root {
max-width: 800px;
margin: 0 auto;
}
</style>

View file

@ -1,14 +1,12 @@
<script> <script>
import { Select, Label, Checkbox, Input } from "@budibase/bbui" import { Select, Label, Checkbox, Input } from "@budibase/bbui"
import { store, currentAsset } from "builderStore"
import { tables } from "stores/backend" import { tables } from "stores/backend"
import { getBindableProperties } from "builderStore/dataBinding"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
export let parameters export let parameters
export let bindings = []
$: tableOptions = $tables.list || [] $: tableOptions = $tables.list || []
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
</script> </script>
<div class="root"> <div class="root">

View file

@ -1,21 +1,16 @@
<script> <script>
import { Select, Layout, Input, Checkbox } from "@budibase/bbui" import { Select, Layout, Input, Checkbox } from "@budibase/bbui"
import { store, currentAsset } from "builderStore"
import { datasources, integrations, queries } from "stores/backend" import { datasources, integrations, queries } from "stores/backend"
import { getBindableProperties } from "builderStore/dataBinding"
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte" import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte" import IntegrationQueryEditor from "components/integration/index.svelte"
export let parameters export let parameters
export let bindings = []
$: query = $queries.list.find(q => q._id === parameters.queryId) $: query = $queries.list.find(q => q._id === parameters.queryId)
$: datasource = $datasources.list.find( $: datasource = $datasources.list.find(
ds => ds._id === parameters.datasourceId ds => ds._id === parameters.datasourceId
) )
$: bindableProperties = getBindableProperties(
$currentAsset,
$store.selectedComponentId
)
function fetchQueryDefinition(query) { function fetchQueryDefinition(query) {
const source = $datasources.list.find( const source = $datasources.list.find(
@ -61,7 +56,7 @@
<ParameterBuilder <ParameterBuilder
bind:customParams={parameters.queryParams} bind:customParams={parameters.queryParams}
parameters={query.parameters} parameters={query.parameters}
bindings={bindableProperties} {bindings}
/> />
<IntegrationQueryEditor <IntegrationQueryEditor
height={200} height={200}

View file

@ -1,12 +1,9 @@
<script> <script>
import { Label } from "@budibase/bbui" import { Label } from "@budibase/bbui"
import { getBindableProperties } from "builderStore/dataBinding"
import { currentAsset, store } from "builderStore"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
export let parameters export let parameters
export let bindings = []
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
</script> </script>
<div class="root"> <div class="root">
@ -18,19 +15,17 @@
on:change={value => (parameters.url = value.detail)} on:change={value => (parameters.url = value.detail)}
{bindings} {bindings}
/> />
<div />
<Checkbox text="Open screen in modal" bind:value={parameters.peek} />
</div> </div>
<style> <style>
.root { .root {
display: flex; display: grid;
flex-direction: row; align-items: center;
align-items: baseline; gap: var(--spacing-m);
grid-template-columns: auto 1fr;
max-width: 800px; max-width: 800px;
margin: 0 auto; margin: 0 auto;
} }
.root :global(> div) {
flex: 1;
margin-left: var(--spacing-l);
}
</style> </style>

View file

@ -1,7 +1,5 @@
<script> <script>
import { Label, ActionButton, Button, Select, Input } from "@budibase/bbui" import { Label, ActionButton, Button, Select, Input } from "@budibase/bbui"
import { store, currentAsset } from "builderStore"
import { getBindableProperties } from "builderStore/dataBinding"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
@ -11,13 +9,10 @@
export let schemaFields export let schemaFields
export let fieldLabel = "Column" export let fieldLabel = "Column"
export let valueLabel = "Value" export let valueLabel = "Value"
export let bindings = []
let fields = Object.entries(parameterFields || {}) let fields = Object.entries(parameterFields || {})
$: onChange(fields) $: onChange(fields)
$: bindableProperties = getBindableProperties(
$currentAsset,
$store.selectedComponentId
)
const addField = () => { const addField = () => {
fields = [...fields.filter(field => field[0]), ["", ""]] fields = [...fields.filter(field => field[0]), ["", ""]]
@ -69,7 +64,7 @@
<DrawerBindableInput <DrawerBindableInput
title={`Value for "${field[0]}"`} title={`Value for "${field[0]}"`}
value={field[1]} value={field[1]}
bindings={bindableProperties} {bindings}
on:change={event => updateFieldValue(idx, event.detail)} on:change={event => updateFieldValue(idx, event.detail)}
/> />
<ActionButton <ActionButton

View file

@ -9,6 +9,7 @@
import SaveFields from "./SaveFields.svelte" import SaveFields from "./SaveFields.svelte"
export let parameters export let parameters
export let bindings = []
$: dataProviderComponents = getDataProviderComponents( $: dataProviderComponents = getDataProviderComponents(
$currentAsset, $currentAsset,
@ -70,6 +71,7 @@
parameterFields={parameters.fields} parameterFields={parameters.fields}
{schemaFields} {schemaFields}
on:change={onFieldsChanged} on:change={onFieldsChanged}
{bindings}
/> />
</div> </div>
{/if} {/if}

View file

@ -3,13 +3,14 @@
import { automationStore } from "builderStore" import { automationStore } from "builderStore"
import SaveFields from "./SaveFields.svelte" import SaveFields from "./SaveFields.svelte"
export let parameters = {}
export let bindings = []
const AUTOMATION_STATUS = { const AUTOMATION_STATUS = {
NEW: "new", NEW: "new",
EXISTING: "existing", EXISTING: "existing",
} }
export let parameters = {}
let automationStatus = parameters.automationId let automationStatus = parameters.automationId
? AUTOMATION_STATUS.EXISTING ? AUTOMATION_STATUS.EXISTING
: AUTOMATION_STATUS.NEW : AUTOMATION_STATUS.NEW
@ -109,6 +110,7 @@
parameterFields={parameters.fields} parameterFields={parameters.fields}
fieldLabel="Field" fieldLabel="Field"
on:change={onFieldsChanged} on:change={onFieldsChanged}
{bindings}
/> />
{/key} {/key}
</div> </div>

View file

@ -6,6 +6,7 @@ import TriggerAutomation from "./TriggerAutomation.svelte"
import ValidateForm from "./ValidateForm.svelte" import ValidateForm from "./ValidateForm.svelte"
import LogOut from "./LogOut.svelte" import LogOut from "./LogOut.svelte"
import ClearForm from "./ClearForm.svelte" import ClearForm from "./ClearForm.svelte"
import CloseScreenModal from "./CloseScreenModal.svelte"
// Defines which actions are available to configure in the front end. // Defines which actions are available to configure in the front end.
// Unfortunately the "name" property is used as the identifier so please don't // Unfortunately the "name" property is used as the identifier so please don't
@ -47,4 +48,8 @@ export default [
name: "Clear Form", name: "Clear Form",
component: ClearForm, component: ClearForm,
}, },
{
name: "Close Screen Modal",
component: CloseScreenModal,
},
] ]

View file

@ -20,6 +20,8 @@
export let value = [] export let value = []
export let componentInstance export let componentInstance
export let bindings = []
let drawer let drawer
let tempValue = value || [] let tempValue = value || []
@ -51,7 +53,7 @@
constraints. constraints.
{/if} {/if}
</Body> </Body>
<LuceneFilterBuilder bind:value={tempValue} {schemaFields} /> <LuceneFilterBuilder bind:value={tempValue} {schemaFields} {bindings} />
</Layout> </Layout>
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>

View file

@ -7,20 +7,15 @@
Combobox, Combobox,
Input, Input,
} from "@budibase/bbui" } from "@budibase/bbui"
import { store, currentAsset } from "builderStore"
import { getBindableProperties } from "builderStore/dataBinding"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
import { generate } from "shortid" import { generate } from "shortid"
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene" import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
export let schemaFields export let schemaFields
export let value export let value
export let bindings = []
const BannedTypes = ["link", "attachment"] const BannedTypes = ["link", "attachment"]
$: bindableProperties = getBindableProperties(
$currentAsset,
$store.selectedComponentId
)
$: fieldOptions = (schemaFields ?? []) $: fieldOptions = (schemaFields ?? [])
.filter(field => !BannedTypes.includes(field.type)) .filter(field => !BannedTypes.includes(field.type))
.map(field => field.name) .map(field => field.name)
@ -101,7 +96,7 @@
title={`Value for "${expression.field}"`} title={`Value for "${expression.field}"`}
value={expression.value} value={expression.value}
placeholder="Value" placeholder="Value"
bindings={bindableProperties} {bindings}
on:change={event => (expression.value = event.detail)} on:change={event => (expression.value = event.detail)}
/> />
{:else if ["string", "longform", "number"].includes(expression.type)} {:else if ["string", "longform", "number"].includes(expression.type)}

View file

@ -1,8 +1,6 @@
<script> <script>
import { Button, Icon, Drawer, Label } from "@budibase/bbui" import { Button, Icon, Drawer, Label } from "@budibase/bbui"
import { store, currentAsset } from "builderStore"
import { import {
getBindableProperties,
readableToRuntimeBinding, readableToRuntimeBinding,
runtimeToReadableBinding, runtimeToReadableBinding,
} from "builderStore/dataBinding" } from "builderStore/dataBinding"
@ -18,18 +16,15 @@
export let value = null export let value = null
export let props = {} export let props = {}
export let onChange = () => {} export let onChange = () => {}
export let bindings = []
let bindingDrawer let bindingDrawer
let anchor let anchor
let valid let valid
$: bindableProperties = getBindableProperties( $: safeValue = getSafeValue(value, props.defaultValue, bindings)
$currentAsset,
$store.selectedComponentId
)
$: safeValue = getSafeValue(value, props.defaultValue, bindableProperties)
$: tempValue = safeValue $: tempValue = safeValue
$: replaceBindings = val => readableToRuntimeBinding(bindableProperties, val) $: replaceBindings = val => readableToRuntimeBinding(bindings, val)
const handleClose = () => { const handleClose = () => {
handleChange(tempValue) handleChange(tempValue)
@ -61,8 +56,8 @@
// The "safe" value is the value with any bindings made readable // The "safe" value is the value with any bindings made readable
// If there is no value set, any default value is used // If there is no value set, any default value is used
const getSafeValue = (value, defaultValue, bindableProperties) => { const getSafeValue = (value, defaultValue, bindings) => {
const enriched = runtimeToReadableBinding(bindableProperties, value) const enriched = runtimeToReadableBinding(bindings, value)
return enriched == null && defaultValue !== undefined return enriched == null && defaultValue !== undefined
? defaultValue ? defaultValue
: enriched : enriched
@ -83,6 +78,7 @@
updateOnChange={false} updateOnChange={false}
on:change={handleChange} on:change={handleChange}
onChange={handleChange} onChange={handleChange}
{bindings}
name={key} name={key}
text={label} text={label}
{type} {type}
@ -108,7 +104,7 @@
bind:valid bind:valid
value={safeValue} value={safeValue}
on:change={e => (tempValue = e.detail)} on:change={e => (tempValue = e.detail)}
{bindableProperties} bindableProperties={bindings}
/> />
</Drawer> </Drawer>
{/if} {/if}

View file

@ -3,10 +3,11 @@
import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCombobox.svelte" import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCombobox.svelte"
export let value export let value
export let bindings
$: urlOptions = $store.screens $: urlOptions = $store.screens
.map(screen => screen.routing?.route) .map(screen => screen.routing?.route)
.filter(x => x != null) .filter(x => x != null)
</script> </script>
<DrawerBindableCombobox {value} on:change options={urlOptions} /> <DrawerBindableCombobox {value} {bindings} on:change options={urlOptions} />

View file

@ -9,6 +9,7 @@
import { FrontendTypes } from "constants" import { FrontendTypes } from "constants"
export let componentInstance export let componentInstance
export let bindings
function setAssetProps(name, value) { function setAssetProps(name, value) {
const selectedAsset = get(currentAsset) const selectedAsset = get(currentAsset)
@ -44,6 +45,7 @@
key={def.key} key={def.key}
value={deepGet($currentAsset, def.key)} value={deepGet($currentAsset, def.key)}
onChange={val => setAssetProps(def.key, val)} onChange={val => setAssetProps(def.key, val)}
{bindings}
/> />
{/each} {/each}
</DetailSummary> </DetailSummary>

View file

@ -7,6 +7,7 @@
export let columns export let columns
export let properties export let properties
export let componentInstance export let componentInstance
export let bindings = []
$: style = componentInstance._styles.normal || {} $: style = componentInstance._styles.normal || {}
$: changed = properties?.some(prop => hasPropChanged(style, prop)) ?? false $: changed = properties?.some(prop => hasPropChanged(style, prop)) ?? false
@ -36,6 +37,7 @@
value={style[prop.key]} value={style[prop.key]}
onChange={val => store.actions.components.updateStyle(prop.key, val)} onChange={val => store.actions.components.updateStyle(prop.key, val)}
props={getControlProps(prop)} props={getControlProps(prop)}
{bindings}
/> />
</div> </div>
{/each} {/each}

View file

@ -158,10 +158,16 @@
fieldName: fromTable.primary[0], fieldName: fromTable.primary[0],
} }
} else { } else {
// the relateFrom.fieldName should remain the same, as it is the foreignKey in the other
// table, this is due to the way that budibase represents relationships, the fieldName in a
// link column schema is the column linked to (FK in this case). The foreignKey column is
// essentially what is linked to in the from table, this is unique to SQL as this isn't a feature
// of Budibase internal tables.
// Essentially this means the fieldName is what we are linking to in the other table, and the
// foreignKey is what is linking out of the current table.
relateFrom = { relateFrom = {
...relateFrom, ...relateFrom,
foreignKey: relateFrom.fieldName, foreignKey: fromTable.primary[0],
fieldName: fromTable.primary[0],
} }
relateTo = { relateTo = {
...relateTo, ...relateTo,

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "0.9.87-alpha.7", "version": "0.9.96",
"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.87-alpha.7", "version": "0.9.96",
"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,9 +18,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.87-alpha.7", "@budibase/bbui": "^0.9.96",
"@budibase/standard-components": "^0.9.87-alpha.7", "@budibase/standard-components": "^0.9.96",
"@budibase/string-templates": "^0.9.87-alpha.7", "@budibase/string-templates": "^0.9.96",
"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"

View file

@ -38,15 +38,15 @@ const makeApiCall = async ({ method, url, body, json = true }) => {
case 200: case 200:
return response.json() return response.json()
case 401: case 401:
notificationStore.danger("Invalid credentials") notificationStore.actions.error("Invalid credentials")
return handleError(`Invalid credentials`) return handleError(`Invalid credentials`)
case 404: case 404:
notificationStore.danger("Not found") notificationStore.actions.warning("Not found")
return handleError(`${url}: Not Found`) return handleError(`${url}: Not Found`)
case 400: case 400:
return handleError(`${url}: Bad Request`) return handleError(`${url}: Bad Request`)
case 403: case 403:
notificationStore.danger( notificationStore.actions.error(
"Your session has expired, or you don't have permission to access that data" "Your session has expired, or you don't have permission to access that data"
) )
return handleError(`${url}: Forbidden`) return handleError(`${url}: Forbidden`)

View file

@ -9,7 +9,7 @@ export const triggerAutomation = async (automationId, fields) => {
body: { fields }, body: { fields },
}) })
res.error res.error
? notificationStore.danger("An error has occurred") ? notificationStore.actions.error("An error has occurred")
: notificationStore.success("Automation triggered") : notificationStore.actions.success("Automation triggered")
return res return res
} }

View file

@ -7,7 +7,7 @@ import API from "./api"
export const executeQuery = async ({ queryId, parameters }) => { export const executeQuery = async ({ queryId, parameters }) => {
const query = await API.get({ url: `/api/queries/${queryId}` }) const query = await API.get({ url: `/api/queries/${queryId}` })
if (query?.datasourceId == null) { if (query?.datasourceId == null) {
notificationStore.danger("That query couldn't be found") notificationStore.actions.error("That query couldn't be found")
return return
} }
const res = await API.post({ const res = await API.post({
@ -17,9 +17,9 @@ export const executeQuery = async ({ queryId, parameters }) => {
}, },
}) })
if (res.error) { if (res.error) {
notificationStore.danger("An error has occurred") notificationStore.actions.error("An error has occurred")
} else if (!query.readable) { } else if (!query.readable) {
notificationStore.success("Query executed successfully") notificationStore.actions.success("Query executed successfully")
dataSourceStore.actions.invalidateDataSource(query.datasourceId) dataSourceStore.actions.invalidateDataSource(query.datasourceId)
} }
return res return res

View file

@ -27,8 +27,8 @@ export const saveRow = async row => {
body: row, body: row,
}) })
res.error res.error
? notificationStore.danger("An error has occurred") ? notificationStore.actions.error("An error has occurred")
: notificationStore.success("Row saved") : notificationStore.actions.success("Row saved")
// Refresh related datasources // Refresh related datasources
dataSourceStore.actions.invalidateDataSource(row.tableId) dataSourceStore.actions.invalidateDataSource(row.tableId)
@ -48,8 +48,8 @@ export const updateRow = async row => {
body: row, body: row,
}) })
res.error res.error
? notificationStore.danger("An error has occurred") ? notificationStore.actions.error("An error has occurred")
: notificationStore.success("Row updated") : notificationStore.actions.success("Row updated")
// Refresh related datasources // Refresh related datasources
dataSourceStore.actions.invalidateDataSource(row.tableId) dataSourceStore.actions.invalidateDataSource(row.tableId)
@ -72,8 +72,8 @@ export const deleteRow = async ({ tableId, rowId, revId }) => {
}, },
}) })
res.error res.error
? notificationStore.danger("An error has occurred") ? notificationStore.actions.error("An error has occurred")
: notificationStore.success("Row deleted") : notificationStore.actions.success("Row deleted")
// Refresh related datasources // Refresh related datasources
dataSourceStore.actions.invalidateDataSource(tableId) dataSourceStore.actions.invalidateDataSource(tableId)
@ -95,8 +95,8 @@ export const deleteRows = async ({ tableId, rows }) => {
}, },
}) })
res.error res.error
? notificationStore.danger("An error has occurred") ? notificationStore.actions.error("An error has occurred")
: notificationStore.success(`${rows.length} row(s) deleted`) : notificationStore.actions.success(`${rows.length} row(s) deleted`)
// Refresh related datasources // Refresh related datasources
dataSourceStore.actions.invalidateDataSource(tableId) dataSourceStore.actions.invalidateDataSource(tableId)

View file

@ -4,6 +4,7 @@
import Component from "./Component.svelte" import Component from "./Component.svelte"
import NotificationDisplay from "./NotificationDisplay.svelte" import NotificationDisplay from "./NotificationDisplay.svelte"
import ConfirmationDisplay from "./ConfirmationDisplay.svelte" import ConfirmationDisplay from "./ConfirmationDisplay.svelte"
import PeekScreenDisplay from "./PeekScreenDisplay.svelte"
import Provider from "./Provider.svelte" import Provider from "./Provider.svelte"
import SDK from "../sdk" import SDK from "../sdk"
import { import {
@ -93,13 +94,14 @@
</div> </div>
{:else if $screenStore.activeLayout} {:else if $screenStore.activeLayout}
<Provider key="user" data={$authStore} {actions}> <Provider key="user" data={$authStore} {actions}>
<div id="app-root"> <div id="app-root" class:preview={$builderStore.inBuilder}>
{#key $screenStore.activeLayout._id} {#key $screenStore.activeLayout._id}
<Component instance={$screenStore.activeLayout.props} /> <Component instance={$screenStore.activeLayout.props} />
{/key} {/key}
</div> </div>
<NotificationDisplay /> <NotificationDisplay />
<ConfirmationDisplay /> <ConfirmationDisplay />
<PeekScreenDisplay />
<!-- Key block needs to be outside the if statement or it breaks --> <!-- Key block needs to be outside the if statement or it breaks -->
{#key $builderStore.selectedComponentId} {#key $builderStore.selectedComponentId}
{#if $builderStore.inBuilder} {#if $builderStore.inBuilder}
@ -131,6 +133,9 @@
#app-root { #app-root {
position: relative; position: relative;
} }
#app-root.preview {
border: 1px solid var(--spectrum-global-color-gray-300);
}
/* Custom scrollbars */ /* Custom scrollbars */
:global(::-webkit-scrollbar) { :global(::-webkit-scrollbar) {

View file

@ -1,36 +1,34 @@
<script> <script>
import { flip } from "svelte/animate" import { notificationStore } from "../store"
import { Notification } from "@budibase/bbui"
import { fly } from "svelte/transition" import { fly } from "svelte/transition"
import { getContext } from "svelte"
const { notifications } = getContext("sdk")
export let themes = {
danger: "#E26D69",
success: "#84C991",
warning: "#f0ad4e",
info: "#5bc0de",
default: "#aaaaaa",
}
</script> </script>
<div class="notifications"> <div class="notifications">
{#each $notifications as notification (notification.id)} {#if $notificationStore}
<div {#key $notificationStore.id}
animate:flip <div
class="toast" in:fly={{
style="background: {themes[notification.type]};" duration: 300,
transition:fly={{ y: -30 }} y: -20,
> delay: $notificationStore.delay ? 300 : 0,
<div class="content">{notification.message}</div> }}
{#if notification.icon}<i class={notification.icon} />{/if} out:fly={{ y: -20, duration: 150 }}
</div> >
{/each} <Notification
type={$notificationStore.type}
message={$notificationStore.message}
icon={$notificationStore.icon}
/>
</div>
{/key}
{/if}
</div> </div>
<style> <style>
.notifications { .notifications {
position: fixed; position: fixed;
top: 10px; top: 20px;
left: 0; left: 0;
right: 0; right: 0;
margin: 0 auto; margin: 0 auto;
@ -42,19 +40,4 @@
align-items: center; align-items: center;
pointer-events: none; pointer-events: none;
} }
.toast {
flex: 0 0 auto;
margin-bottom: 10px;
border-radius: var(--border-radius-s);
/* The toasts now support being auto sized, so this static width could be removed */
width: 40vw;
}
.content {
padding: 10px;
display: block;
color: white;
font-weight: 600;
}
</style> </style>

View file

@ -0,0 +1,120 @@
<script>
import {
peekStore,
dataSourceStore,
notificationStore,
routeStore,
} from "../store"
import { Modal, ModalContent, ActionButton } from "@budibase/bbui"
import { onDestroy } from "svelte"
let iframe
let listenersAttached = false
const invalidateDataSource = event => {
const { dataSourceId } = event.detail
dataSourceStore.actions.invalidateDataSource(dataSourceId)
}
const proxyNotification = event => {
const { message, type, icon } = event.detail
notificationStore.actions.send(message, type, icon)
}
const attachListeners = () => {
// Mirror datasource invalidation to keep the parent window up to date
iframe.contentWindow.addEventListener(
"invalidate-datasource",
invalidateDataSource
)
// Listen for a close event to close the screen peek
iframe.contentWindow.addEventListener(
"close-screen-modal",
peekStore.actions.hidePeek
)
// Proxy notifications back to the parent window instead of iframe
iframe.contentWindow.addEventListener("notification", proxyNotification)
}
const handleCancel = () => {
peekStore.actions.hidePeek()
iframe.contentWindow.removeEventListener(
"invalidate-datasource",
invalidateDataSource
)
iframe.contentWindow.removeEventListener(
"close-screen-modal",
peekStore.actions.hidePeek
)
iframe.contentWindow.removeEventListener("notification", proxyNotification)
}
const handleFullscreen = () => {
if ($peekStore.external) {
window.location = $peekStore.href
} else {
routeStore.actions.navigate($peekStore.url)
handleCancel()
}
}
$: {
if (iframe && !listenersAttached) {
attachListeners()
listenersAttached = true
} else if (!iframe) {
listenersAttached = false
}
}
onDestroy(() => {
if (iframe) {
handleCancel()
}
})
</script>
{#if $peekStore.showPeek}
<Modal fixed on:cancel={handleCancel}>
<div class="actions spectrum--darkest" slot="outside">
<ActionButton size="S" quiet icon="OpenIn" on:click={handleFullscreen}>
Full screen
</ActionButton>
<ActionButton size="S" quiet icon="Close" on:click={handleCancel}>
Close
</ActionButton>
</div>
<ModalContent
showCancelButton={false}
showConfirmButton={false}
size="L"
showDivider={false}
showCloseIcon={false}
>
<iframe title="Peek" bind:this={iframe} src={$peekStore.href} />
</ModalContent>
</Modal>
{/if}
<style>
iframe {
margin: -40px;
border: none;
width: calc(100% + 80px);
height: 640px;
max-height: calc(100vh - 120px);
transition: width 1s ease, height 1s ease, top 1s ease, left 1s ease;
border-radius: var(--spectrum-global-dimension-size-100);
}
.actions {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
position: absolute;
top: 0;
width: 640px;
max-width: 100%;
}
</style>

View file

@ -1,6 +1,6 @@
<script> <script>
import { setContext, getContext } from "svelte" import { setContext, getContext } from "svelte"
import Router from "svelte-spa-router" import Router, { querystring } from "svelte-spa-router"
import { routeStore } from "../store" import { routeStore } from "../store"
import Screen from "./Screen.svelte" import Screen from "./Screen.svelte"
@ -16,6 +16,18 @@
id: $routeStore.routeSessionId, id: $routeStore.routeSessionId,
} }
// Keep query params up to date
$: {
let queryParams = {}
if ($querystring) {
const urlSearchParams = new URLSearchParams($querystring)
for (const [key, value] of urlSearchParams) {
queryParams[key] = value
}
}
routeStore.actions.setQueryParams(queryParams)
}
const getRouterConfig = routes => { const getRouterConfig = routes => {
let config = {} let config = {}
routes.forEach(route => { routes.forEach(route => {

View file

@ -15,7 +15,7 @@ import { ActionTypes } from "./constants"
export default { export default {
API, API,
authStore, authStore,
notifications: notificationStore, notificationStore,
routeStore, routeStore,
screenStore, screenStore,
builderStore, builderStore,

View file

@ -1,5 +1,4 @@
import { writable, get } from "svelte/store" import { writable, get } from "svelte/store"
import { notificationStore } from "./notification"
export const createDataSourceStore = () => { export const createDataSourceStore = () => {
const store = writable([]) const store = writable([])
@ -67,12 +66,17 @@ export const createDataSourceStore = () => {
const relatedInstances = get(store).filter(instance => { const relatedInstances = get(store).filter(instance => {
return instance.dataSourceId === dataSourceId return instance.dataSourceId === dataSourceId
}) })
if (relatedInstances?.length) {
notificationStore.blockNotifications(1000)
}
relatedInstances?.forEach(instance => { relatedInstances?.forEach(instance => {
instance.refresh() instance.refresh()
}) })
// Emit this as a window event, so parent screens which are iframing us in
// can also invalidate the same datasource
window.dispatchEvent(
new CustomEvent("invalidate-datasource", {
detail: { dataSourceId },
})
)
} }
return { return {

View file

@ -6,6 +6,7 @@ export { screenStore } from "./screens"
export { builderStore } from "./builder" export { builderStore } from "./builder"
export { dataSourceStore } from "./dataSource" export { dataSourceStore } from "./dataSource"
export { confirmationStore } from "./confirmation" export { confirmationStore } from "./confirmation"
export { peekStore } from "./peek"
// Context stores are layered and duplicated, so it is not a singleton // Context stores are layered and duplicated, so it is not a singleton
export { createContextStore } from "./context" export { createContextStore } from "./context"

View file

@ -1,56 +1,63 @@
import { writable } from "svelte/store" import { writable, get } from "svelte/store"
import { generate } from "shortid"
import { routeStore } from "./routes"
const NOTIFICATION_TIMEOUT = 3000 const NOTIFICATION_TIMEOUT = 3000
const createNotificationStore = () => { const createNotificationStore = () => {
const timeoutIds = new Set() let timeout
const _notifications = writable([], () => { let block = false
const store = writable(null, () => {
return () => { return () => {
// clear all the timers clearTimeout(timeout)
timeoutIds.forEach(timeoutId => {
clearTimeout(timeoutId)
})
_notifications.set([])
} }
}) })
let block = false
const blockNotifications = (timeout = 1000) => { const blockNotifications = (timeout = 1000) => {
block = true block = true
setTimeout(() => (block = false), timeout) setTimeout(() => (block = false), timeout)
} }
const send = (message, type = "default") => { const send = (message, type = "info", icon) => {
if (block) { if (block) {
return return
} }
let _id = id()
_notifications.update(state => {
return [...state, { id: _id, type, message }]
})
const timeoutId = setTimeout(() => {
_notifications.update(state => {
return state.filter(({ id }) => id !== _id)
})
}, NOTIFICATION_TIMEOUT)
timeoutIds.add(timeoutId)
}
const { subscribe } = _notifications // If peeking, pass notifications back to parent window
if (get(routeStore).queryParams?.peek) {
window.dispatchEvent(
new CustomEvent("notification", {
detail: { message, type, icon },
})
)
return
}
store.set({
id: generate(),
type,
message,
icon,
delay: get(store) != null,
})
clearTimeout(timeout)
timeout = setTimeout(() => {
store.set(null)
}, NOTIFICATION_TIMEOUT)
}
return { return {
subscribe, subscribe: store.subscribe,
send, actions: {
danger: msg => send(msg, "danger"), send,
warning: msg => send(msg, "warning"), info: msg => send(msg, "info", "Info"),
info: msg => send(msg, "info"), success: msg => send(msg, "success", "CheckmarkCircle"),
success: msg => send(msg, "success"), warning: msg => send(msg, "warning", "Alert"),
blockNotifications, error: msg => send(msg, "error", "Alert"),
blockNotifications,
},
} }
} }
function id() {
return "_" + Math.random().toString(36).substr(2, 9)
}
export const notificationStore = createNotificationStore() export const notificationStore = createNotificationStore()

View file

@ -0,0 +1,36 @@
import { writable } from "svelte/store"
const initialState = {
showPeek: false,
url: null,
href: null,
external: false,
}
const createPeekStore = () => {
const store = writable(initialState)
const showPeek = url => {
let href = url
let external = !url.startsWith("/")
if (!external) {
href = `${window.location.href.split("#")[0]}#${url}?peek=true`
}
store.set({
showPeek: true,
url,
href,
external,
})
}
const hidePeek = () => {
store.set(initialState)
}
return {
subscribe: store.subscribe,
actions: { showPeek, hidePeek },
}
}
export const peekStore = createPeekStore()

View file

@ -9,6 +9,7 @@ const createRouteStore = () => {
activeRoute: null, activeRoute: null,
routeSessionId: Math.random(), routeSessionId: Math.random(),
routerLoaded: false, routerLoaded: false,
queryParams: {},
} }
const store = writable(initialState) const store = writable(initialState)
@ -41,6 +42,17 @@ const createRouteStore = () => {
return state return state
}) })
} }
const setQueryParams = queryParams => {
store.update(state => {
state.queryParams = {
...queryParams,
// Never unset the peek param - screen peek modals should always be
// in a peek state, even if they navigate to a different page
peek: queryParams.peek || state.queryParams?.peek,
}
return state
})
}
const setActiveRoute = route => { const setActiveRoute = route => {
store.update(state => { store.update(state => {
state.activeRoute = state.routes.find(x => x.path === route) state.activeRoute = state.routes.find(x => x.path === route)
@ -58,6 +70,7 @@ const createRouteStore = () => {
fetchRoutes, fetchRoutes,
navigate, navigate,
setRouteParams, setRouteParams,
setQueryParams,
setActiveRoute, setActiveRoute,
setRouterLoaded, setRouterLoaded,
}, },

View file

@ -4,6 +4,7 @@ import {
builderStore, builderStore,
confirmationStore, confirmationStore,
authStore, authStore,
peekStore,
} from "../store" } from "../store"
import { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api" import { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api"
import { ActionTypes } from "../constants" import { ActionTypes } from "../constants"
@ -39,13 +40,17 @@ const triggerAutomationHandler = async action => {
} }
const navigationHandler = action => { const navigationHandler = action => {
const { url } = action.parameters const { url, peek } = action.parameters
if (url) { if (url) {
const external = !url.startsWith("/") if (peek) {
if (external) { peekStore.actions.showPeek(url)
window.location.href = url
} else { } else {
routeStore.actions.navigate(action.parameters.url) const external = !url.startsWith("/")
if (external) {
window.location.href = url
} else {
routeStore.actions.navigate(action.parameters.url)
}
} }
} }
} }
@ -94,6 +99,12 @@ const clearFormHandler = async (action, context) => {
) )
} }
const closeScreenModalHandler = () => {
// Emit this as a window event, so parent screens which are iframing us in
// can close the modal
window.dispatchEvent(new Event("close-screen-modal"))
}
const handlerMap = { const handlerMap = {
["Save Row"]: saveRowHandler, ["Save Row"]: saveRowHandler,
["Delete Row"]: deleteRowHandler, ["Delete Row"]: deleteRowHandler,
@ -104,6 +115,7 @@ const handlerMap = {
["Refresh Datasource"]: refreshDatasourceHandler, ["Refresh Datasource"]: refreshDatasourceHandler,
["Log Out"]: logoutHandler, ["Log Out"]: logoutHandler,
["Clear Form"]: clearFormHandler, ["Clear Form"]: clearFormHandler,
["Close Screen Modal"]: closeScreenModalHandler,
} }
const confirmTextMap = { const confirmTextMap = {

View file

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.9.87-alpha.7", "version": "0.9.96",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {
@ -60,9 +60,9 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.87-alpha.7", "@budibase/auth": "^0.9.96",
"@budibase/client": "^0.9.87-alpha.7", "@budibase/client": "^0.9.96",
"@budibase/string-templates": "^0.9.87-alpha.7", "@budibase/string-templates": "^0.9.96",
"@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",
@ -115,7 +115,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.87-alpha.7", "@budibase/standard-components": "^0.9.96",
"@jest/test-sequencer": "^24.8.0", "@jest/test-sequencer": "^24.8.0",
"@types/bull": "^3.15.1", "@types/bull": "^3.15.1",
"@types/jest": "^26.0.23", "@types/jest": "^26.0.23",

View file

@ -165,6 +165,10 @@ module External {
if (!row[key] || newRow[key] || field.autocolumn) { if (!row[key] || newRow[key] || field.autocolumn) {
continue continue
} }
// parse floats/numbers
if (field.type === FieldTypes.NUMBER && !isNaN(parseFloat(row[key]))) {
newRow[key] = parseFloat(row[key])
}
// if its not a link then just copy it over // if its not a link then just copy it over
if (field.type !== FieldTypes.LINK) { if (field.type !== FieldTypes.LINK) {
newRow[key] = row[key] newRow[key] = row[key]

View file

@ -19,8 +19,6 @@ function parseBody(body: any) {
} }
if (isIsoDateString(value)) { if (isIsoDateString(value)) {
body[key] = new Date(value) body[key] = new Date(value)
} else if (!isNaN(parseFloat(value))) {
body[key] = parseFloat(value)
} }
} }
return body return body

View file

@ -29,13 +29,12 @@
"keywords": [ "keywords": [
"svelte" "svelte"
], ],
"version": "0.9.87-alpha.7", "version": "0.9.96",
"license": "MIT", "license": "MIT",
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc", "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc",
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.87-alpha.7", "@budibase/bbui": "^0.9.96",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3",
"@spectrum-css/link": "^3.1.3", "@spectrum-css/link": "^3.1.3",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/typography": "^3.0.2", "@spectrum-css/typography": "^3.0.2",

View file

@ -1,6 +1,7 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { Heading, Icon } from "@budibase/bbui" import { Heading, Icon } from "@budibase/bbui"
import { routeStore } from "../../client/src/store"
const { styleable, linkable, builderStore } = getContext("sdk") const { styleable, linkable, builderStore } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
@ -26,6 +27,14 @@
Small: "s", Small: "s",
} }
// Permanently go into peek mode if we ever get the peek flag
let isPeeking = false
$: {
if ($routeStore.queryParams?.peek) {
isPeeking = true
}
}
$: validLinks = links?.filter(link => link.text && link.url) || [] $: validLinks = links?.filter(link => link.text && link.url) || []
$: typeClass = navigationClasses[navigation] || "none" $: typeClass = navigationClasses[navigation] || "none"
$: widthClass = widthClasses[width] || "l" $: widthClass = widthClasses[width] || "l"
@ -51,7 +60,7 @@
<div class="layout layout--{typeClass}" use:styleable={$component.styles}> <div class="layout layout--{typeClass}" use:styleable={$component.styles}>
{#if typeClass !== "none"} {#if typeClass !== "none"}
<div class="nav-wrapper" class:sticky> <div class="nav-wrapper" class:sticky class:hidden={isPeeking}>
<div class="nav nav--{typeClass} size--{widthClass}"> <div class="nav nav--{typeClass} size--{widthClass}">
<div class="nav-header"> <div class="nav-header">
{#if validLinks?.length} {#if validLinks?.length}
@ -139,6 +148,9 @@
border-bottom: 1px solid var(--spectrum-global-color-gray-300); border-bottom: 1px solid var(--spectrum-global-color-gray-300);
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.05); box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.05);
} }
.nav-wrapper.hidden {
display: none;
}
.layout--top .nav-wrapper.sticky { .layout--top .nav-wrapper.sticky {
position: sticky; position: sticky;
top: 0; top: 0;

View file

@ -14,7 +14,7 @@
export let underline export let underline
export let size export let size
$: external = url && !url.startsWith("/") $: external = url && typeof url === "string" && !url.startsWith("/")
$: target = openInNewTab ? "_blank" : "_self" $: target = openInNewTab ? "_blank" : "_self"
$: placeholder = $builderStore.inBuilder && !text $: placeholder = $builderStore.inBuilder && !text
$: componentText = $builderStore.inBuilder $: componentText = $builderStore.inBuilder

View file

@ -10,12 +10,12 @@
let fieldState let fieldState
let fieldApi let fieldApi
const { API, notifications } = getContext("sdk") const { API, notificationStore } = getContext("sdk")
const formContext = getContext("form") const formContext = getContext("form")
const BYTES_IN_MB = 1000000 const BYTES_IN_MB = 1000000
const handleFileTooLarge = fileSizeLimit => { const handleFileTooLarge = fileSizeLimit => {
notifications.warning( notificationStore.actions.warning(
`Files cannot exceed ${ `Files cannot exceed ${
fileSizeLimit / BYTES_IN_MB fileSizeLimit / BYTES_IN_MB
} MB. Please try again with smaller files.` } MB. Please try again with smaller files.`

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "0.9.87-alpha.7", "version": "0.9.96",
"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.87-alpha.7", "version": "0.9.96",
"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.87-alpha.7", "@budibase/auth": "^0.9.96",
"@budibase/string-templates": "^0.9.87-alpha.7", "@budibase/string-templates": "^0.9.96",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"@techpass/passport-openidconnect": "^0.3.0", "@techpass/passport-openidconnect": "^0.3.0",
"aws-sdk": "^2.811.0", "aws-sdk": "^2.811.0",