forked from tsai/budibase
committed by
GitHub
234 changed files with 7700 additions and 12096 deletions
File diff suppressed because it is too large
@ -1,13 +0,0 @@ |
|||||
import { Screen } from "./utils/Screen" |
|
||||
|
|
||||
export default { |
|
||||
name: `New Row (Empty)`, |
|
||||
create: () => createScreen(), |
|
||||
} |
|
||||
|
|
||||
const createScreen = () => { |
|
||||
return new Screen() |
|
||||
.component("@budibase/standard-components/newrow") |
|
||||
.table("") |
|
||||
.json() |
|
||||
} |
|
||||
@ -1,13 +0,0 @@ |
|||||
import { Screen } from "./utils/Screen" |
|
||||
|
|
||||
export default { |
|
||||
name: `Row Detail (Empty)`, |
|
||||
create: () => createScreen(), |
|
||||
} |
|
||||
|
|
||||
const createScreen = () => { |
|
||||
return new Screen() |
|
||||
.component("@budibase/standard-components/rowdetail") |
|
||||
.table("") |
|
||||
.json() |
|
||||
} |
|
||||
@ -0,0 +1,55 @@ |
|||||
|
import { TableNames } from "../constants" |
||||
|
import { |
||||
|
AUTO_COLUMN_DISPLAY_NAMES, |
||||
|
AUTO_COLUMN_SUB_TYPES, |
||||
|
FIELDS, |
||||
|
isAutoColumnUserRelationship, |
||||
|
} from "../constants/backend" |
||||
|
|
||||
|
export function getAutoColumnInformation(enabled = true) { |
||||
|
let info = {} |
||||
|
for (let [key, subtype] of Object.entries(AUTO_COLUMN_SUB_TYPES)) { |
||||
|
info[subtype] = { enabled, name: AUTO_COLUMN_DISPLAY_NAMES[key] } |
||||
|
} |
||||
|
return info |
||||
|
} |
||||
|
|
||||
|
export function buildAutoColumn(tableName, name, subtype) { |
||||
|
let type, constraints |
||||
|
switch (subtype) { |
||||
|
case AUTO_COLUMN_SUB_TYPES.UPDATED_BY: |
||||
|
case AUTO_COLUMN_SUB_TYPES.CREATED_BY: |
||||
|
type = FIELDS.LINK.type |
||||
|
constraints = FIELDS.LINK.constraints |
||||
|
break |
||||
|
case AUTO_COLUMN_SUB_TYPES.AUTO_ID: |
||||
|
type = FIELDS.NUMBER.type |
||||
|
constraints = FIELDS.NUMBER.constraints |
||||
|
break |
||||
|
case AUTO_COLUMN_SUB_TYPES.UPDATED_AT: |
||||
|
case AUTO_COLUMN_SUB_TYPES.CREATED_AT: |
||||
|
type = FIELDS.DATETIME.type |
||||
|
constraints = FIELDS.DATETIME.constraints |
||||
|
break |
||||
|
default: |
||||
|
type = FIELDS.STRING.type |
||||
|
constraints = FIELDS.STRING.constraints |
||||
|
break |
||||
|
} |
||||
|
if (Object.values(AUTO_COLUMN_SUB_TYPES).indexOf(subtype) === -1) { |
||||
|
throw "Cannot build auto column with supplied subtype" |
||||
|
} |
||||
|
const base = { |
||||
|
name, |
||||
|
type, |
||||
|
subtype, |
||||
|
icon: "ri-magic-line", |
||||
|
autocolumn: true, |
||||
|
constraints, |
||||
|
} |
||||
|
if (isAutoColumnUserRelationship(subtype)) { |
||||
|
base.tableId = TableNames.USERS |
||||
|
base.fieldName = `${tableName}-${name}` |
||||
|
} |
||||
|
return base |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
<script> |
||||
|
import { TextButton } from "@budibase/bbui" |
||||
|
|
||||
|
export let hideAutocolumns |
||||
|
|
||||
|
let anchor |
||||
|
let dropdown |
||||
|
|
||||
|
function hideOrUnhide() { |
||||
|
hideAutocolumns = !hideAutocolumns |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div bind:this={anchor}> |
||||
|
<TextButton text small on:click={hideOrUnhide}> |
||||
|
{#if hideAutocolumns} |
||||
|
<i class="ri-magic-line" /> |
||||
|
Show Auto Columns |
||||
|
{:else}<i class="ri-magic-fill" /> Hide Auto Columns{/if} |
||||
|
</TextButton> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
i { |
||||
|
margin-right: 4px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,43 @@ |
|||||
|
<script> |
||||
|
import { TextButton, Icon, Popover } from "@budibase/bbui" |
||||
|
import { backendUiStore } from "builderStore" |
||||
|
import { Roles } from "constants/backend" |
||||
|
import api from "builderStore/api" |
||||
|
import ManageAccessPopover from "../popovers/ManageAccessPopover.svelte" |
||||
|
|
||||
|
export let resourceId |
||||
|
|
||||
|
let anchor |
||||
|
let dropdown |
||||
|
let levels |
||||
|
let permissions |
||||
|
|
||||
|
async function openDropdown() { |
||||
|
permissions = await backendUiStore.actions.permissions.forResource( |
||||
|
resourceId |
||||
|
) |
||||
|
levels = await backendUiStore.actions.permissions.fetchLevels() |
||||
|
dropdown.show() |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div bind:this={anchor}> |
||||
|
<TextButton text small on:click={openDropdown}> |
||||
|
<i class="ri-lock-line" /> |
||||
|
Manage Access |
||||
|
</TextButton> |
||||
|
</div> |
||||
|
<Popover bind:this={dropdown} {anchor} align="left"> |
||||
|
<ManageAccessPopover |
||||
|
{resourceId} |
||||
|
{levels} |
||||
|
{permissions} |
||||
|
onClosed={dropdown.hide} /> |
||||
|
</Popover> |
||||
|
|
||||
|
<style> |
||||
|
i { |
||||
|
margin-right: var(--spacing-xs); |
||||
|
font-size: var(--font-size-s); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,94 @@ |
|||||
|
<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" |
||||
|
|
||||
|
export let resourceId |
||||
|
export let permissions |
||||
|
export let onClosed |
||||
|
|
||||
|
async function changePermission(level, role) { |
||||
|
await backendUiStore.actions.permissions.save({ |
||||
|
level, |
||||
|
role, |
||||
|
resource: resourceId, |
||||
|
}) |
||||
|
|
||||
|
// Show updated permissions in UI: REMOVE |
||||
|
permissions = await backendUiStore.actions.permissions.forResource( |
||||
|
resourceId |
||||
|
) |
||||
|
notifier.success("Updated permissions.") |
||||
|
// TODO: update permissions |
||||
|
// permissions[] |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="popover"> |
||||
|
<h5>Who Can Access This Data?</h5> |
||||
|
<div class="note"> |
||||
|
<Label extraSmall grey> |
||||
|
Specify the minimum access level role for this data. |
||||
|
</Label> |
||||
|
</div> |
||||
|
<Spacer large /> |
||||
|
<div class="row"> |
||||
|
<Label extraSmall grey>Level</Label> |
||||
|
<Label extraSmall grey>Role</Label> |
||||
|
{#each Object.keys(permissions) as level} |
||||
|
<Input secondary thin value={level} disabled={true} /> |
||||
|
<Select |
||||
|
secondary |
||||
|
thin |
||||
|
value={permissions[level]} |
||||
|
on:change={e => changePermission(level, e.target.value)}> |
||||
|
{#each $backendUiStore.roles as role} |
||||
|
<option value={role._id}>{role.name}</option> |
||||
|
{/each} |
||||
|
</Select> |
||||
|
{/each} |
||||
|
</div> |
||||
|
|
||||
|
<Spacer large /> |
||||
|
|
||||
|
<div class="footer"> |
||||
|
<Button secondary on:click={onClosed}>Cancel</Button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.popover { |
||||
|
display: grid; |
||||
|
width: 400px; |
||||
|
} |
||||
|
|
||||
|
h5 { |
||||
|
margin: 0; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
hr { |
||||
|
margin: var(--spacing-s) 0 var(--spacing-m) 0; |
||||
|
} |
||||
|
|
||||
|
.footer { |
||||
|
display: flex; |
||||
|
justify-content: flex-end; |
||||
|
gap: var(--spacing-m); |
||||
|
margin-top: var(--spacing-l); |
||||
|
} |
||||
|
|
||||
|
.row { |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr; |
||||
|
grid-gap: var(--spacing-m); |
||||
|
} |
||||
|
|
||||
|
.note { |
||||
|
margin-top: 10px; |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
</style> |
||||
@ -1,15 +1,38 @@ |
|||||
<script> |
<script> |
||||
import { Input, TextArea, Spacer } from "@budibase/bbui" |
import { Label, Input, TextArea, Spacer } from "@budibase/bbui" |
||||
|
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte" |
||||
|
|
||||
export let integration |
export let integration |
||||
|
export let schema |
||||
|
|
||||
|
let unsaved = false |
||||
</script> |
</script> |
||||
|
|
||||
<form> |
<form> |
||||
{#each Object.keys(integration) as configKey} |
{#each Object.keys(schema) as configKey} |
||||
<Input |
{#if typeof schema[configKey].type === 'object'} |
||||
type={integration[configKey].type} |
<Label small>{configKey}</Label> |
||||
label={configKey} |
<Spacer small /> |
||||
bind:value={integration[configKey]} /> |
<KeyValueBuilder bind:object={integration[configKey]} on:change /> |
||||
<Spacer large /> |
{:else} |
||||
|
<div class="form-row"> |
||||
|
<Label small>{configKey}</Label> |
||||
|
<Input |
||||
|
outline |
||||
|
type={schema[configKey].type} |
||||
|
on:change |
||||
|
bind:value={integration[configKey]} /> |
||||
|
</div> |
||||
|
{/if} |
||||
{/each} |
{/each} |
||||
</form> |
</form> |
||||
|
|
||||
|
<style> |
||||
|
.form-row { |
||||
|
display: grid; |
||||
|
grid-template-columns: 20% 1fr; |
||||
|
grid-gap: var(--spacing-l); |
||||
|
align-items: center; |
||||
|
margin-bottom: var(--spacing-m); |
||||
|
} |
||||
|
</style> |
||||
|
|||||
@ -0,0 +1,36 @@ |
|||||
|
<script> |
||||
|
export let width = "100" |
||||
|
export let height = "100" |
||||
|
</script> |
||||
|
|
||||
|
<svg |
||||
|
{width} |
||||
|
{height} |
||||
|
viewBox="0 0 120 120" |
||||
|
fill="none" |
||||
|
xmlns="http://www.w3.org/2000/svg"> |
||||
|
<path |
||||
|
d="M103.125 20.625H68.25L60.375 |
||||
|
36.375H16.5V99.375H111V20.625H103.125ZM103.125 36.375H72.375L76.5 |
||||
|
28.5H103.125V36.375Z" |
||||
|
fill="#FFBA58" /> |
||||
|
<path |
||||
|
d="M75 46.875V52.5H60C58.0127 52.5059 56.1085 53.298 54.7033 54.7033C53.298 |
||||
|
56.1085 52.5059 58.0127 52.5 60V75H46.875C44.3886 75 42.004 75.9877 40.2459 |
||||
|
77.7459C38.4877 79.504 37.5 81.8886 37.5 84.375C37.5 86.8614 38.4877 89.246 |
||||
|
40.2459 91.0041C42.004 92.7623 44.3886 93.75 46.875 |
||||
|
93.75H52.5V108.75C52.5059 110.737 53.298 112.642 54.7033 114.047C56.1085 |
||||
|
115.452 58.0127 116.244 60 116.25H74.25V110.625C74.25 107.94 75.3167 105.364 |
||||
|
77.2155 103.466C79.1143 101.567 81.6897 100.5 84.375 100.5C87.0603 100.5 |
||||
|
89.6357 101.567 91.5345 103.466C93.4333 105.364 94.5 107.94 94.5 |
||||
|
110.625V116.25H108.75C110.737 116.244 112.642 115.452 114.047 |
||||
|
114.047C115.452 112.642 116.244 110.737 116.25 108.75V94.5H110.625C107.94 |
||||
|
94.5 105.364 93.4333 103.466 91.5345C101.567 89.6357 100.5 87.0603 100.5 |
||||
|
84.375C100.5 81.6897 101.567 79.1143 103.466 77.2155C105.364 75.3167 107.94 |
||||
|
74.25 110.625 74.25H116.25V60C116.244 58.0127 115.452 56.1085 114.047 |
||||
|
54.7033C112.642 53.298 110.737 52.5059 108.75 52.5H93.75V46.875C93.75 |
||||
|
44.3886 92.7623 42.004 91.0041 40.2459C89.246 38.4877 86.8614 37.5 84.375 |
||||
|
37.5C81.8886 37.5 79.504 38.4877 77.7459 40.2459C75.9877 42.004 75 44.3886 |
||||
|
75 46.875Z" |
||||
|
fill="#E76A00" /> |
||||
|
</svg> |
||||
@ -1,61 +0,0 @@ |
|||||
<script> |
|
||||
import { goto, params } from "@sveltech/routify" |
|
||||
import { backendUiStore, store } from "builderStore" |
|
||||
import { notifier } from "builderStore/store/notifications" |
|
||||
import { Input, Label, ModalContent, Button, Spacer } from "@budibase/bbui" |
|
||||
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 |
|
||||
if ( |
|
||||
$backendUiStore.datasources?.some( |
|
||||
datasource => datasource.name === datasourceName |
|
||||
) |
|
||||
) { |
|
||||
error = `Datasource with name ${tableName} already exists. Please choose another name.` |
|
||||
return |
|
||||
} |
|
||||
error = "" |
|
||||
} |
|
||||
|
|
||||
async function saveDatasource() { |
|
||||
const { type, ...config } = integration |
|
||||
|
|
||||
// Create datasource |
|
||||
await backendUiStore.actions.datasources.save({ |
|
||||
name, |
|
||||
source: type, |
|
||||
config, |
|
||||
}) |
|
||||
notifier.success(`Datasource ${name} created successfully.`) |
|
||||
analytics.captureEvent("Datasource Created", { name }) |
|
||||
|
|
||||
// Navigate to new datasource |
|
||||
$goto(`./datasource/${datasource._id}`) |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<ModalContent |
|
||||
title="Create Datasource" |
|
||||
confirmText="Create" |
|
||||
onConfirm={saveDatasource} |
|
||||
disabled={error || !name}> |
|
||||
<Input |
|
||||
data-cy="datasource-name-input" |
|
||||
thin |
|
||||
label="Datasource Name" |
|
||||
on:input={checkValid} |
|
||||
bind:value={name} |
|
||||
{error} /> |
|
||||
<Label grey extraSmall>Create Integrated Table from External Source</Label> |
|
||||
<TableIntegrationMenu bind:integration /> |
|
||||
</ModalContent> |
|
||||
@ -1,39 +0,0 @@ |
|||||
<script> |
|
||||
import { backendUiStore, store, allScreens } from "builderStore" |
|
||||
import { notifier } from "builderStore/store/notifications" |
|
||||
import { DropdownMenu, Button, Input, TextButton, Icon } from "@budibase/bbui" |
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
|
||||
import IntegrationConfigForm from "../TableIntegrationMenu//IntegrationConfigForm.svelte" |
|
||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" |
|
||||
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte" |
|
||||
|
|
||||
export let bindable |
|
||||
export let parameters |
|
||||
|
|
||||
let anchor |
|
||||
let dropdown |
|
||||
let confirmDeleteDialog |
|
||||
|
|
||||
function hideEditor() { |
|
||||
dropdown?.hide() |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<div on:click|stopPropagation bind:this={anchor}> |
|
||||
<TextButton text on:click={dropdown.show} active={false}> |
|
||||
<Icon name="add" /> |
|
||||
Add Parameters |
|
||||
</TextButton> |
|
||||
<DropdownMenu align="right" {anchor} bind:this={dropdown}> |
|
||||
<div class="wrapper"> |
|
||||
<ParameterBuilder bind:parameters {bindable} /> |
|
||||
</div> |
|
||||
</DropdownMenu> |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.wrapper { |
|
||||
padding: var(--spacing-xl); |
|
||||
min-width: 600px; |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,86 @@ |
|||||
|
<script> |
||||
|
import { Icon, Input, Drawer, Body, Button } from "@budibase/bbui" |
||||
|
import { |
||||
|
readableToRuntimeBinding, |
||||
|
runtimeToReadableBinding, |
||||
|
} from "builderStore/dataBinding" |
||||
|
import BindingPanel from "components/design/PropertiesPanel/BindingPanel.svelte" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
const dispatch = createEventDispatcher() |
||||
|
|
||||
|
export let value = "" |
||||
|
export let bindings = [] |
||||
|
export let thin = true |
||||
|
export let title = "Bindings" |
||||
|
export let placeholder |
||||
|
|
||||
|
let bindingDrawer |
||||
|
|
||||
|
$: tempValue = value |
||||
|
$: readableValue = runtimeToReadableBinding(bindings, value) |
||||
|
|
||||
|
const handleClose = () => { |
||||
|
onChange(tempValue) |
||||
|
bindingDrawer.hide() |
||||
|
} |
||||
|
|
||||
|
const onChange = value => { |
||||
|
dispatch("change", readableToRuntimeBinding(bindings, value)) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="control"> |
||||
|
<Input |
||||
|
{thin} |
||||
|
value={readableValue} |
||||
|
on:change={event => onChange(event.target.value)} |
||||
|
{placeholder} /> |
||||
|
<div class="icon" on:click={bindingDrawer.show}> |
||||
|
<Icon name="lightning" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
<Drawer bind:this={bindingDrawer} {title}> |
||||
|
<div slot="description"> |
||||
|
<Body extraSmall grey> |
||||
|
Add the objects on the left to enrich your text. |
||||
|
</Body> |
||||
|
</div> |
||||
|
<heading slot="buttons"> |
||||
|
<Button thin blue on:click={handleClose}>Save</Button> |
||||
|
</heading> |
||||
|
<div slot="body"> |
||||
|
<BindingPanel |
||||
|
value={readableValue} |
||||
|
close={handleClose} |
||||
|
on:update={event => (tempValue = event.detail)} |
||||
|
bindableProperties={bindings} /> |
||||
|
</div> |
||||
|
</Drawer> |
||||
|
|
||||
|
<style> |
||||
|
.control { |
||||
|
flex: 1; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.icon { |
||||
|
right: 2px; |
||||
|
top: 2px; |
||||
|
bottom: 2px; |
||||
|
position: absolute; |
||||
|
align-items: center; |
||||
|
display: flex; |
||||
|
box-sizing: border-box; |
||||
|
padding-left: 7px; |
||||
|
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: 14px; |
||||
|
} |
||||
|
.icon:hover { |
||||
|
color: var(--ink); |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<script> |
||||
|
import FormFieldSelect from "./FormFieldSelect.svelte" |
||||
|
</script> |
||||
|
|
||||
|
<FormFieldSelect {...$$props} type="attachment" /> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<script> |
||||
|
import FormFieldSelect from "./FormFieldSelect.svelte" |
||||
|
</script> |
||||
|
|
||||
|
<FormFieldSelect {...$$props} type="boolean" /> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<script> |
||||
|
import FormFieldSelect from "./FormFieldSelect.svelte" |
||||
|
</script> |
||||
|
|
||||
|
<FormFieldSelect {...$$props} type="datetime" /> |
||||
@ -1,207 +0,0 @@ |
|||||
<script> |
|
||||
import { TextButton, Body, DropdownMenu, ModalContent } from "@budibase/bbui" |
|
||||
import { AddIcon, ArrowDownIcon } from "components/common/Icons/" |
|
||||
import actionTypes from "./actions" |
|
||||
import { createEventDispatcher } from "svelte" |
|
||||
import { automationStore } from "builderStore" |
|
||||
|
|
||||
const dispatch = createEventDispatcher() |
|
||||
const eventTypeKey = "##eventHandlerType" |
|
||||
|
|
||||
export let event |
|
||||
|
|
||||
let addActionButton |
|
||||
let addActionDropdown |
|
||||
let selectedAction |
|
||||
|
|
||||
$: actions = event || [] |
|
||||
$: selectedActionComponent = |
|
||||
selectedAction && |
|
||||
actionTypes.find(t => t.name === selectedAction[eventTypeKey]).component |
|
||||
|
|
||||
const deleteAction = index => { |
|
||||
actions.splice(index, 1) |
|
||||
actions = actions |
|
||||
} |
|
||||
|
|
||||
const addAction = actionType => () => { |
|
||||
const newAction = { |
|
||||
parameters: {}, |
|
||||
[eventTypeKey]: actionType.name, |
|
||||
} |
|
||||
actions.push(newAction) |
|
||||
selectedAction = newAction |
|
||||
actions = actions |
|
||||
addActionDropdown.hide() |
|
||||
} |
|
||||
|
|
||||
const selectAction = action => () => { |
|
||||
selectedAction = action |
|
||||
} |
|
||||
|
|
||||
const saveEventData = async () => { |
|
||||
// e.g. The Trigger Automation action exposes beforeSave, so it can |
|
||||
// create any automations it needs to |
|
||||
for (let action of actions) { |
|
||||
if (action[eventTypeKey] === "Trigger Automation") { |
|
||||
await createAutomation(action.parameters) |
|
||||
} |
|
||||
} |
|
||||
dispatch("change", actions) |
|
||||
} |
|
||||
|
|
||||
// called by the parent modal when actions are saved |
|
||||
const createAutomation = async parameters => { |
|
||||
if (parameters.automationId || !parameters.newAutomationName) return |
|
||||
|
|
||||
await automationStore.actions.create({ name: parameters.newAutomationName }) |
|
||||
|
|
||||
const appActionDefinition = $automationStore.blockDefinitions.TRIGGER.APP |
|
||||
|
|
||||
const newBlock = $automationStore.selectedAutomation.constructBlock( |
|
||||
"TRIGGER", |
|
||||
"APP", |
|
||||
appActionDefinition |
|
||||
) |
|
||||
|
|
||||
newBlock.inputs = { |
|
||||
fields: Object.entries(parameters.fields).reduce( |
|
||||
(fields, [key, value]) => { |
|
||||
fields[key] = value.type |
|
||||
return fields |
|
||||
}, |
|
||||
{} |
|
||||
), |
|
||||
} |
|
||||
|
|
||||
automationStore.actions.addBlockToAutomation(newBlock) |
|
||||
|
|
||||
await automationStore.actions.save($automationStore.selectedAutomation) |
|
||||
|
|
||||
parameters.automationId = $automationStore.selectedAutomation.automation._id |
|
||||
delete parameters.newAutomationName |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<ModalContent title="Actions" confirmText="Save" onConfirm={saveEventData}> |
|
||||
<div slot="header"> |
|
||||
<div bind:this={addActionButton}> |
|
||||
<TextButton text small blue on:click={addActionDropdown.show}> |
|
||||
<div style="height: 20px; width: 20px;"> |
|
||||
<AddIcon /> |
|
||||
</div> |
|
||||
Add Action |
|
||||
</TextButton> |
|
||||
</div> |
|
||||
<DropdownMenu |
|
||||
bind:this={addActionDropdown} |
|
||||
anchor={addActionButton} |
|
||||
align="right"> |
|
||||
<div class="available-actions-container"> |
|
||||
{#each actionTypes as actionType} |
|
||||
<div class="available-action" on:click={addAction(actionType)}> |
|
||||
<span>{actionType.name}</span> |
|
||||
</div> |
|
||||
{/each} |
|
||||
</div> |
|
||||
</DropdownMenu> |
|
||||
</div> |
|
||||
|
|
||||
<div class="actions-container"> |
|
||||
{#if actions && actions.length > 0} |
|
||||
{#each actions as action, index} |
|
||||
<div class="action-container"> |
|
||||
<div class="action-header" on:click={selectAction(action)}> |
|
||||
<Body small lh>{index + 1}. {action[eventTypeKey]}</Body> |
|
||||
<div class="row-expander" class:rotate={action !== selectedAction}> |
|
||||
<ArrowDownIcon /> |
|
||||
</div> |
|
||||
</div> |
|
||||
{#if action === selectedAction} |
|
||||
<div class="selected-action-container"> |
|
||||
<svelte:component |
|
||||
this={selectedActionComponent} |
|
||||
parameters={selectedAction.parameters} /> |
|
||||
<div class="delete-action-button"> |
|
||||
<TextButton text medium on:click={() => deleteAction(index)}> |
|
||||
Delete |
|
||||
</TextButton> |
|
||||
</div> |
|
||||
</div> |
|
||||
{/if} |
|
||||
</div> |
|
||||
{/each} |
|
||||
{/if} |
|
||||
</div> |
|
||||
|
|
||||
<div slot="footer"> |
|
||||
<a href="https://docs.budibase.com">Learn more about Actions</a> |
|
||||
</div> |
|
||||
</ModalContent> |
|
||||
|
|
||||
<style> |
|
||||
.action-header { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
align-items: center; |
|
||||
} |
|
||||
|
|
||||
.action-header > p { |
|
||||
flex: 1; |
|
||||
} |
|
||||
|
|
||||
.row-expander { |
|
||||
height: 30px; |
|
||||
width: 30px; |
|
||||
} |
|
||||
|
|
||||
.available-action { |
|
||||
padding: var(--spacing-m); |
|
||||
font-size: var(--font-size-m); |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.available-action:hover { |
|
||||
background: var(--grey-2); |
|
||||
} |
|
||||
|
|
||||
.actions-container { |
|
||||
flex: 1; |
|
||||
min-height: 0; |
|
||||
padding-top: 0; |
|
||||
border: var(--border-light); |
|
||||
border-width: 0 0 1px 0; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
|
|
||||
.action-container { |
|
||||
border: var(--border-light); |
|
||||
border-width: 1px 0 0 0; |
|
||||
} |
|
||||
|
|
||||
.selected-action-container { |
|
||||
padding-bottom: var(--spacing-s); |
|
||||
padding-top: var(--spacing-s); |
|
||||
} |
|
||||
|
|
||||
.delete-action-button { |
|
||||
padding-top: var(--spacing-l); |
|
||||
display: flex; |
|
||||
justify-content: flex-end; |
|
||||
flex-direction: row; |
|
||||
} |
|
||||
|
|
||||
a { |
|
||||
flex: 1; |
|
||||
color: var(--grey-5); |
|
||||
font-size: var(--font-size-s); |
|
||||
text-decoration: none; |
|
||||
} |
|
||||
a:hover { |
|
||||
color: var(--blue); |
|
||||
} |
|
||||
|
|
||||
.rotate :global(svg) { |
|
||||
transform: rotate(90deg); |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,38 @@ |
|||||
|
<script> |
||||
|
import { Select, Label } from "@budibase/bbui" |
||||
|
import { currentAsset, store } from "builderStore" |
||||
|
import { getActionProviderComponents } from "builderStore/dataBinding" |
||||
|
|
||||
|
export let parameters |
||||
|
|
||||
|
$: actionProviders = getActionProviderComponents( |
||||
|
$currentAsset, |
||||
|
$store.selectedComponentId, |
||||
|
"ValidateForm" |
||||
|
) |
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
<Label small>Form</Label> |
||||
|
<Select thin secondary bind:value={parameters.componentId}> |
||||
|
<option value="" /> |
||||
|
{#if actionProviders} |
||||
|
{#each actionProviders as component} |
||||
|
<option value={component._id}>{component._instanceName}</option> |
||||
|
{/each} |
||||
|
{/if} |
||||
|
</Select> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.root { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: baseline; |
||||
|
} |
||||
|
|
||||
|
.root :global(> div) { |
||||
|
flex: 1; |
||||
|
margin-left: var(--spacing-l); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,77 @@ |
|||||
|
<script> |
||||
|
import { Button, Drawer, Spacer, Body } from "@budibase/bbui" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
import { notifier } from "builderStore/store/notifications" |
||||
|
import { |
||||
|
getDatasourceForProvider, |
||||
|
getSchemaForDatasource, |
||||
|
} from "builderStore/dataBinding" |
||||
|
import SaveFields from "./EventsEditor/actions/SaveFields.svelte" |
||||
|
|
||||
|
const dispatch = createEventDispatcher() |
||||
|
|
||||
|
export let value = {} |
||||
|
export let componentInstance |
||||
|
let drawer |
||||
|
let tempValue = value |
||||
|
|
||||
|
$: schemaFields = getSchemaFields(componentInstance) |
||||
|
|
||||
|
const getSchemaFields = component => { |
||||
|
const datasource = getDatasourceForProvider(component) |
||||
|
const { schema } = getSchemaForDatasource(datasource) |
||||
|
return Object.values(schema || {}) |
||||
|
} |
||||
|
|
||||
|
const saveFilter = async () => { |
||||
|
dispatch("change", tempValue) |
||||
|
notifier.success("Filters saved.") |
||||
|
drawer.hide() |
||||
|
} |
||||
|
|
||||
|
const onFieldsChanged = event => { |
||||
|
tempValue = event.detail |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<Button secondary wide on:click={drawer.show}>Define Filters</Button> |
||||
|
<Drawer bind:this={drawer} title={'Filtering'}> |
||||
|
<heading slot="buttons"> |
||||
|
<Button thin blue on:click={saveFilter}>Save</Button> |
||||
|
</heading> |
||||
|
<div slot="body"> |
||||
|
<div class="root"> |
||||
|
<Body small grey> |
||||
|
{#if !Object.keys(tempValue || {}).length} |
||||
|
Add your first filter column. |
||||
|
{:else} |
||||
|
Results are filtered to only those which match all of the following |
||||
|
constaints. |
||||
|
{/if} |
||||
|
</Body> |
||||
|
<Spacer medium /> |
||||
|
<div class="fields"> |
||||
|
<SaveFields |
||||
|
parameterFields={value} |
||||
|
{schemaFields} |
||||
|
valueLabel="Equals" |
||||
|
on:change={onFieldsChanged} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Drawer> |
||||
|
|
||||
|
<style> |
||||
|
.root { |
||||
|
padding: var(--spacing-l); |
||||
|
min-height: calc(40vh - 2 * var(--spacing-l)); |
||||
|
} |
||||
|
|
||||
|
.fields { |
||||
|
display: grid; |
||||
|
column-gap: var(--spacing-l); |
||||
|
row-gap: var(--spacing-s); |
||||
|
grid-template-columns: auto 1fr auto 1fr auto; |
||||
|
align-items: baseline; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,59 @@ |
|||||
|
<script> |
||||
|
import { DataList } from "@budibase/bbui" |
||||
|
import { |
||||
|
getDatasourceForProvider, |
||||
|
getSchemaForDatasource, |
||||
|
} from "builderStore/dataBinding" |
||||
|
import { currentAsset } from "builderStore" |
||||
|
import { findClosestMatchingComponent } from "builderStore/storeUtils" |
||||
|
|
||||
|
export let componentInstance |
||||
|
export let value |
||||
|
export let onChange |
||||
|
export let type |
||||
|
|
||||
|
$: form = findClosestMatchingComponent( |
||||
|
$currentAsset.props, |
||||
|
componentInstance._id, |
||||
|
component => component._component === "@budibase/standard-components/form" |
||||
|
) |
||||
|
$: datasource = getDatasourceForProvider(form) |
||||
|
$: schema = getSchemaForDatasource(datasource, true).schema |
||||
|
$: options = getOptions(schema, type) |
||||
|
|
||||
|
const getOptions = (schema, fieldType) => { |
||||
|
let entries = Object.entries(schema ?? {}) |
||||
|
if (fieldType) { |
||||
|
entries = entries.filter(entry => entry[1].type === fieldType) |
||||
|
} |
||||
|
return entries.map(entry => entry[0]) |
||||
|
} |
||||
|
|
||||
|
const handleBlur = () => onChange(value) |
||||
|
</script> |
||||
|
|
||||
|
<div> |
||||
|
<DataList |
||||
|
editable |
||||
|
secondary |
||||
|
extraThin |
||||
|
on:blur={handleBlur} |
||||
|
on:change |
||||
|
bind:value> |
||||
|
<option value="" /> |
||||
|
{#each options as option} |
||||
|
<option value={option}>{option}</option> |
||||
|
{/each} |
||||
|
</DataList> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
div { |
||||
|
flex: 1 1 auto; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
} |
||||
|
div :global(> div) { |
||||
|
flex: 1 1 auto; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<script> |
||||
|
import FormFieldSelect from "./FormFieldSelect.svelte" |
||||
|
</script> |
||||
|
|
||||
|
<FormFieldSelect {...$$props} type="longform" /> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<script> |
||||
|
import FieldSelect from "./FieldSelect.svelte" |
||||
|
</script> |
||||
|
|
||||
|
<FieldSelect {...$$props} multiselect /> |
||||
@ -1,5 +0,0 @@ |
|||||
<script> |
|
||||
import TableViewFieldSelect from "./TableViewFieldSelect.svelte" |
|
||||
</script> |
|
||||
|
|
||||
<TableViewFieldSelect {...$$props} multiselect /> |
|
||||
@ -0,0 +1,5 @@ |
|||||
|
<script> |
||||
|
import FormFieldSelect from "./FormFieldSelect.svelte" |
||||
|
</script> |
||||
|
|
||||
|
<FormFieldSelect {...$$props} type="number" /> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<script> |
||||
|
import FormFieldSelect from "./FormFieldSelect.svelte" |
||||
|
</script> |
||||
|
|
||||
|
<FormFieldSelect {...$$props} type="options" /> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<script> |
||||
|
import FormFieldSelect from "./FormFieldSelect.svelte" |
||||
|
</script> |
||||
|
|
||||
|
<FormFieldSelect {...$$props} type="link" /> |
||||
@ -0,0 +1,7 @@ |
|||||
|
<script> |
||||
|
import DatasourceSelect from "./DatasourceSelect.svelte" |
||||
|
|
||||
|
const otherSources = [{ name: "Custom", label: "Custom" }] |
||||
|
</script> |
||||
|
|
||||
|
<DatasourceSelect on:change {...$$props} {otherSources} /> |
||||
@ -1,80 +0,0 @@ |
|||||
<script> |
|
||||
import { DataList } from "@budibase/bbui" |
|
||||
import { createEventDispatcher } from "svelte" |
|
||||
import { store, allScreens, currentAsset } from "builderStore" |
|
||||
import { getBindableProperties } from "builderStore/dataBinding" |
|
||||
|
|
||||
export let value = "" |
|
||||
|
|
||||
$: urls = getUrls($allScreens, $currentAsset, $store.selectedComponentId) |
|
||||
|
|
||||
// Update value on blur |
|
||||
const dispatch = createEventDispatcher() |
|
||||
const handleBlur = () => dispatch("change", value) |
|
||||
|
|
||||
// Get all valid screen URL, as well as detail screens which can be used in |
|
||||
// the current data context |
|
||||
const getUrls = (screens, asset, componentId) => { |
|
||||
// Get all screens which aren't detail screens |
|
||||
let urls = screens |
|
||||
.filter(screen => !screen.props._component.endsWith("/rowdetail")) |
|
||||
.map(screen => ({ |
|
||||
name: screen.props._instanceName, |
|
||||
url: screen.routing.route, |
|
||||
sort: screen.props._component, |
|
||||
})) |
|
||||
|
|
||||
// Add detail screens enriched with the current data context |
|
||||
const bindableProperties = getBindableProperties(asset.props, componentId) |
|
||||
screens |
|
||||
.filter(screen => screen.props._component.endsWith("/rowdetail")) |
|
||||
.forEach(detailScreen => { |
|
||||
// Find any _id bindings that match the detail screen's table |
|
||||
const binding = bindableProperties.find(p => { |
|
||||
return ( |
|
||||
p.type === "context" && |
|
||||
p.runtimeBinding.endsWith("._id") && |
|
||||
p.tableId === detailScreen.props.table |
|
||||
) |
|
||||
}) |
|
||||
if (binding) { |
|
||||
urls.push({ |
|
||||
name: detailScreen.props._instanceName, |
|
||||
url: detailScreen.routing.route.replace( |
|
||||
":id", |
|
||||
`{{ ${binding.runtimeBinding} }}` |
|
||||
), |
|
||||
sort: detailScreen.props._component, |
|
||||
}) |
|
||||
} |
|
||||
}) |
|
||||
|
|
||||
return urls |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<div> |
|
||||
<DataList |
|
||||
editable |
|
||||
secondary |
|
||||
extraThin |
|
||||
on:blur={handleBlur} |
|
||||
on:change |
|
||||
bind:value> |
|
||||
<option value="" /> |
|
||||
{#each urls as url} |
|
||||
<option value={url.url}>{url.name}</option> |
|
||||
{/each} |
|
||||
</DataList> |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
div { |
|
||||
flex: 1 1 auto; |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
} |
|
||||
div :global(> div) { |
|
||||
flex: 1 1 auto; |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,5 @@ |
|||||
|
<script> |
||||
|
import FormFieldSelect from "./FormFieldSelect.svelte" |
||||
|
</script> |
||||
|
|
||||
|
<FormFieldSelect {...$$props} type="string" /> |
||||
@ -0,0 +1,50 @@ |
|||||
|
<script> |
||||
|
import { Button, Input } from "@budibase/bbui" |
||||
|
|
||||
|
export let object = {} |
||||
|
export let readOnly |
||||
|
|
||||
|
let fields = Object.entries(object).map(([name, value]) => ({ name, value })) |
||||
|
|
||||
|
$: object = fields.reduce( |
||||
|
(acc, next) => ({ ...acc, [next.name]: next.value }), |
||||
|
{} |
||||
|
) |
||||
|
|
||||
|
function addEntry() { |
||||
|
fields = [...fields, {}] |
||||
|
} |
||||
|
|
||||
|
function deleteEntry(idx) { |
||||
|
fields.splice(idx, 1) |
||||
|
fields = fields |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<!-- Builds Objects with Key Value Pairs. Useful for building things like Request Headers. --> |
||||
|
<div class="container" class:readOnly> |
||||
|
{#each fields as field, idx} |
||||
|
<Input placeholder="Key" thin outline bind:value={field.name} /> |
||||
|
<Input placeholder="Value" thin outline bind:value={field.value} /> |
||||
|
{#if !readOnly} |
||||
|
<i class="ri-close-circle-fill" on:click={() => deleteEntry(idx)} /> |
||||
|
{/if} |
||||
|
{/each} |
||||
|
</div> |
||||
|
{#if !readOnly} |
||||
|
<Button secondary thin outline on:click={addEntry}>Add</Button> |
||||
|
{/if} |
||||
|
|
||||
|
<style> |
||||
|
.container { |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr 20px; |
||||
|
grid-gap: var(--spacing-m); |
||||
|
align-items: center; |
||||
|
margin-bottom: var(--spacing-m); |
||||
|
} |
||||
|
|
||||
|
.ri-close-circle-fill { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
</style> |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue