Merge branch 'develop' of github.com:Budibase/budibase into default-field-values
This commit is contained in:
commit
c3dde3e5f4
|
@ -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.
|
|
@ -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.
|
||||
|
||||
- **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).
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.9.87-alpha.7",
|
||||
"version": "0.9.96",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/auth",
|
||||
"version": "0.9.87-alpha.7",
|
||||
"version": "0.9.96",
|
||||
"description": "Authentication middlewares for budibase builder and apps",
|
||||
"main": "src/index.js",
|
||||
"author": "Budibase",
|
||||
|
|
|
@ -66,6 +66,7 @@ module.exports = (noAuthPatterns = [], opts) => {
|
|||
}
|
||||
}
|
||||
if (error) {
|
||||
console.error("Auth Error", error)
|
||||
// remove the cookie as the user does not exist anymore
|
||||
clearCookie(ctx, Cookies.Auth)
|
||||
} else {
|
||||
|
|
|
@ -6,7 +6,6 @@ const { authError } = require("./utils")
|
|||
const { newid } = require("../../hashing")
|
||||
const { createASession } = require("../../security/sessions")
|
||||
const { getGlobalUserByEmail } = require("../../utils")
|
||||
const fetch = require("node-fetch")
|
||||
|
||||
/**
|
||||
* Common authentication logic for third parties. e.g. OAuth, OIDC.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/bbui",
|
||||
"description": "A UI solution used in the different Budibase projects.",
|
||||
"version": "0.9.87-alpha.7",
|
||||
"version": "0.9.96",
|
||||
"license": "AGPL-3.0",
|
||||
"svelte": "src/index.js",
|
||||
"module": "dist/bbui.es.js",
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
>
|
||||
<div class="modal-wrapper" on:mousedown|self={cancel}>
|
||||
<div class="modal-inner-wrapper" on:mousedown|self={cancel}>
|
||||
<slot name="outside" />
|
||||
<div
|
||||
use:focusFirstInput
|
||||
class="spectrum-Modal is-open"
|
||||
|
@ -93,6 +94,7 @@
|
|||
z-index: 999;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
.modal-wrapper {
|
||||
|
@ -112,6 +114,7 @@
|
|||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
width: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.spectrum-Modal {
|
||||
|
@ -122,6 +125,7 @@
|
|||
--spectrum-dialog-confirm-border-radius: var(
|
||||
--spectrum-global-dimension-size-100
|
||||
);
|
||||
max-width: 100%;
|
||||
}
|
||||
:global(.spectrum--lightest .spectrum-Modal.inline) {
|
||||
border: var(--border-light);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
export let showCloseIcon = true
|
||||
export let onConfirm = undefined
|
||||
export let disabled = false
|
||||
export let showDivider = true
|
||||
|
||||
const { hide, cancel } = getContext(Context.Modal)
|
||||
let loading = false
|
||||
|
@ -41,11 +42,17 @@
|
|||
aria-modal="true"
|
||||
>
|
||||
<div class="spectrum-Dialog-grid">
|
||||
<h1 class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<Divider size="M" />
|
||||
{#if title}
|
||||
<h1
|
||||
class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader"
|
||||
class:noDivider={!showDivider}
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
{#if showDivider}
|
||||
<Divider size="M" />
|
||||
{/if}
|
||||
{/if}
|
||||
<!-- TODO: Remove content-grid class once Layout components are in bbui -->
|
||||
<section class="spectrum-Dialog-content content-grid">
|
||||
<slot />
|
||||
|
@ -72,8 +79,8 @@
|
|||
</div>
|
||||
{/if}
|
||||
{#if showCloseIcon}
|
||||
<div class="close-icon" on:click={hide}>
|
||||
<Icon hoverable name="Close" />
|
||||
<div class="close-icon">
|
||||
<Icon hoverable name="Close" on:click={cancel} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -96,6 +103,9 @@
|
|||
.spectrum-Dialog-heading {
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
.spectrum-Dialog-heading.noDivider {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.spectrum-Dialog-buttonGroup {
|
||||
gap: var(--spectrum-global-dimension-static-size-200);
|
||||
|
|
|
@ -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>
|
|
@ -2,30 +2,16 @@
|
|||
import "@spectrum-css/toast/dist/index-vars.css"
|
||||
import Portal from "svelte-portal"
|
||||
import { flip } from "svelte/animate"
|
||||
import { fly } from "svelte/transition"
|
||||
import { notifications } from "../Stores/notifications"
|
||||
import Notification from "./Notification.svelte"
|
||||
import { fly } from "svelte/transition"
|
||||
</script>
|
||||
|
||||
<Portal target=".modal-container">
|
||||
<div class="notifications">
|
||||
{#each $notifications as { type, icon, message, id } (id)}
|
||||
<div
|
||||
animate:flip
|
||||
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 animate:flip transition:fly={{ y: -30 }}>
|
||||
<Notification {type} {icon} {message} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
@ -34,7 +20,7 @@
|
|||
<style>
|
||||
.notifications {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
top: 20px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
|
@ -45,8 +31,6 @@
|
|||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
.notification-offset {
|
||||
margin-bottom: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -38,6 +38,7 @@ export { default as MenuItem } from "./Menu/Item.svelte"
|
|||
export { default as Modal } from "./Modal/Modal.svelte"
|
||||
export { default as ModalContent } from "./Modal/ModalContent.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 SideNavigationItem } from "./SideNavigation/Item.svelte"
|
||||
export { default as DatePicker } from "./Form/DatePicker.svelte"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "0.9.87-alpha.7",
|
||||
"version": "0.9.96",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -65,10 +65,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^0.9.87-alpha.7",
|
||||
"@budibase/client": "^0.9.87-alpha.7",
|
||||
"@budibase/bbui": "^0.9.96",
|
||||
"@budibase/client": "^0.9.96",
|
||||
"@budibase/colorpicker": "1.1.2",
|
||||
"@budibase/string-templates": "^0.9.87-alpha.7",
|
||||
"@budibase/string-templates": "^0.9.96",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
"@spectrum-css/vars": "^3.0.1",
|
||||
|
|
|
@ -70,7 +70,7 @@ export const getFrontendStore = () => {
|
|||
url: application.url,
|
||||
layouts,
|
||||
screens,
|
||||
theme: application.theme,
|
||||
theme: application.theme || "spectrum--light",
|
||||
hasAppPackage: true,
|
||||
appInstance: application.instance,
|
||||
clientLibPath,
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
const dispatch = createEventDispatcher()
|
||||
let bindingDrawer
|
||||
$: tempValue = Array.isArray(value) ? value : []
|
||||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||
$: tempValue = readableValue
|
||||
|
||||
const handleClose = () => {
|
||||
onChange(tempValue)
|
||||
|
@ -56,7 +56,7 @@
|
|||
slot="body"
|
||||
value={readableValue}
|
||||
close={handleClose}
|
||||
on:update={event => (tempValue = event.detail)}
|
||||
on:change={event => (tempValue = event.detail)}
|
||||
bindableProperties={bindings}
|
||||
/>
|
||||
</Drawer>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
<div>
|
||||
<Select
|
||||
value={$store.theme || "spectrum--light"}
|
||||
value={$store.theme}
|
||||
options={themeOptions}
|
||||
placeholder={null}
|
||||
on:change={e => store.actions.theme.save(e.detail)}
|
||||
|
|
|
@ -47,6 +47,18 @@ export default `
|
|||
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
|
||||
const {
|
||||
selectedComponentId,
|
||||
|
@ -55,7 +67,7 @@ export default `
|
|||
previewType,
|
||||
appId,
|
||||
theme
|
||||
} = JSON.parse(event.data)
|
||||
} = parsed
|
||||
|
||||
// Set some flags so the app knows we're in the builder
|
||||
window["##BUDIBASE_IN_BUILDER##"] = true
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
export let componentDefinition
|
||||
export let componentInstance
|
||||
export let assetInstance
|
||||
export let bindings
|
||||
|
||||
const layoutDefinition = []
|
||||
const screenDefinition = [
|
||||
|
@ -65,6 +66,7 @@
|
|||
options: setting.options,
|
||||
placeholder: setting.placeholder,
|
||||
}}
|
||||
{bindings}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import ConditionalUIDrawer from "./PropertyControls/ConditionalUIDrawer.svelte"
|
||||
|
||||
export let componentInstance
|
||||
export let bindings
|
||||
|
||||
let tempValue
|
||||
let drawer
|
||||
|
@ -32,5 +33,5 @@
|
|||
Show, hide and update components in response to conditions being met.
|
||||
</svelte:fragment>
|
||||
<Button cta slot="buttons" on:click={() => save()}>Save</Button>
|
||||
<ConditionalUIDrawer slot="body" bind:conditions={tempValue} />
|
||||
<ConditionalUIDrawer slot="body" bind:conditions={tempValue} {bindings} />
|
||||
</Drawer>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
export let componentDefinition
|
||||
export let componentInstance
|
||||
export let bindings
|
||||
|
||||
const getStyles = def => {
|
||||
if (!def?.styles?.length) {
|
||||
|
@ -29,6 +30,7 @@
|
|||
columns={style.columns}
|
||||
properties={style.settings}
|
||||
{componentInstance}
|
||||
{bindings}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
|
|
|
@ -1,27 +1,45 @@
|
|||
<script>
|
||||
import { store, selectedComponent } from "builderStore"
|
||||
import { store, selectedComponent, currentAsset } from "builderStore"
|
||||
import { Tabs, Tab } from "@budibase/bbui"
|
||||
import ScreenSettingsSection from "./ScreenSettingsSection.svelte"
|
||||
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
||||
import DesignSection from "./DesignSection.svelte"
|
||||
import CustomStylesSection from "./CustomStylesSection.svelte"
|
||||
import ConditionalUISection from "./ConditionalUISection.svelte"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
|
||||
$: componentInstance = $selectedComponent
|
||||
$: componentDefinition = store.actions.components.getDefinition(
|
||||
$selectedComponent?._component
|
||||
)
|
||||
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
||||
</script>
|
||||
|
||||
<Tabs selected="Settings" noPadding>
|
||||
<Tab title="Settings">
|
||||
<div class="container">
|
||||
{#key componentInstance?._id}
|
||||
<ScreenSettingsSection {componentInstance} {componentDefinition} />
|
||||
<ComponentSettingsSection {componentInstance} {componentDefinition} />
|
||||
<DesignSection {componentInstance} {componentDefinition} />
|
||||
<CustomStylesSection {componentInstance} {componentDefinition} />
|
||||
<ConditionalUISection {componentInstance} {componentDefinition} />
|
||||
<ScreenSettingsSection
|
||||
{componentInstance}
|
||||
{componentDefinition}
|
||||
{bindings}
|
||||
/>
|
||||
<ComponentSettingsSection
|
||||
{componentInstance}
|
||||
{componentDefinition}
|
||||
{bindings}
|
||||
/>
|
||||
<DesignSection {componentInstance} {componentDefinition} {bindings} />
|
||||
<CustomStylesSection
|
||||
{componentInstance}
|
||||
{componentDefinition}
|
||||
{bindings}
|
||||
/>
|
||||
<ConditionalUISection
|
||||
{componentInstance}
|
||||
{componentDefinition}
|
||||
{bindings}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
</Tab>
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
import { generate } from "shortid"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
import { currentAsset, selectedComponent, store } from "builderStore"
|
||||
import { selectedComponent, store } from "builderStore"
|
||||
import { getComponentForSettingType } from "./componentSettings"
|
||||
import PropertyControl from "./PropertyControl.svelte"
|
||||
|
||||
export let conditions = []
|
||||
export let bindings = []
|
||||
|
||||
const flipDurationMs = 150
|
||||
const actionOptions = [
|
||||
|
@ -64,10 +64,6 @@
|
|||
value: setting.key,
|
||||
}
|
||||
})
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
$: conditions.forEach(link => {
|
||||
if (!link.id) {
|
||||
link.id = generate()
|
||||
|
@ -194,6 +190,7 @@
|
|||
placeholder: getSettingDefinition(condition.setting)
|
||||
.placeholder,
|
||||
}}
|
||||
{bindings}
|
||||
/>
|
||||
{:else}
|
||||
<Select disabled placeholder=" " />
|
||||
|
@ -201,7 +198,7 @@
|
|||
{/if}
|
||||
<div>IF</div>
|
||||
<DrawerBindableInput
|
||||
bindings={bindableProperties}
|
||||
{bindings}
|
||||
placeholder="Value"
|
||||
value={condition.newValue}
|
||||
on:change={e => (condition.newValue = e.detail)}
|
||||
|
@ -222,7 +219,7 @@
|
|||
{#if ["string", "number"].includes(condition.valueType)}
|
||||
<DrawerBindableInput
|
||||
disabled={condition.noValue}
|
||||
bindings={bindableProperties}
|
||||
{bindings}
|
||||
placeholder="Value"
|
||||
value={condition.referenceValue}
|
||||
on:change={e => (condition.referenceValue = e.detail)}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<script>
|
||||
import {
|
||||
getBindableProperties,
|
||||
getDataProviderComponents,
|
||||
} from "builderStore/dataBinding"
|
||||
import { getDataProviderComponents } from "builderStore/dataBinding"
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
|
@ -31,6 +28,7 @@
|
|||
export let value = {}
|
||||
export let otherSources
|
||||
export let showAllQueries
|
||||
export let bindings = []
|
||||
|
||||
$: text = value?.label ?? "Choose an option"
|
||||
$: tables = $tablesStore.list.map(m => ({
|
||||
|
@ -60,10 +58,6 @@
|
|||
parameters: query.parameters,
|
||||
type: "query",
|
||||
}))
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
$: dataProviders = getDataProviderComponents(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId
|
||||
|
@ -75,13 +69,13 @@
|
|||
type: "provider",
|
||||
schema: provider.schema,
|
||||
}))
|
||||
$: queryBindableProperties = bindableProperties.map(property => ({
|
||||
$: queryBindableProperties = bindings.map(property => ({
|
||||
...property,
|
||||
category: property.type === "instance" ? "Component" : "Table",
|
||||
label: property.readableBinding,
|
||||
path: property.readableBinding,
|
||||
}))
|
||||
$: links = bindableProperties
|
||||
$: links = bindings
|
||||
.filter(x => x.fieldSchema?.type === "link")
|
||||
.map(property => {
|
||||
return {
|
||||
|
@ -138,7 +132,7 @@
|
|||
bind:customParams={value.queryParams}
|
||||
parameters={queries.find(query => query._id === value._id)
|
||||
.parameters}
|
||||
bindings={queryBindableProperties}
|
||||
{bindings}
|
||||
/>
|
||||
{/if}
|
||||
<IntegrationQueryEditor
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
import { generate } from "shortid"
|
||||
|
||||
const flipDurationMs = 150
|
||||
|
||||
const EVENT_TYPE_KEY = "##eventHandlerType"
|
||||
|
||||
export let actions
|
||||
export let bindings = []
|
||||
|
||||
// dndzone needs an id on the array items, so this adds some temporary ones.
|
||||
$: {
|
||||
|
@ -121,6 +121,7 @@
|
|||
<svelte:component
|
||||
this={selectedActionComponent}
|
||||
parameters={selectedAction.parameters}
|
||||
{bindings}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export let value = []
|
||||
export let name
|
||||
export let bindings
|
||||
|
||||
let drawer
|
||||
|
||||
|
@ -57,5 +58,5 @@
|
|||
Define what actions to run.
|
||||
</svelte:fragment>
|
||||
<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>
|
||||
|
|
|
@ -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>
|
|
@ -1,14 +1,12 @@
|
|||
<script>
|
||||
import { Select, Label, Checkbox, Input } from "@budibase/bbui"
|
||||
import { store, currentAsset } from "builderStore"
|
||||
import { tables } from "stores/backend"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
|
||||
export let parameters
|
||||
export let bindings = []
|
||||
|
||||
$: tableOptions = $tables.list || []
|
||||
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
<script>
|
||||
import { Select, Layout, Input, Checkbox } from "@budibase/bbui"
|
||||
import { store, currentAsset } from "builderStore"
|
||||
import { datasources, integrations, queries } from "stores/backend"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||
|
||||
export let parameters
|
||||
export let bindings = []
|
||||
|
||||
$: query = $queries.list.find(q => q._id === parameters.queryId)
|
||||
$: datasource = $datasources.list.find(
|
||||
ds => ds._id === parameters.datasourceId
|
||||
)
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
|
||||
function fetchQueryDefinition(query) {
|
||||
const source = $datasources.list.find(
|
||||
|
@ -61,7 +56,7 @@
|
|||
<ParameterBuilder
|
||||
bind:customParams={parameters.queryParams}
|
||||
parameters={query.parameters}
|
||||
bindings={bindableProperties}
|
||||
{bindings}
|
||||
/>
|
||||
<IntegrationQueryEditor
|
||||
height={200}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
<script>
|
||||
import { Label } from "@budibase/bbui"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
import { currentAsset, store } from "builderStore"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
|
||||
export let parameters
|
||||
|
||||
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
||||
export let bindings = []
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
|
@ -18,19 +15,17 @@
|
|||
on:change={value => (parameters.url = value.detail)}
|
||||
{bindings}
|
||||
/>
|
||||
<div />
|
||||
<Checkbox text="Open screen in modal" bind:value={parameters.peek} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
grid-template-columns: auto 1fr;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.root :global(> div) {
|
||||
flex: 1;
|
||||
margin-left: var(--spacing-l);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<script>
|
||||
import { Label, ActionButton, Button, Select, Input } from "@budibase/bbui"
|
||||
import { store, currentAsset } from "builderStore"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
|
||||
|
@ -11,13 +9,10 @@
|
|||
export let schemaFields
|
||||
export let fieldLabel = "Column"
|
||||
export let valueLabel = "Value"
|
||||
export let bindings = []
|
||||
|
||||
let fields = Object.entries(parameterFields || {})
|
||||
$: onChange(fields)
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
|
||||
const addField = () => {
|
||||
fields = [...fields.filter(field => field[0]), ["", ""]]
|
||||
|
@ -69,7 +64,7 @@
|
|||
<DrawerBindableInput
|
||||
title={`Value for "${field[0]}"`}
|
||||
value={field[1]}
|
||||
bindings={bindableProperties}
|
||||
{bindings}
|
||||
on:change={event => updateFieldValue(idx, event.detail)}
|
||||
/>
|
||||
<ActionButton
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import SaveFields from "./SaveFields.svelte"
|
||||
|
||||
export let parameters
|
||||
export let bindings = []
|
||||
|
||||
$: dataProviderComponents = getDataProviderComponents(
|
||||
$currentAsset,
|
||||
|
@ -70,6 +71,7 @@
|
|||
parameterFields={parameters.fields}
|
||||
{schemaFields}
|
||||
on:change={onFieldsChanged}
|
||||
{bindings}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
import { automationStore } from "builderStore"
|
||||
import SaveFields from "./SaveFields.svelte"
|
||||
|
||||
export let parameters = {}
|
||||
export let bindings = []
|
||||
|
||||
const AUTOMATION_STATUS = {
|
||||
NEW: "new",
|
||||
EXISTING: "existing",
|
||||
}
|
||||
|
||||
export let parameters = {}
|
||||
|
||||
let automationStatus = parameters.automationId
|
||||
? AUTOMATION_STATUS.EXISTING
|
||||
: AUTOMATION_STATUS.NEW
|
||||
|
@ -109,6 +110,7 @@
|
|||
parameterFields={parameters.fields}
|
||||
fieldLabel="Field"
|
||||
on:change={onFieldsChanged}
|
||||
{bindings}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
|
|
|
@ -6,6 +6,7 @@ import TriggerAutomation from "./TriggerAutomation.svelte"
|
|||
import ValidateForm from "./ValidateForm.svelte"
|
||||
import LogOut from "./LogOut.svelte"
|
||||
import ClearForm from "./ClearForm.svelte"
|
||||
import CloseScreenModal from "./CloseScreenModal.svelte"
|
||||
|
||||
// Defines which actions are available to configure in the front end.
|
||||
// Unfortunately the "name" property is used as the identifier so please don't
|
||||
|
@ -47,4 +48,8 @@ export default [
|
|||
name: "Clear Form",
|
||||
component: ClearForm,
|
||||
},
|
||||
{
|
||||
name: "Close Screen Modal",
|
||||
component: CloseScreenModal,
|
||||
},
|
||||
]
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
export let value = []
|
||||
export let componentInstance
|
||||
export let bindings = []
|
||||
|
||||
let drawer
|
||||
let tempValue = value || []
|
||||
|
||||
|
@ -51,7 +53,7 @@
|
|||
constraints.
|
||||
{/if}
|
||||
</Body>
|
||||
<LuceneFilterBuilder bind:value={tempValue} {schemaFields} />
|
||||
<LuceneFilterBuilder bind:value={tempValue} {schemaFields} {bindings} />
|
||||
</Layout>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
|
|
@ -7,20 +7,15 @@
|
|||
Combobox,
|
||||
Input,
|
||||
} from "@budibase/bbui"
|
||||
import { store, currentAsset } from "builderStore"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
import { generate } from "shortid"
|
||||
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
|
||||
|
||||
export let schemaFields
|
||||
export let value
|
||||
export let bindings = []
|
||||
|
||||
const BannedTypes = ["link", "attachment"]
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
$: fieldOptions = (schemaFields ?? [])
|
||||
.filter(field => !BannedTypes.includes(field.type))
|
||||
.map(field => field.name)
|
||||
|
@ -101,7 +96,7 @@
|
|||
title={`Value for "${expression.field}"`}
|
||||
value={expression.value}
|
||||
placeholder="Value"
|
||||
bindings={bindableProperties}
|
||||
{bindings}
|
||||
on:change={event => (expression.value = event.detail)}
|
||||
/>
|
||||
{:else if ["string", "longform", "number"].includes(expression.type)}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
<script>
|
||||
import { Button, Icon, Drawer, Label } from "@budibase/bbui"
|
||||
import { store, currentAsset } from "builderStore"
|
||||
import {
|
||||
getBindableProperties,
|
||||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "builderStore/dataBinding"
|
||||
|
@ -18,18 +16,15 @@
|
|||
export let value = null
|
||||
export let props = {}
|
||||
export let onChange = () => {}
|
||||
export let bindings = []
|
||||
|
||||
let bindingDrawer
|
||||
let anchor
|
||||
let valid
|
||||
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
$: safeValue = getSafeValue(value, props.defaultValue, bindableProperties)
|
||||
$: safeValue = getSafeValue(value, props.defaultValue, bindings)
|
||||
$: tempValue = safeValue
|
||||
$: replaceBindings = val => readableToRuntimeBinding(bindableProperties, val)
|
||||
$: replaceBindings = val => readableToRuntimeBinding(bindings, val)
|
||||
|
||||
const handleClose = () => {
|
||||
handleChange(tempValue)
|
||||
|
@ -61,8 +56,8 @@
|
|||
|
||||
// The "safe" value is the value with any bindings made readable
|
||||
// If there is no value set, any default value is used
|
||||
const getSafeValue = (value, defaultValue, bindableProperties) => {
|
||||
const enriched = runtimeToReadableBinding(bindableProperties, value)
|
||||
const getSafeValue = (value, defaultValue, bindings) => {
|
||||
const enriched = runtimeToReadableBinding(bindings, value)
|
||||
return enriched == null && defaultValue !== undefined
|
||||
? defaultValue
|
||||
: enriched
|
||||
|
@ -83,6 +78,7 @@
|
|||
updateOnChange={false}
|
||||
on:change={handleChange}
|
||||
onChange={handleChange}
|
||||
{bindings}
|
||||
name={key}
|
||||
text={label}
|
||||
{type}
|
||||
|
@ -108,7 +104,7 @@
|
|||
bind:valid
|
||||
value={safeValue}
|
||||
on:change={e => (tempValue = e.detail)}
|
||||
{bindableProperties}
|
||||
bindableProperties={bindings}
|
||||
/>
|
||||
</Drawer>
|
||||
{/if}
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCombobox.svelte"
|
||||
|
||||
export let value
|
||||
export let bindings
|
||||
|
||||
$: urlOptions = $store.screens
|
||||
.map(screen => screen.routing?.route)
|
||||
.filter(x => x != null)
|
||||
</script>
|
||||
|
||||
<DrawerBindableCombobox {value} on:change options={urlOptions} />
|
||||
<DrawerBindableCombobox {value} {bindings} on:change options={urlOptions} />
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { FrontendTypes } from "constants"
|
||||
|
||||
export let componentInstance
|
||||
export let bindings
|
||||
|
||||
function setAssetProps(name, value) {
|
||||
const selectedAsset = get(currentAsset)
|
||||
|
@ -44,6 +45,7 @@
|
|||
key={def.key}
|
||||
value={deepGet($currentAsset, def.key)}
|
||||
onChange={val => setAssetProps(def.key, val)}
|
||||
{bindings}
|
||||
/>
|
||||
{/each}
|
||||
</DetailSummary>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
export let columns
|
||||
export let properties
|
||||
export let componentInstance
|
||||
export let bindings = []
|
||||
|
||||
$: style = componentInstance._styles.normal || {}
|
||||
$: changed = properties?.some(prop => hasPropChanged(style, prop)) ?? false
|
||||
|
@ -36,6 +37,7 @@
|
|||
value={style[prop.key]}
|
||||
onChange={val => store.actions.components.updateStyle(prop.key, val)}
|
||||
props={getControlProps(prop)}
|
||||
{bindings}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
|
|
|
@ -158,10 +158,16 @@
|
|||
fieldName: fromTable.primary[0],
|
||||
}
|
||||
} 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,
|
||||
foreignKey: relateFrom.fieldName,
|
||||
fieldName: fromTable.primary[0],
|
||||
foreignKey: fromTable.primary[0],
|
||||
}
|
||||
relateTo = {
|
||||
...relateTo,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/cli",
|
||||
"version": "0.9.87-alpha.7",
|
||||
"version": "0.9.96",
|
||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/client",
|
||||
"version": "0.9.87-alpha.7",
|
||||
"version": "0.9.96",
|
||||
"license": "MPL-2.0",
|
||||
"module": "dist/budibase-client.js",
|
||||
"main": "dist/budibase-client.js",
|
||||
|
@ -18,9 +18,9 @@
|
|||
"dev:builder": "rollup -cw"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^0.9.87-alpha.7",
|
||||
"@budibase/standard-components": "^0.9.87-alpha.7",
|
||||
"@budibase/string-templates": "^0.9.87-alpha.7",
|
||||
"@budibase/bbui": "^0.9.96",
|
||||
"@budibase/standard-components": "^0.9.96",
|
||||
"@budibase/string-templates": "^0.9.96",
|
||||
"regexparam": "^1.3.0",
|
||||
"shortid": "^2.2.15",
|
||||
"svelte-spa-router": "^3.0.5"
|
||||
|
|
|
@ -38,15 +38,15 @@ const makeApiCall = async ({ method, url, body, json = true }) => {
|
|||
case 200:
|
||||
return response.json()
|
||||
case 401:
|
||||
notificationStore.danger("Invalid credentials")
|
||||
notificationStore.actions.error("Invalid credentials")
|
||||
return handleError(`Invalid credentials`)
|
||||
case 404:
|
||||
notificationStore.danger("Not found")
|
||||
notificationStore.actions.warning("Not found")
|
||||
return handleError(`${url}: Not Found`)
|
||||
case 400:
|
||||
return handleError(`${url}: Bad Request`)
|
||||
case 403:
|
||||
notificationStore.danger(
|
||||
notificationStore.actions.error(
|
||||
"Your session has expired, or you don't have permission to access that data"
|
||||
)
|
||||
return handleError(`${url}: Forbidden`)
|
||||
|
|
|
@ -9,7 +9,7 @@ export const triggerAutomation = async (automationId, fields) => {
|
|||
body: { fields },
|
||||
})
|
||||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success("Automation triggered")
|
||||
? notificationStore.actions.error("An error has occurred")
|
||||
: notificationStore.actions.success("Automation triggered")
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import API from "./api"
|
|||
export const executeQuery = async ({ queryId, parameters }) => {
|
||||
const query = await API.get({ url: `/api/queries/${queryId}` })
|
||||
if (query?.datasourceId == null) {
|
||||
notificationStore.danger("That query couldn't be found")
|
||||
notificationStore.actions.error("That query couldn't be found")
|
||||
return
|
||||
}
|
||||
const res = await API.post({
|
||||
|
@ -17,9 +17,9 @@ export const executeQuery = async ({ queryId, parameters }) => {
|
|||
},
|
||||
})
|
||||
if (res.error) {
|
||||
notificationStore.danger("An error has occurred")
|
||||
notificationStore.actions.error("An error has occurred")
|
||||
} else if (!query.readable) {
|
||||
notificationStore.success("Query executed successfully")
|
||||
notificationStore.actions.success("Query executed successfully")
|
||||
dataSourceStore.actions.invalidateDataSource(query.datasourceId)
|
||||
}
|
||||
return res
|
||||
|
|
|
@ -27,8 +27,8 @@ export const saveRow = async row => {
|
|||
body: row,
|
||||
})
|
||||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success("Row saved")
|
||||
? notificationStore.actions.error("An error has occurred")
|
||||
: notificationStore.actions.success("Row saved")
|
||||
|
||||
// Refresh related datasources
|
||||
dataSourceStore.actions.invalidateDataSource(row.tableId)
|
||||
|
@ -48,8 +48,8 @@ export const updateRow = async row => {
|
|||
body: row,
|
||||
})
|
||||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success("Row updated")
|
||||
? notificationStore.actions.error("An error has occurred")
|
||||
: notificationStore.actions.success("Row updated")
|
||||
|
||||
// Refresh related datasources
|
||||
dataSourceStore.actions.invalidateDataSource(row.tableId)
|
||||
|
@ -72,8 +72,8 @@ export const deleteRow = async ({ tableId, rowId, revId }) => {
|
|||
},
|
||||
})
|
||||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success("Row deleted")
|
||||
? notificationStore.actions.error("An error has occurred")
|
||||
: notificationStore.actions.success("Row deleted")
|
||||
|
||||
// Refresh related datasources
|
||||
dataSourceStore.actions.invalidateDataSource(tableId)
|
||||
|
@ -95,8 +95,8 @@ export const deleteRows = async ({ tableId, rows }) => {
|
|||
},
|
||||
})
|
||||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success(`${rows.length} row(s) deleted`)
|
||||
? notificationStore.actions.error("An error has occurred")
|
||||
: notificationStore.actions.success(`${rows.length} row(s) deleted`)
|
||||
|
||||
// Refresh related datasources
|
||||
dataSourceStore.actions.invalidateDataSource(tableId)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import Component from "./Component.svelte"
|
||||
import NotificationDisplay from "./NotificationDisplay.svelte"
|
||||
import ConfirmationDisplay from "./ConfirmationDisplay.svelte"
|
||||
import PeekScreenDisplay from "./PeekScreenDisplay.svelte"
|
||||
import Provider from "./Provider.svelte"
|
||||
import SDK from "../sdk"
|
||||
import {
|
||||
|
@ -93,13 +94,14 @@
|
|||
</div>
|
||||
{:else if $screenStore.activeLayout}
|
||||
<Provider key="user" data={$authStore} {actions}>
|
||||
<div id="app-root">
|
||||
<div id="app-root" class:preview={$builderStore.inBuilder}>
|
||||
{#key $screenStore.activeLayout._id}
|
||||
<Component instance={$screenStore.activeLayout.props} />
|
||||
{/key}
|
||||
</div>
|
||||
<NotificationDisplay />
|
||||
<ConfirmationDisplay />
|
||||
<PeekScreenDisplay />
|
||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||
{#key $builderStore.selectedComponentId}
|
||||
{#if $builderStore.inBuilder}
|
||||
|
@ -131,6 +133,9 @@
|
|||
#app-root {
|
||||
position: relative;
|
||||
}
|
||||
#app-root.preview {
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
}
|
||||
|
||||
/* Custom scrollbars */
|
||||
:global(::-webkit-scrollbar) {
|
||||
|
|
|
@ -1,36 +1,34 @@
|
|||
<script>
|
||||
import { flip } from "svelte/animate"
|
||||
import { notificationStore } from "../store"
|
||||
import { Notification } from "@budibase/bbui"
|
||||
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>
|
||||
|
||||
<div class="notifications">
|
||||
{#each $notifications as notification (notification.id)}
|
||||
<div
|
||||
animate:flip
|
||||
class="toast"
|
||||
style="background: {themes[notification.type]};"
|
||||
transition:fly={{ y: -30 }}
|
||||
>
|
||||
<div class="content">{notification.message}</div>
|
||||
{#if notification.icon}<i class={notification.icon} />{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{#if $notificationStore}
|
||||
{#key $notificationStore.id}
|
||||
<div
|
||||
in:fly={{
|
||||
duration: 300,
|
||||
y: -20,
|
||||
delay: $notificationStore.delay ? 300 : 0,
|
||||
}}
|
||||
out:fly={{ y: -20, duration: 150 }}
|
||||
>
|
||||
<Notification
|
||||
type={$notificationStore.type}
|
||||
message={$notificationStore.message}
|
||||
icon={$notificationStore.icon}
|
||||
/>
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.notifications {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
top: 20px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
|
@ -42,19 +40,4 @@
|
|||
align-items: center;
|
||||
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>
|
||||
|
|
|
@ -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>
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { setContext, getContext } from "svelte"
|
||||
import Router from "svelte-spa-router"
|
||||
import Router, { querystring } from "svelte-spa-router"
|
||||
import { routeStore } from "../store"
|
||||
import Screen from "./Screen.svelte"
|
||||
|
||||
|
@ -16,6 +16,18 @@
|
|||
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 => {
|
||||
let config = {}
|
||||
routes.forEach(route => {
|
||||
|
|
|
@ -15,7 +15,7 @@ import { ActionTypes } from "./constants"
|
|||
export default {
|
||||
API,
|
||||
authStore,
|
||||
notifications: notificationStore,
|
||||
notificationStore,
|
||||
routeStore,
|
||||
screenStore,
|
||||
builderStore,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { notificationStore } from "./notification"
|
||||
|
||||
export const createDataSourceStore = () => {
|
||||
const store = writable([])
|
||||
|
@ -67,12 +66,17 @@ export const createDataSourceStore = () => {
|
|||
const relatedInstances = get(store).filter(instance => {
|
||||
return instance.dataSourceId === dataSourceId
|
||||
})
|
||||
if (relatedInstances?.length) {
|
||||
notificationStore.blockNotifications(1000)
|
||||
}
|
||||
relatedInstances?.forEach(instance => {
|
||||
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 {
|
||||
|
|
|
@ -6,6 +6,7 @@ export { screenStore } from "./screens"
|
|||
export { builderStore } from "./builder"
|
||||
export { dataSourceStore } from "./dataSource"
|
||||
export { confirmationStore } from "./confirmation"
|
||||
export { peekStore } from "./peek"
|
||||
|
||||
// Context stores are layered and duplicated, so it is not a singleton
|
||||
export { createContextStore } from "./context"
|
||||
|
|
|
@ -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 createNotificationStore = () => {
|
||||
const timeoutIds = new Set()
|
||||
const _notifications = writable([], () => {
|
||||
let timeout
|
||||
let block = false
|
||||
|
||||
const store = writable(null, () => {
|
||||
return () => {
|
||||
// clear all the timers
|
||||
timeoutIds.forEach(timeoutId => {
|
||||
clearTimeout(timeoutId)
|
||||
})
|
||||
_notifications.set([])
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
})
|
||||
let block = false
|
||||
|
||||
const blockNotifications = (timeout = 1000) => {
|
||||
block = true
|
||||
setTimeout(() => (block = false), timeout)
|
||||
}
|
||||
|
||||
const send = (message, type = "default") => {
|
||||
const send = (message, type = "info", icon) => {
|
||||
if (block) {
|
||||
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 {
|
||||
subscribe,
|
||||
send,
|
||||
danger: msg => send(msg, "danger"),
|
||||
warning: msg => send(msg, "warning"),
|
||||
info: msg => send(msg, "info"),
|
||||
success: msg => send(msg, "success"),
|
||||
blockNotifications,
|
||||
subscribe: store.subscribe,
|
||||
actions: {
|
||||
send,
|
||||
info: msg => send(msg, "info", "Info"),
|
||||
success: msg => send(msg, "success", "CheckmarkCircle"),
|
||||
warning: msg => send(msg, "warning", "Alert"),
|
||||
error: msg => send(msg, "error", "Alert"),
|
||||
blockNotifications,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function id() {
|
||||
return "_" + Math.random().toString(36).substr(2, 9)
|
||||
}
|
||||
|
||||
export const notificationStore = createNotificationStore()
|
||||
|
|
|
@ -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()
|
|
@ -9,6 +9,7 @@ const createRouteStore = () => {
|
|||
activeRoute: null,
|
||||
routeSessionId: Math.random(),
|
||||
routerLoaded: false,
|
||||
queryParams: {},
|
||||
}
|
||||
const store = writable(initialState)
|
||||
|
||||
|
@ -41,6 +42,17 @@ const createRouteStore = () => {
|
|||
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 => {
|
||||
store.update(state => {
|
||||
state.activeRoute = state.routes.find(x => x.path === route)
|
||||
|
@ -58,6 +70,7 @@ const createRouteStore = () => {
|
|||
fetchRoutes,
|
||||
navigate,
|
||||
setRouteParams,
|
||||
setQueryParams,
|
||||
setActiveRoute,
|
||||
setRouterLoaded,
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
builderStore,
|
||||
confirmationStore,
|
||||
authStore,
|
||||
peekStore,
|
||||
} from "../store"
|
||||
import { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api"
|
||||
import { ActionTypes } from "../constants"
|
||||
|
@ -39,13 +40,17 @@ const triggerAutomationHandler = async action => {
|
|||
}
|
||||
|
||||
const navigationHandler = action => {
|
||||
const { url } = action.parameters
|
||||
const { url, peek } = action.parameters
|
||||
if (url) {
|
||||
const external = !url.startsWith("/")
|
||||
if (external) {
|
||||
window.location.href = url
|
||||
if (peek) {
|
||||
peekStore.actions.showPeek(url)
|
||||
} 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 = {
|
||||
["Save Row"]: saveRowHandler,
|
||||
["Delete Row"]: deleteRowHandler,
|
||||
|
@ -104,6 +115,7 @@ const handlerMap = {
|
|||
["Refresh Datasource"]: refreshDatasourceHandler,
|
||||
["Log Out"]: logoutHandler,
|
||||
["Clear Form"]: clearFormHandler,
|
||||
["Close Screen Modal"]: closeScreenModalHandler,
|
||||
}
|
||||
|
||||
const confirmTextMap = {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/server",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "0.9.87-alpha.7",
|
||||
"version": "0.9.96",
|
||||
"description": "Budibase Web Server",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
|
@ -60,9 +60,9 @@
|
|||
"author": "Budibase",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@budibase/auth": "^0.9.87-alpha.7",
|
||||
"@budibase/client": "^0.9.87-alpha.7",
|
||||
"@budibase/string-templates": "^0.9.87-alpha.7",
|
||||
"@budibase/auth": "^0.9.96",
|
||||
"@budibase/client": "^0.9.96",
|
||||
"@budibase/string-templates": "^0.9.96",
|
||||
"@elastic/elasticsearch": "7.10.0",
|
||||
"@koa/router": "8.0.0",
|
||||
"@sendgrid/mail": "7.1.1",
|
||||
|
@ -115,7 +115,7 @@
|
|||
"devDependencies": {
|
||||
"@babel/core": "^7.14.3",
|
||||
"@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",
|
||||
"@types/bull": "^3.15.1",
|
||||
"@types/jest": "^26.0.23",
|
||||
|
|
|
@ -165,6 +165,10 @@ module External {
|
|||
if (!row[key] || newRow[key] || field.autocolumn) {
|
||||
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 (field.type !== FieldTypes.LINK) {
|
||||
newRow[key] = row[key]
|
||||
|
|
|
@ -19,8 +19,6 @@ function parseBody(body: any) {
|
|||
}
|
||||
if (isIsoDateString(value)) {
|
||||
body[key] = new Date(value)
|
||||
} else if (!isNaN(parseFloat(value))) {
|
||||
body[key] = parseFloat(value)
|
||||
}
|
||||
}
|
||||
return body
|
||||
|
|
|
@ -29,13 +29,12 @@
|
|||
"keywords": [
|
||||
"svelte"
|
||||
],
|
||||
"version": "0.9.87-alpha.7",
|
||||
"version": "0.9.96",
|
||||
"license": "MIT",
|
||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc",
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^0.9.87-alpha.7",
|
||||
"@budibase/bbui": "^0.9.96",
|
||||
"@spectrum-css/card": "^3.0.3",
|
||||
"@spectrum-css/divider": "^1.0.3",
|
||||
"@spectrum-css/link": "^3.1.3",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
"@spectrum-css/typography": "^3.0.2",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import { Heading, Icon } from "@budibase/bbui"
|
||||
import { routeStore } from "../../client/src/store"
|
||||
|
||||
const { styleable, linkable, builderStore } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
@ -26,6 +27,14 @@
|
|||
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) || []
|
||||
$: typeClass = navigationClasses[navigation] || "none"
|
||||
$: widthClass = widthClasses[width] || "l"
|
||||
|
@ -51,7 +60,7 @@
|
|||
|
||||
<div class="layout layout--{typeClass}" use:styleable={$component.styles}>
|
||||
{#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-header">
|
||||
{#if validLinks?.length}
|
||||
|
@ -139,6 +148,9 @@
|
|||
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
||||
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.nav-wrapper.hidden {
|
||||
display: none;
|
||||
}
|
||||
.layout--top .nav-wrapper.sticky {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
export let underline
|
||||
export let size
|
||||
|
||||
$: external = url && !url.startsWith("/")
|
||||
$: external = url && typeof url === "string" && !url.startsWith("/")
|
||||
$: target = openInNewTab ? "_blank" : "_self"
|
||||
$: placeholder = $builderStore.inBuilder && !text
|
||||
$: componentText = $builderStore.inBuilder
|
||||
|
|
|
@ -10,12 +10,12 @@
|
|||
let fieldState
|
||||
let fieldApi
|
||||
|
||||
const { API, notifications } = getContext("sdk")
|
||||
const { API, notificationStore } = getContext("sdk")
|
||||
const formContext = getContext("form")
|
||||
const BYTES_IN_MB = 1000000
|
||||
|
||||
const handleFileTooLarge = fileSizeLimit => {
|
||||
notifications.warning(
|
||||
notificationStore.actions.warning(
|
||||
`Files cannot exceed ${
|
||||
fileSizeLimit / BYTES_IN_MB
|
||||
} MB. Please try again with smaller files.`
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/string-templates",
|
||||
"version": "0.9.87-alpha.7",
|
||||
"version": "0.9.96",
|
||||
"description": "Handlebars wrapper for Budibase templating.",
|
||||
"main": "src/index.cjs",
|
||||
"module": "dist/bundle.mjs",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/worker",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "0.9.87-alpha.7",
|
||||
"version": "0.9.96",
|
||||
"description": "Budibase background service",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
|
@ -21,8 +21,8 @@
|
|||
"author": "Budibase",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@budibase/auth": "^0.9.87-alpha.7",
|
||||
"@budibase/string-templates": "^0.9.87-alpha.7",
|
||||
"@budibase/auth": "^0.9.96",
|
||||
"@budibase/string-templates": "^0.9.96",
|
||||
"@koa/router": "^8.0.0",
|
||||
"@techpass/passport-openidconnect": "^0.3.0",
|
||||
"aws-sdk": "^2.811.0",
|
||||
|
|
Loading…
Reference in New Issue