Merge pull request #1243 from Budibase/develop

Develop
This commit is contained in:
Martin McKeaveney 2021-03-03 16:18:23 +00:00 committed by GitHub
commit 72199f319e
62 changed files with 599 additions and 584 deletions

View File

@ -79,6 +79,7 @@
"posthog-js": "1.4.5",
"remixicon": "2.5.0",
"shortid": "2.2.15",
"svelte-dnd-action": "^0.8.9",
"svelte-loading-spinners": "^0.1.1",
"svelte-portal": "0.1.0",
"uuid": "8.3.1",

View File

@ -120,7 +120,7 @@ const getContextBindings = (asset, componentId) => {
tableName = info.table?.name
// Add _id and _rev fields for certain types
if (datasource.type === "table" || datasource.type === "link") {
if (schema && ["table", "link"].includes(datasource.type)) {
schema["_id"] = { type: "string" }
schema["_rev"] = { type: "string" }
}

View File

@ -285,6 +285,7 @@ export const getFrontendStore = () => {
_id: uuid(),
_component: definition.component,
_styles: { normal: {}, hover: {}, active: {} },
_transition: "",
_instanceName: `New ${definition.name}`,
...cloneDeep(props),
...extras,
@ -487,6 +488,15 @@ export const getFrontendStore = () => {
selected._styles = { normal: {}, hover: {}, active: {} }
await store.actions.preview.saveSelected()
},
updateTransition: async transition => {
const selected = get(selectedComponent)
if (transition == null || transition === "") {
selected._transition = ""
} else {
selected._transition = transition
}
await store.actions.preview.saveSelected()
},
updateProp: async (name, value) => {
let component = get(selectedComponent)
if (!name || !component) {

View File

@ -14,6 +14,7 @@ export class Component extends BaseStructure {
active: {},
selected: {},
},
_transition: "",
_instanceName: "",
_children: [],
}
@ -39,6 +40,11 @@ export class Component extends BaseStructure {
return this
}
transition(transition) {
this._json._transition = transition
return this
}
// Shorthand for custom props "type"
type(type) {
this._json.type = type

View File

@ -2,10 +2,6 @@
import AutomationList from "./AutomationList.svelte"
import CreateAutomationModal from "./CreateAutomationModal.svelte"
import { Modal } from "@budibase/bbui"
import { automationStore, backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"
let selectedTab = "AUTOMATIONS"
let modal
</script>
@ -40,12 +36,4 @@
cursor: pointer;
color: var(--blue);
}
span:not(.selected) {
color: var(--grey-5);
}
span:not(.selected):hover {
color: var(--ink);
}
</style>

View File

@ -8,13 +8,6 @@
$: valid = !!name
$: instanceId = $backendUiStore.selectedDatabase._id
$: appId = $store.appId
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
async function createAutomation() {
await automationStore.actions.create({

View File

@ -60,29 +60,4 @@
div.icon i {
font-size: 16px;
}
ul {
list-style: none;
margin: 0;
padding: var(--spacing-s) 0;
}
li {
display: flex;
font-family: var(--font-sans);
font-size: var(--font-size-xs);
color: var(--ink);
padding: var(--spacing-s) var(--spacing-m);
margin: auto 0;
align-items: center;
cursor: pointer;
}
li:hover {
background-color: var(--grey-2);
}
li:active {
color: var(--blue);
}
</style>

View File

@ -0,0 +1,190 @@
<script>
import groupBy from "lodash/fp/groupBy"
import { Input, TextArea, Heading, Spacer, Label } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { isValid } from "@budibase/string-templates"
import { handlebarsCompletions } from "constants/completions"
const dispatch = createEventDispatcher()
export let value = ""
export let bindingDrawer
export let bindableProperties = []
let originalValue = value
let helpers = handlebarsCompletions()
let getCaretPosition
let search = ""
let validity = true
$: categories = Object.entries(groupBy("category", bindableProperties))
$: value && checkValid()
$: dispatch("update", value)
$: searchRgx = new RegExp(search, "ig")
function checkValid() {
validity = isValid(value)
}
function addToText(binding) {
const position = getCaretPosition()
const toAdd = `{{ ${binding.path} }}`
if (position.start) {
value =
value.substring(0, position.start) +
toAdd +
value.substring(position.end, value.length)
} else {
value += toAdd
}
}
export function cancel() {
dispatch("update", originalValue)
bindingDrawer.close()
}
</script>
<div class="container">
<div class="list">
<Heading small>Available bindings</Heading>
<Spacer medium />
<Input extraThin placeholder="Search" bind:value={search} />
<Spacer medium />
{#each categories as [categoryName, bindings]}
<Heading extraSmall>{categoryName}</Heading>
<Spacer extraSmall />
{#each bindableProperties.filter(binding =>
binding.label.match(searchRgx)
) as binding}
<div class="binding" on:click={() => addToText(binding)}>
<span class="binding__label">{binding.label}</span>
<span class="binding__type">{binding.type}</span>
<br />
<div class="binding__description">
{binding.description || ''}
</div>
</div>
{/each}
{/each}
<Heading extraSmall>Helpers</Heading>
<Spacer extraSmall />
{#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper}
<div class="binding" on:click={() => addToText(helper)}>
<span class="binding__label">{helper.label}</span>
<br />
<div class="binding__description">
{@html helper.description || ''}
</div>
<pre>{helper.example || ''}</pre>
</div>
{/each}
</div>
<div class="text">
<TextArea
bind:getCaretPosition
thin
bind:value
placeholder="Add text, or click the objects on the left to add them to the textbox." />
{#if !validity}
<p class="syntax-error">
Current Handlebars syntax is invalid, please check the guide
<a href="https://handlebarsjs.com/guide/">here</a>
for more details.
</p>
{/if}
</div>
</div>
<style>
.container {
height: 40vh;
overflow-y: auto;
display: grid;
grid-template-columns: 280px 1fr;
}
.list {
border-right: var(--border-light);
padding: var(--spacing-l);
overflow: auto;
}
.list {
border-right: var(--border-light);
padding: var(--spacing-l);
overflow: auto;
}
.list::-webkit-scrollbar {
display: none;
}
.text {
padding: var(--spacing-l);
font-family: var(--font-sans);
}
.text :global(textarea) {
min-height: 100px;
}
.text :global(p) {
margin: 0;
}
.binding {
font-size: 12px;
padding: var(--spacing-s);
border-radius: var(--border-radius-m);
}
.binding:hover {
background-color: var(--grey-2);
cursor: pointer;
}
.binding__label {
font-weight: 500;
text-transform: capitalize;
}
.binding__description {
color: var(--grey-8);
margin-top: 2px;
white-space: normal;
}
pre {
white-space: normal;
}
.binding__type {
font-family: monospace;
background-color: var(--grey-2);
border-radius: var(--border-radius-m);
padding: 2px;
margin-left: 2px;
font-weight: 500;
}
.editor {
padding-left: var(--spacing-l);
}
.editor :global(textarea) {
min-height: 60px;
}
.controls {
display: grid;
grid-template-columns: 1fr auto;
grid-gap: var(--spacing-l);
align-items: center;
margin-top: var(--spacing-m);
}
.syntax-error {
color: var(--red);
font-size: 12px;
}
.syntax-error a {
color: var(--red);
text-decoration: underline;
}
</style>

View File

@ -5,7 +5,8 @@
import { Button, Input, Select, Label } from "@budibase/bbui"
import { automationStore } from "builderStore"
import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
import BindableInput from "../../common/BindableInput.svelte"
import DrawerBindableInput from "../../common/DrawerBindableInput.svelte"
import AutomationBindingPanel from './AutomationBindingPanel.svelte'
export let block
export let webhookModal
@ -64,10 +65,12 @@
{:else if value.customType === 'password'}
<Input type="password" extraThin bind:value={block.inputs[key]} />
{:else if value.customType === 'email'}
<BindableInput
<DrawerBindableInput
panel={AutomationBindingPanel}
type={'email'}
extraThin
bind:value={block.inputs[key]}
value={block.inputs[key]}
on:change={e => block.inputs[key] = e.detail}
{bindings} />
{:else if value.customType === 'table'}
<TableSelector bind:value={block.inputs[key]} />
@ -78,10 +81,12 @@
{:else if value.customType === 'triggerSchema'}
<SchemaSetup bind:value={block.inputs[key]} />
{:else if value.type === 'string' || value.type === 'number'}
<BindableInput
<DrawerBindableInput
panel={AutomationBindingPanel}
type={value.customType}
extraThin
bind:value={block.inputs[key]}
value={block.inputs[key]}
on:change={e => block.inputs[key] = e.detail}
{bindings} />
{/if}
</div>
@ -102,11 +107,4 @@
font-size: var(--font-size-xs);
color: var(--grey-7);
}
textarea {
min-height: 150px;
font-family: inherit;
padding: 12px;
margin-top: 8px;
}
</style>

View File

@ -1,219 +0,0 @@
<script>
import groupBy from "lodash/fp/groupBy"
import {
TextArea,
Input,
Heading,
Body,
Spacer,
Button,
Popover,
} from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { isValid } from "@budibase/string-templates"
import { handlebarsCompletions } from "constants/completions"
import { readableToRuntimeBinding } from "builderStore/dataBinding"
const dispatch = createEventDispatcher()
export let value = ""
export let bindings = []
export let anchor
export let align
export let popover = null
let helpers = handlebarsCompletions()
let getCaretPosition
let validity = true
let search = ""
$: categories = Object.entries(groupBy("category", bindings))
$: value && checkValid()
$: searchRgx = new RegExp(search, "ig")
function onClickBinding(binding) {
const position = getCaretPosition()
const toAdd = `{{ ${binding.path} }}`
if (position.start) {
value =
value.substring(0, position.start) +
toAdd +
value.substring(position.end, value.length)
} else {
value += toAdd
}
}
function checkValid() {
const runtimeValue = readableToRuntimeBinding(bindings, value)
validity = isValid(runtimeValue)
}
</script>
<Popover {anchor} {align} bind:this={popover}>
<div class="container">
<div class="bindings">
<Heading small>Available bindings</Heading>
<Spacer medium />
<Input extraThin placeholder="Search" bind:value={search} />
<Spacer medium />
<div class="bindings__wrapper">
<div class="bindings__list">
{#each categories as [categoryName, bindings]}
<Heading extraSmall>{categoryName}</Heading>
<Spacer extraSmall />
{#each bindings.filter(binding =>
binding.label.match(searchRgx)
) as binding}
<div class="binding" on:click={() => onClickBinding(binding)}>
<span class="binding__label">{binding.label}</span>
<span class="binding__type">{binding.type}</span>
<br />
<div class="binding__description">
{binding.description || ''}
</div>
</div>
{/each}
{/each}
<Heading extraSmall>Helpers</Heading>
<Spacer extraSmall />
{#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper}
<div class="binding" on:click={() => onClickBinding(helper)}>
<span class="binding__label">{helper.label}</span>
<br />
<div class="binding__description">
{@html helper.description || ''}
</div>
<pre>{helper.example || ''}</pre>
</div>
{/each}
</div>
</div>
</div>
<div class="editor">
<Heading small>Data binding</Heading>
<Body small lh black>
Binding connects one piece of data to another and makes it dynamic.
Click the objects on the left to add them to the textbox.
</Body>
<TextArea
thin
bind:getCaretPosition
bind:value
placeholder="Add options from the left, type text, or do both" />
{#if !validity}
<p class="syntax-error">
Current Handlebars syntax is invalid, please check the guide
<a href="https://handlebarsjs.com/guide/">here</a>
for more details.
</p>
{/if}
<div class="controls">
<a href="https://docs.budibase.com/design/binding">
<Body small>Learn more about binding</Body>
</a>
<Button on:click={popover.hide} disabled={!validity} primary>
Done
</Button>
</div>
</div>
</div>
</Popover>
<style>
.container {
display: grid;
grid-template-columns: 280px 1fr;
width: 800px;
}
.bindings {
border-right: 1px solid var(--grey-4);
flex: 0 0 240px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
padding-right: var(--spacing-l);
}
.bindings :global(label) {
margin: var(--spacing-m) 0;
}
.bindings :global(label:first-child) {
margin-top: 0;
}
.bindings__wrapper {
overflow-y: scroll;
overflow-x: hidden;
position: relative;
flex: 1 1 auto;
-ms-overflow-style: none;
}
.bindings__wrapper::-webkit-scrollbar {
width: 0;
height: 0;
}
.bindings__list {
position: absolute;
width: 100%;
}
.binding {
font-size: 12px;
padding: var(--spacing-s);
border-radius: var(--border-radius-m);
}
.binding:hover {
background-color: var(--grey-2);
cursor: pointer;
}
.binding__label {
font-weight: 500;
text-transform: capitalize;
}
.binding__description {
color: var(--grey-8);
margin-top: 2px;
white-space: normal;
}
pre {
white-space: normal;
}
.binding__type {
font-family: monospace;
background-color: var(--grey-2);
border-radius: var(--border-radius-m);
padding: 2px;
margin-left: 2px;
font-weight: 500;
}
.editor {
padding-left: var(--spacing-l);
}
.editor :global(textarea) {
min-height: 60px;
}
.controls {
display: grid;
grid-template-columns: 1fr auto;
grid-gap: var(--spacing-l);
align-items: center;
margin-top: var(--spacing-m);
}
.syntax-error {
color: var(--red);
font-size: 12px;
}
.syntax-error a {
color: var(--red);
text-decoration: underline;
}
</style>

View File

@ -1,7 +1,8 @@
<script>
import { backendUiStore } from "builderStore"
import { Select } from "@budibase/bbui"
import BindableInput from "../../common/BindableInput.svelte"
import DrawerBindableInput from "../../common/DrawerBindableInput.svelte"
import AutomationBindingPanel from './AutomationBindingPanel.svelte'
export let value
export let bindings
@ -39,9 +40,11 @@
{/each}
</Select>
{:else if schema.type === 'string' || schema.type === 'number'}
<BindableInput
<DrawerBindableInput
panel={AutomationBindingPanel}
extraThin
bind:value={value[field]}
value={value[field]}
on:change={e => value[field] = e.detail}
label={field}
type="string"
{bindings} />

View File

@ -92,9 +92,6 @@
text-align: center;
color: var(--blue);
}
h5 {
margin: 0;
}
code {
padding: 1px 4px 1px 4px;
font-size: 14px;

View File

@ -246,10 +246,6 @@
color: var(--ink);
}
tbody tr:hover {
background: var(--grey-1);
}
:global(.ag-filter) {
background: var(--background);
padding: var(--spacing-s);

View File

@ -28,10 +28,6 @@
title="Confirm Deletion" />
<style>
.ri-delete-bin-line:hover {
cursor: pointer;
}
div {
display: flex;
justify-content: center;

View File

@ -1,5 +1,5 @@
<script>
import { Button, Input, Select } from "@budibase/bbui"
import { Button, Select } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import analytics from "analytics"

View File

@ -1,5 +1,5 @@
<script>
import { Button, Input, Select } from "@budibase/bbui"
import { Button, Input } from "@budibase/bbui"
import { goto } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"

View File

@ -1,5 +1,4 @@
<script>
import api from "builderStore/api"
import { Button, Select } from "@budibase/bbui"
import download from "downloadjs"

View File

@ -1,5 +1,5 @@
<script>
import { Button, Input, Select } from "@budibase/bbui"
import { Button, Select } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import { FIELDS } from "constants/backend"

View File

@ -1,8 +1,5 @@
<script>
import { onMount } from "svelte"
import { backendUiStore } from "builderStore"
import { Roles } from "constants/backend"
import api from "builderStore/api"
import { notifier } from "builderStore/store/notifications"
import { Button, Label, Input, Select, Spacer } from "@budibase/bbui"

View File

@ -2,17 +2,11 @@
import { onMount } from "svelte"
import { goto } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { TableNames } from "constants"
import CreateDatasourceModal from "./modals/CreateDatasourceModal.svelte"
import EditDatasourcePopover from "./popovers/EditDatasourcePopover.svelte"
import EditQueryPopover from "./popovers/EditQueryPopover.svelte"
import { Modal, Switcher } from "@budibase/bbui"
import NavItem from "components/common/NavItem.svelte"
import ICONS from "./icons"
$: selectedView =
$backendUiStore.selectedView && $backendUiStore.selectedView.name
function selectDatasource(datasource) {
backendUiStore.actions.datasources.select(datasource._id)
$goto(`./datasource/${datasource._id}`)

View File

@ -1,9 +1,6 @@
<script>
import { onMount } from "svelte"
import { backendUiStore } from "builderStore"
import api from "builderStore/api"
import { Input, Label, TextArea, Spacer } from "@budibase/bbui"
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import ICONS from "../icons"
export let integration = {}

View File

@ -6,13 +6,10 @@
import TableIntegrationMenu from "../TableIntegrationMenu/index.svelte"
import analytics from "analytics"
let modal
let error = ""
let name
let source
let integration
let datasource
function checkValid(evt) {
const datasourceName = evt.target.value

View File

@ -16,8 +16,6 @@
let templateScreens
let willBeDeleted
$: fields = Object.keys(table.schema)
function showEditor() {
editing = true
}

View File

@ -1,63 +0,0 @@
<script>
import { createEventDispatcher } from "svelte"
import GenericBindingPopover from "../automation/SetupPanel/GenericBindingPopover.svelte"
import { Input, Icon } from "@budibase/bbui"
const dispatch = createEventDispatcher()
export let bindings = []
export let value
let anchor
let popover = undefined
let enrichedValue
let inputProps
// Extract all other props to pass to input component
$: {
let { bindings, ...otherProps } = $$props
inputProps = otherProps
}
$: value && dispatch("change", value)
</script>
<div class="container" bind:this={anchor}>
<Input {...inputProps} bind:value />
<div class="icon" on:click={popover.show}>
<Icon name="lightning" />
</div>
</div>
<GenericBindingPopover
{anchor}
{bindings}
bind:value
bind:popover
align="right" />
<style>
.container {
position: relative;
}
.icon {
right: 2px;
top: 5px;
bottom: 2px;
position: absolute;
align-items: center;
display: flex;
box-sizing: border-box;
padding-left: var(--spacing-xs);
border-left: 1px solid var(--grey-4);
background-color: var(--grey-2);
border-top-right-radius: var(--border-radius-m);
border-bottom-right-radius: var(--border-radius-m);
color: var(--grey-7);
font-size: 16px;
margin-top: 20px;
}
.icon:hover {
color: var(--ink);
cursor: pointer;
}
</style>

View File

@ -8,6 +8,7 @@
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
export let panel = BindingPanel
export let value = ""
export let bindings = []
export let thin = true
@ -49,7 +50,8 @@
<Button thin blue on:click={handleClose}>Save</Button>
</heading>
<div slot="body">
<BindingPanel
<svelte:component
this={panel}
value={readableValue}
close={handleClose}
on:update={event => (tempValue = event.detail)}

View File

@ -12,6 +12,7 @@
"fieldgroup",
"stringfield",
"numberfield",
"passwordfield",
"optionsfield",
"booleanfield",
"longformfield",
@ -49,6 +50,7 @@
"heading",
"text",
"image",
"backgroundimage",
"link",
"icon",
"embed"

View File

@ -2,7 +2,6 @@
import { get } from "svelte/store"
import { store, currentAsset } from "builderStore"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { last } from "lodash/fp"
import { findComponentParent } from "builderStore/storeUtils"
import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
@ -17,16 +16,10 @@
$: noChildrenAllowed = !component || !definition?.hasChildren
$: noPaste = !$store.componentToPaste
const lastPartOfName = c => (c ? last(c._component.split("/")) : "")
const hideDropdown = () => {
dropdown.hide()
}
const selectComponent = component => {
store.actions.components.select(component)
}
const moveUpComponent = () => {
const asset = get(currentAsset)
const parent = findComponentParent(asset.props, component._id)

View File

@ -1,11 +1,8 @@
<script>
import { goto } from "@sveltech/routify"
import { store, backendUiStore, allScreens } from "builderStore"
import { Input, Select, ModalContent, Toggle } from "@budibase/bbui"
import getTemplates from "builderStore/store/screenTemplates"
import analytics from "analytics"
import { onMount } from "svelte"
import api from "builderStore/api"
const CONTAINER = "@budibase/standard-components/container"
@ -19,9 +16,6 @@
$: templates = getTemplates($store, $backendUiStore.tables)
$: route = !route && $allScreens.length === 0 ? "*" : route
$: baseComponents = Object.values($store.components)
.filter(componentDefinition => componentDefinition.baseComponent)
.map(c => c._component)
$: {
if (templates && templateIndex === undefined) {
templateIndex = 0
@ -31,7 +25,6 @@
const templateChanged = newTemplateIndex => {
if (newTemplateIndex === undefined) return
const template = templates[newTemplateIndex]
draftScreen = templates[newTemplateIndex].create()
if (draftScreen.props._instanceName) {
name = draftScreen.props._instanceName
@ -60,6 +53,7 @@
if (routeError) return false
draftScreen.props._instanceName = name
draftScreen.props._transition = "fade"
draftScreen.props._component = baseComponent
draftScreen.routing = { route, roleId }

View File

@ -1,5 +1,5 @@
<script>
import { TextArea, DetailSummary, Button } from "@budibase/bbui"
import { TextArea, DetailSummary, Button, Select } from "@budibase/bbui"
import PropertyGroup from "./PropertyControls/PropertyGroup.svelte"
import FlatButtonGroup from "./PropertyControls/FlatButtonGroup"
import { allStyles } from "./componentStyles"
@ -8,6 +8,7 @@
export let componentInstance = {}
export let onStyleChanged = () => {}
export let onCustomStyleChanged = () => {}
export let onUpdateTransition = () => {}
export let onResetStyles = () => {}
let selectedCategory = "normal"
@ -23,16 +24,22 @@
{ value: "active", text: "Active" },
]
const transitions = [
'none', 'fade', 'blur', 'fly', 'scale' // slide is hidden because it does not seem to result in any effect
]
const capitalize = ([first,...rest]) => first.toUpperCase() + rest.join('');
$: groups = componentDefinition?.styleable ? Object.keys(allStyles) : []
</script>
<div class="design-view-container">
<div class="design-view-state-categories">
<div class="container">
<div class="state-categories">
<FlatButtonGroup value={selectedCategory} {buttonProps} {onChange} />
</div>
<div class="positioned-wrapper">
<div class="design-view-property-groups">
<div class="property-groups">
{#if groups.length > 0}
{#each groups as groupName}
<PropertyGroup
@ -64,10 +71,19 @@
{/if}
</div>
</div>
{#if componentDefinition?.transitionable}
<div class="transitions">
<Select value={componentInstance._transition} on:change={event => onUpdateTransition(event.target.value)} name="transition" label="Transition" secondary thin>
{#each transitions as transition}
<option value={transition}>{capitalize(transition)}</option>
{/each}
</Select>
</div>
{/if}
</div>
<style>
.design-view-container {
.container {
display: flex;
flex-direction: column;
width: 100%;
@ -81,7 +97,7 @@
min-height: 0;
}
.design-view-property-groups {
.property-groups {
flex: 1;
overflow-y: auto;
min-height: 0;
@ -104,4 +120,8 @@
min-height: 120px;
font-size: var(--font-size-xs);
}
option {
text-transform: capitalize;
}
</style>

View File

@ -24,29 +24,9 @@
const onStyleChanged = store.actions.components.updateStyle
const onCustomStyleChanged = store.actions.components.updateCustomStyle
const onUpdateTransition = store.actions.components.updateTransition
const onResetStyles = store.actions.components.resetStyles
function walkProps(component, action) {
action(component)
if (component.children) {
for (let child of component.children) {
walkProps(child, action)
}
}
}
function flattenComponents(props) {
const components = []
props.forEach(comp =>
walkProps(comp, c => {
if ("_component" in c) {
components.push(c)
}
})
)
return components
}
function setAssetProps(name, value) {
const selectedAsset = get(currentAsset)
store.update(state => {
@ -62,10 +42,6 @@
})
store.actions.preview.saveSelected()
}
function getProps(obj, keys) {
return keys.map((key, i) => [key, obj[key], obj.props._id + i])
}
</script>
<CategoryTab
@ -84,6 +60,7 @@
componentDefinition={definition}
{onStyleChanged}
{onCustomStyleChanged}
{onUpdateTransition}
{onResetStyles} />
{:else if selectedCategory.value === 'settings'}
<SettingsView

View File

@ -1,11 +1,22 @@
<script>
import {flip} from "svelte/animate";
import {dndzone} from "svelte-dnd-action";
import { Button, DropdownMenu, Spacer } from "@budibase/bbui"
import actionTypes from "./actions"
const flipDurationMs = 150;
const EVENT_TYPE_KEY = "##eventHandlerType"
export let actions
// dndzone needs an id on the array items, so this adds some temporary ones.
if (actions) {
actions = actions.map((action, i) => {
return {...action, id: i}
})
}
let addActionButton
let addActionDropdown
let selectedAction = actions?.length ? actions[0] : null
@ -30,19 +41,26 @@
const newAction = {
parameters: {},
[EVENT_TYPE_KEY]: actionType.name,
id: actions ? actions.length + 1 : 0
}
if (!actions) {
actions = []
}
actions.push(newAction)
actions = [...actions, newAction]
selectedAction = newAction
actions = actions
addActionDropdown.hide()
}
const selectAction = action => () => {
selectedAction = action
}
function handleDndConsider(e) {
actions = e.detail.items;
}
function handleDndFinalize(e) {
actions = e.detail.items;
}
</script>
<div class="actions-container">
@ -69,21 +87,23 @@
</div>
{#if actions && actions.length > 0}
{#each actions as action, index}
<div class="action-container">
<div
class="action-header"
class:selected={action === selectedAction}
on:click={selectAction(action)}>
{index + 1}.
{action[EVENT_TYPE_KEY]}
<div class="action-dnd-container" use:dndzone={{items: actions, flipDurationMs, dropTargetStyle: { outline: 'none'}}} on:consider={handleDndConsider} on:finalize={handleDndFinalize}>
{#each actions as action, index (action.id)}
<div class="action-container" animate:flip={{duration: flipDurationMs}}>
<div
class="action-header"
class:selected={action === selectedAction}
on:click={selectAction(action)}>
{index + 1}.
{action[EVENT_TYPE_KEY]}
</div>
<i
class="ri-close-fill"
style="margin-left: auto;"
on:click={() => deleteAction(index)} />
</div>
<i
class="ri-close-fill"
style="margin-left: auto;"
on:click={() => deleteAction(index)} />
</div>
{/each}
{/each}
</div>
{/if}
</div>
<div class="action-config">
@ -156,17 +176,6 @@
padding: var(--spacing-l);
}
a {
flex: 1;
color: var(--grey-5);
font-size: var(--font-size-s);
text-decoration: none;
}
a:hover {
color: var(--blue);
}
i:hover {
color: var(--red);
cursor: pointer;

View File

@ -0,0 +1,36 @@
<script>
import { Label } from "@budibase/bbui"
import { getBindableProperties } from "builderStore/dataBinding"
import { currentAsset, store } from "builderStore"
import DrawerBindableInput from "components/common/DrawerBindableInput.svelte"
export let parameters
let bindingDrawer
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
</script>
<div class="root">
<Label small>Email</Label>
<DrawerBindableInput
title="Email"
value={parameters.email}
on:change={value => (parameters.email = value.detail)}
{bindings} />
<Label small>Password</Label>
<DrawerBindableInput
title="Password"
value={parameters.password}
on:change={value => (parameters.password = value.detail)}
{bindings} />
</div>
<style>
.root {
display: grid;
column-gap: var(--spacing-l);
row-gap: var(--spacing-s);
grid-template-columns: auto 1fr auto 1fr;
align-items: baseline;
}
</style>

View File

@ -0,0 +1,7 @@
<div class="root">This action doesn't require any additional settings.</div>
<style>
.root {
font-size: var(--font-size-s);
}
</style>

View File

@ -4,6 +4,8 @@ import DeleteRow from "./DeleteRow.svelte"
import ExecuteQuery from "./ExecuteQuery.svelte"
import TriggerAutomation from "./TriggerAutomation.svelte"
import ValidateForm from "./ValidateForm.svelte"
import LogIn from "./LogIn.svelte"
import LogOut from "./LogOut.svelte"
// defines what actions are available, when adding a new one
// the component is the setup panel for the action
@ -35,4 +37,12 @@ export default [
name: "Validate Form",
component: ValidateForm,
},
{
name: "Log In",
component: LogIn,
},
{
name: "Log Out",
component: LogOut,
},
]

View File

@ -1,7 +1,6 @@
<script context="module">
import iconData from "./icons.js"
const categories = Object.keys(iconData)
const icons = Object.keys(iconData).reduce(
(acc, cat) => [...acc, ...Object.keys(iconData[cat])],
[]

View File

@ -3,8 +3,6 @@
import { backendUiStore } from "builderStore"
export let value
let roles = []
</script>
<Select bind:value extraThin secondary on:change>

View File

@ -3,7 +3,6 @@
import { Label, Spacer } from "@budibase/bbui"
import { onMount, createEventDispatcher } from "svelte"
import { themeStore } from "builderStore"
import { handlebarsCompletions } from "constants/completions"
const dispatch = createEventDispatcher()
@ -21,13 +20,6 @@
export let editorHeight = 500
// export let parameters = []
let completions = handlebarsCompletions()
// $: completions = parameters.map(param => ({
// text: `{{ ${param.name} }}`,
// displayText: param.name,
// }))
let width
let height

View File

@ -7,9 +7,6 @@
export let schema
export let editable
let draftField = {}
$: fieldKeys = Object.keys(fields)
$: schemaKeys = Object.keys(schema.fields)
function updateCustomFields({ detail }) {

View File

@ -43,12 +43,7 @@
.topnavitemright:hover i {
color: var(--ink);
}
h5 {
margin: 0;
font-weight: 500;
}
.content {
padding: var(--spacing-xl);
}

View File

@ -61,11 +61,4 @@
.card-footer {
margin-top: var(--spacing-m);
}
h3 {
font-size: var(--font-size-l);
font-weight: 600;
color: var(--ink);
text-transform: capitalize;
}
</style>

View File

@ -130,11 +130,6 @@
border-bottom: 1px solid var(--grey-2);
}
.content > div {
height: 100%;
width: 100%;
}
.toprightnav {
display: flex;
flex-direction: row;

View File

@ -1,11 +1,8 @@
<script>
import { params } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { Switcher } from "@budibase/bbui"
import QueryInterface from "components/integration/QueryViewer.svelte"
let query
async function fetchQueryConfig() {
try {
const response = await api.get(`/api/integrations/${datasource.source}`)

View File

@ -135,11 +135,6 @@
margin: 0 auto;
}
h5 {
margin: 0 !important;
font-size: var(--font-size-l);
}
.query-header {
display: flex;
flex-direction: row;

View File

@ -202,31 +202,4 @@
gap: var(--spacing-l);
padding: var(--spacing-l) var(--spacing-xl);
}
.binding-drawer-container {
height: 50vh;
position: absolute;
bottom: 0;
width: 100%;
background: var(--background);
padding: var(--spacing-xl);
}
.nav-group-header > div:nth-child(1) {
padding: 0rem 0.5rem 0rem 0rem;
vertical-align: bottom;
grid-column-start: icon;
margin-right: 5px;
}
.nav-group-header > div:nth-child(3) {
vertical-align: bottom;
grid-column-start: button;
cursor: pointer;
color: var(--blue);
}
.nav-group-header > div:nth-child(3):hover {
color: var(--blue);
}
</style>

View File

@ -8,7 +8,6 @@
import TemplateList from "components/start/TemplateList.svelte"
import analytics from "analytics"
let promise = getApps()
let hasKey
let template
let modal

View File

@ -6686,6 +6686,11 @@ supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0:
dependencies:
has-flag "^4.0.0"
svelte-dnd-action@^0.8.9:
version "0.8.9"
resolved "https://registry.yarnpkg.com/svelte-dnd-action/-/svelte-dnd-action-0.8.9.tgz#90fa211057a5d9e832d5521511f9c1b87242d2ae"
integrity sha512-4WaLRvERr2JoX6Lh9GnlEZRzFF5eY3h2q0n5+lS4/+nqqQjTIpCwTrncMf9tJNa+M4AGy43I5aq5F+Ohl/nxLg==
svelte-flatpickr@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-2.4.0.tgz#190871fc3305956c8c8fd3601cd036b8ac71ef49"

View File

@ -11,6 +11,7 @@
screenStore,
authStore,
} from "../store"
import { TableNames, ActionTypes } from "../constants"
// Provide contexts
setContext("sdk", SDK)
@ -25,10 +26,20 @@
await authStore.actions.fetchUser()
loaded = true
})
// Register this as a refreshable datasource so that user changes cause
// the user object to be refreshed
$: actions = [
{
type: ActionTypes.RefreshDatasource,
callback: () => authStore.actions.fetchUser(),
metadata: { datasource: { type: "table", tableId: TableNames.USERS } },
},
]
</script>
{#if loaded && $screenStore.activeLayout}
<Provider key="user" data={$authStore}>
<Provider key="user" data={$authStore} {actions}>
<Component definition={$screenStore.activeLayout.props} />
<NotificationDisplay />
</Provider>

View File

@ -34,12 +34,14 @@
$: id = definition._id
$: updateComponentProps(definition, $context)
$: styles = definition._styles
$: transition = definition._transition
// Update component context
$: componentStore.set({
id,
children: children.length,
styles: { ...styles, id },
transition
})
// Gets the component constructor for the specified component

View File

@ -18,19 +18,6 @@
<!-- Ensure to fully remount when screen changes -->
{#key screenDefinition?._id}
<Provider key="url" data={params}>
<div in:fade>
<Component definition={screenDefinition} />
</div>
<Component definition={screenDefinition} />
</Provider>
{/key}
<style>
div {
flex: 1 1 auto;
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
</style>

View File

@ -7,6 +7,7 @@ import {
builderStore,
} from "./store"
import { styleable } from "./utils/styleable"
import transition from "./utils/transition"
import { linkable } from "./utils/linkable"
import Provider from "./components/Provider.svelte"
import { ActionTypes } from "./constants"
@ -19,6 +20,7 @@ export default {
screenStore,
builderStore,
styleable,
transition,
linkable,
Provider,
ActionTypes,

View File

@ -21,7 +21,7 @@ const createAuthStore = () => {
const logIn = async ({ email, password }) => {
const user = await API.logIn({ email, password })
if (!user.error) {
store.set(user)
await fetchUser()
await initialise()
goToDefaultRoute()
}

View File

@ -1,42 +1,33 @@
import { writable, derived } from "svelte/store"
import { generate } from "shortid"
import { writable } from "svelte/store"
const NOTIFICATION_TIMEOUT = 3000
const createNotificationStore = () => {
const _notifications = writable([])
let block = false
const send = (message, type = "default") => {
if (block) {
return
}
_notifications.update(state => {
return [...state, { id: generate(), type, message }]
})
}
const blockNotifications = (timeout = 1000) => {
block = true
setTimeout(() => (block = false), timeout)
}
const notifications = derived(_notifications, ($_notifications, set) => {
set($_notifications)
if ($_notifications.length > 0) {
const timeout = setTimeout(() => {
_notifications.update(state => {
state.shift()
return state
})
set($_notifications)
}, NOTIFICATION_TIMEOUT)
return () => {
clearTimeout(timeout)
}
const timeoutIds = new Set()
const _notifications = writable([], () => {
return () => {
// clear all the timers
timeoutIds.forEach(timeoutId => {
clearTimeout(timeoutId)
})
_notifications.set([])
}
})
const { subscribe } = notifications
const send = (message, type = "default") => {
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
return {
subscribe,
@ -45,8 +36,16 @@ const createNotificationStore = () => {
warning: msg => send(msg, "warning"),
info: msg => send(msg, "info"),
success: msg => send(msg, "success"),
blockNotifications,
}
}
function id() {
return (
"_" +
Math.random()
.toString(36)
.substr(2, 9)
)
}
export const notificationStore = createNotificationStore()

View File

@ -1,5 +1,5 @@
import { get } from "svelte/store"
import { routeStore, builderStore } from "../store"
import { routeStore, builderStore, authStore } from "../store"
import { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api"
import { ActionTypes } from "../constants"
@ -68,6 +68,15 @@ const refreshDatasourceHandler = async (action, context) => {
)
}
const loginHandler = async action => {
const { email, password } = action.parameters
await authStore.actions.logIn({ email, password })
}
const logoutHandler = async () => {
await authStore.actions.logOut()
}
const handlerMap = {
["Save Row"]: saveRowHandler,
["Delete Row"]: deleteRowHandler,
@ -76,6 +85,8 @@ const handlerMap = {
["Trigger Automation"]: triggerAutomationHandler,
["Validate Form"]: validateFormHandler,
["Refresh Datasource"]: refreshDatasourceHandler,
["Log In"]: loginHandler,
["Log Out"]: logoutHandler,
}
/**

View File

@ -0,0 +1,16 @@
import { fade, blur, scale, fly } from "svelte/transition"
// Default options
const transitions = new Map([
["fade", { tn: fade, opt: {} }],
["blur", { tn: blur, opt: {} }],
// This one seems to not result in any effect
// ["slide", { tn: slide, opt: {} }],
["scale", { tn: scale, opt: {} }],
["fly", { tn: fly, opt: { y: 80 } }],
])
export default function transition(node, { type, options = {} }) {
const { tn, opt } = transitions.get(type) || { tn: () => {}, opt: {} }
return tn(node, { ...opt, ...options })
}

View File

@ -21,6 +21,7 @@ exports.createHomeScreen = () => ({
active: {},
selected: {},
},
_transition: "fade",
type: "div",
_children: [
{
@ -69,6 +70,7 @@ exports.createLoginScreen = app => ({
active: {},
selected: {},
},
_transition: "fade",
type: "div",
_children: [
{

View File

@ -5,6 +5,7 @@
"icon": "ri-layout-column-line",
"hasChildren": true,
"styleable": true,
"transitionable": true,
"settings": []
},
"datagrid": {
@ -272,6 +273,63 @@
}
]
},
"backgroundimage": {
"name": "Background Image",
"description": "A background image",
"icon": "ri-image-line",
"styleable": true,
"settings": [
{
"type": "text",
"label": "URL",
"key": "url"
},
{
"type": "select",
"label": "Position",
"key": "position",
"defaultValue": "center center",
"options": [
{
"label": "Center Top",
"value": "center top"
},
{
"label": "Center",
"value": "center center"
},
{
"label": "Center Bottom",
"value": "center bottom"
},
{
"label": "Left Top",
"value": "left top"
},
{
"label": "Left Center",
"value": "left center"
},
{
"label": "Left Bottom",
"value": "left bottom"
},
{
"label": "Right Top",
"value": "right top"
},
{
"label": "Right Center",
"value": "right center"
},
{
"label": "Right Bottom",
"value": "right bottom"
}
]
}
]
},
"icon": {
"name": "Icon",
"description": "A basic component for displaying icons",
@ -372,6 +430,11 @@
"type": "boolean",
"label": "New Tab",
"key": "openInNewTab"
},
{
"type": "boolean",
"label": "External",
"key": "external"
}
]
},
@ -1211,6 +1274,34 @@
}
]
},
"passwordfield": {
"name": "Password Field",
"icon": "ri-lock-password-line",
"styleable": true,
"settings": [
{
"type": "field/string",
"label": "Field",
"key": "field"
},
{
"type": "text",
"label": "Label",
"key": "label"
},
{
"type": "text",
"label": "Placeholder",
"key": "placeholder"
},
{
"type": "boolean",
"label": "Disabled",
"key": "disabled",
"defaultValue": false
}
]
},
"optionsfield": {
"name": "Options Picker",
"icon": "ri-file-list-line",

View File

@ -0,0 +1,38 @@
<script>
import { getContext } from "svelte"
const { styleable } = getContext("sdk")
const component = getContext("component")
export let url
export let position
let style = ""
$: {
if (url) {
style += `background-image: url("${url}");`
}
if (position) {
style += `background-position: ${position};`
}
}
</script>
<div class="outer" use:styleable={$component.styles}>
<div class="inner" {style} />
</div>
<style>
.outer {
position: relative;
}
.inner {
position: absolute;
height: 100%;
width: 100%;
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
}
</style>

View File

@ -1,62 +1,62 @@
<script>
import { getContext } from "svelte"
const { styleable } = getContext("sdk")
const { styleable, transition } = getContext("sdk")
const component = getContext("component")
export let type = "div"
</script>
{#if type === 'div'}
<div use:styleable={$component.styles}>
<div in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</div>
{:else if type === 'header'}
<header use:styleable={$component.styles}>
<header in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</header>
{:else if type === 'main'}
<main use:styleable={$component.styles}>
<main in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</main>
{:else if type === 'footer'}
<footer use:styleable={$component.styles}>
<footer in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</footer>
{:else if type === 'aside'}
<aside use:styleable={$component.styles}>
<aside in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</aside>
{:else if type === 'summary'}
<summary use:styleable={$component.styles}>
<summary in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</summary>
{:else if type === 'details'}
<details use:styleable={$component.styles}>
<details in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</details>
{:else if type === 'article'}
<article use:styleable={$component.styles}>
<article in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</article>
{:else if type === 'nav'}
<nav use:styleable={$component.styles}>
<nav in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</nav>
{:else if type === 'mark'}
<mark use:styleable={$component.styles}>
<mark in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</mark>
{:else if type === 'figure'}
<figure use:styleable={$component.styles}>
<figure in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</figure>
{:else if type === 'figcaption'}
<figcaption use:styleable={$component.styles}>
<figcaption in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</figcaption>
{:else if type === 'paragraph'}
<p use:styleable={$component.styles}>
<p in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot />
</p>
{/if}

View File

@ -7,11 +7,19 @@
export let url = ""
export let text = ""
export let openInNewTab = false
export let external = false
$: target = openInNewTab ? "_blank" : "_self"
</script>
<a href={url || '/'} use:linkable {target} use:styleable={$component.styles}>
{text}
<slot />
</a>
{#if external}
<a href={url || '/'} {target} use:styleable={$component.styles}>
{text}
<slot />
</a>
{:else}
<a href={url || '/'} use:linkable {target} use:styleable={$component.styles}>
{text}
<slot />
</a>
{/if}

View File

@ -0,0 +1,5 @@
<script>
import StringField from "./StringField.svelte"
</script>
<StringField {...$$props} type="password" />

View File

@ -8,3 +8,4 @@ export { default as longformfield } from "./LongFormField.svelte"
export { default as datetimefield } from "./DateTimeField.svelte"
export { default as attachmentfield } from "./AttachmentField.svelte"
export { default as relationshipfield } from "./RelationshipField.svelte"
export { default as passwordfield } from "./PasswordField.svelte"

View File

@ -31,5 +31,6 @@ export { default as cardhorizontal } from "./CardHorizontal.svelte"
export { default as cardstat } from "./CardStat.svelte"
export { default as icon } from "./Icon.svelte"
export { default as search } from "./Search.svelte"
export { default as backgroundimage } from "./BackgroundImage.svelte"
export * from "./charts"
export * from "./forms"