Fix and tidy up all button actions and drawers
This commit is contained in:
parent
7494714845
commit
2460280841
|
@ -10,10 +10,13 @@
|
|||
|
||||
export let value = ""
|
||||
export let bindings = []
|
||||
export let thin = true
|
||||
export let title = "Bindings"
|
||||
export let placeholder
|
||||
|
||||
let bindingDrawer
|
||||
let tempValue = value
|
||||
|
||||
$: tempValue = value
|
||||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||
|
||||
const handleClose = () => {
|
||||
|
@ -28,15 +31,15 @@
|
|||
|
||||
<div class="control">
|
||||
<Input
|
||||
thin
|
||||
{thin}
|
||||
value={readableValue}
|
||||
on:change={event => onChange(event.target.value)}
|
||||
placeholder="/screen" />
|
||||
{placeholder} />
|
||||
<div class="icon" on:click={bindingDrawer.show}>
|
||||
<Icon name="lightning" />
|
||||
</div>
|
||||
</div>
|
||||
<Drawer bind:this={bindingDrawer} title="Bindings">
|
||||
<Drawer bind:this={bindingDrawer} {title}>
|
||||
<div slot="description">
|
||||
<Body extraSmall grey>
|
||||
Add the objects on the left to enrich your text.
|
||||
|
@ -57,7 +60,6 @@
|
|||
<style>
|
||||
.control {
|
||||
flex: 1;
|
||||
margin-left: var(--spacing-l);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
|
|
@ -114,8 +114,7 @@
|
|||
bind:getCaretPosition
|
||||
thin
|
||||
bind:value
|
||||
placeholder="Add text, or click the objects on the left to add them to
|
||||
the textbox." />
|
||||
placeholder="Add text, or click the objects on the left to add them to the textbox." />
|
||||
{#if !valid}
|
||||
<p class="syntax-error">
|
||||
Current Handlebars syntax is invalid, please check the guide
|
||||
|
@ -144,9 +143,12 @@
|
|||
}
|
||||
|
||||
.text {
|
||||
padding: var(--spacing-xl);
|
||||
padding: var(--spacing-l);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
.text :global(textarea) {
|
||||
min-height: 100px;
|
||||
}
|
||||
.text :global(p) {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
import { notifier } from "builderStore/store/notifications"
|
||||
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||
import { getSchemaForDatasource } from "builderStore/dataBinding"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let anchorRight, dropdownRight
|
||||
|
@ -77,7 +78,7 @@
|
|||
dropdownRight.hide()
|
||||
}
|
||||
|
||||
function fetchDatasourceSchema(query) {
|
||||
function fetchQueryDefinition(query) {
|
||||
const source = $backendUiStore.datasources.find(
|
||||
ds => ds._id === query.datasourceId
|
||||
).source
|
||||
|
@ -85,43 +86,46 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="dropdownbutton"
|
||||
bind:this={anchorRight}
|
||||
on:click={dropdownRight.show}>
|
||||
<span>{value?.label ?? 'Choose option'}</span>
|
||||
<Icon name="arrowdown" />
|
||||
<div class="container">
|
||||
<div
|
||||
class="dropdownbutton"
|
||||
bind:this={anchorRight}
|
||||
on:click={dropdownRight.show}>
|
||||
<span>{value?.label ?? 'Choose option'}</span>
|
||||
<Icon name="arrowdown" />
|
||||
</div>
|
||||
{#if value?.type === 'query'}
|
||||
<i class="ri-settings-5-line" on:click={drawer.show} />
|
||||
<Drawer title={'Query'} bind:this={drawer}>
|
||||
<div slot="buttons">
|
||||
<Button
|
||||
blue
|
||||
thin
|
||||
on:click={() => {
|
||||
notifier.success('Query parameters saved.')
|
||||
handleSelected(value)
|
||||
drawer.hide()
|
||||
}}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
<div class="drawer-contents" slot="body">
|
||||
{#if value.parameters.length > 0}
|
||||
<ParameterBuilder
|
||||
bind:customParams={value.queryParams}
|
||||
parameters={queries.find(query => query._id === value._id).parameters}
|
||||
bindings={queryBindableProperties} />
|
||||
{/if}
|
||||
<IntegrationQueryEditor
|
||||
height={200}
|
||||
query={value}
|
||||
schema={fetchQueryDefinition(value)}
|
||||
editable={false} />
|
||||
<Spacer large />
|
||||
</div>
|
||||
</Drawer>
|
||||
{/if}
|
||||
</div>
|
||||
{#if value?.type === 'query'}
|
||||
<i class="ri-settings-5-line" on:click={drawer.show} />
|
||||
<Drawer title={'Query'} bind:this={drawer}>
|
||||
<div slot="buttons">
|
||||
<Button
|
||||
blue
|
||||
thin
|
||||
on:click={() => {
|
||||
notifier.success('Query parameters saved.')
|
||||
handleSelected(value)
|
||||
drawer.hide()
|
||||
}}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
<div class="drawer-contents" slot="body">
|
||||
<IntegrationQueryEditor
|
||||
query={value}
|
||||
schema={fetchDatasourceSchema(value)}
|
||||
editable={false} />
|
||||
<Spacer large />
|
||||
{#if value.parameters.length > 0}
|
||||
<ParameterBuilder
|
||||
bind:customParams={value.queryParams}
|
||||
parameters={queries.find(query => query._id === value._id).parameters}
|
||||
bindings={queryBindableProperties} />
|
||||
{/if}
|
||||
</div>
|
||||
</Drawer>
|
||||
{/if}
|
||||
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}>
|
||||
<div class="dropdown">
|
||||
<div class="title">
|
||||
|
@ -196,6 +200,13 @@
|
|||
</DropdownMenu>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dropdownbutton {
|
||||
background-color: var(--grey-2);
|
||||
border: var(--border-transparent);
|
||||
|
@ -258,8 +269,8 @@
|
|||
}
|
||||
|
||||
.drawer-contents {
|
||||
padding: var(--spacing-xl);
|
||||
height: 40vh;
|
||||
padding: var(--spacing-l);
|
||||
height: calc(40vh - 2 * var(--spacing-l));
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,12 +8,19 @@
|
|||
|
||||
let addActionButton
|
||||
let addActionDropdown
|
||||
let selectedAction
|
||||
let selectedAction = actions?.length ? actions[0] : null
|
||||
|
||||
$: selectedActionComponent =
|
||||
selectedAction &&
|
||||
actionTypes.find(t => t.name === selectedAction[EVENT_TYPE_KEY]).component
|
||||
|
||||
// Select the first action if we delete an action
|
||||
$: {
|
||||
if (selectedAction && !actions?.includes(selectedAction)) {
|
||||
selectedAction = actions?.[0]
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAction = index => {
|
||||
actions.splice(index, 1)
|
||||
actions = actions
|
||||
|
@ -42,11 +49,10 @@
|
|||
<div class="actions-list">
|
||||
<div>
|
||||
<div bind:this={addActionButton}>
|
||||
<Spacer small />
|
||||
<Button wide secondary on:click={addActionDropdown.show}>
|
||||
Add Action
|
||||
</Button>
|
||||
<Spacer medium />
|
||||
<Spacer small />
|
||||
</div>
|
||||
<DropdownMenu
|
||||
bind:this={addActionDropdown}
|
||||
|
@ -65,11 +71,12 @@
|
|||
{#if actions && actions.length > 0}
|
||||
{#each actions as action, index}
|
||||
<div class="action-container">
|
||||
<div class="action-header" on:click={selectAction(action)}>
|
||||
<span class:selected={action === selectedAction}>
|
||||
{index + 1}.
|
||||
{action[EVENT_TYPE_KEY]}
|
||||
</span>
|
||||
<div
|
||||
class="action-header"
|
||||
class:selected={action === selectedAction}
|
||||
on:click={selectAction(action)}>
|
||||
{index + 1}.
|
||||
{action[EVENT_TYPE_KEY]}
|
||||
</div>
|
||||
<i
|
||||
class="ri-close-fill"
|
||||
|
@ -98,20 +105,22 @@
|
|||
margin-top: var(--spacing-m);
|
||||
}
|
||||
|
||||
.action-header > span {
|
||||
.action-header {
|
||||
margin-bottom: var(--spacing-m);
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--grey-7);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.action-header > span:hover,
|
||||
.selected {
|
||||
.action-header:hover,
|
||||
.action-header.selected {
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.actions-list {
|
||||
border-right: var(--border-light);
|
||||
padding: var(--spacing-s);
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
|
||||
.available-action {
|
||||
|
@ -127,7 +136,6 @@
|
|||
.actions-container {
|
||||
height: 40vh;
|
||||
display: grid;
|
||||
grid-gap: var(--spacing-m);
|
||||
grid-template-columns: 260px 1fr;
|
||||
grid-auto-flow: column;
|
||||
min-height: 0;
|
||||
|
@ -136,13 +144,16 @@
|
|||
}
|
||||
|
||||
.action-container {
|
||||
border-top: var(--border-light);
|
||||
border-bottom: 1px solid var(--grey-1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.action-container:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.selected-action-container {
|
||||
padding: var(--spacing-xl);
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
|
@ -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>
|
|
@ -17,7 +17,9 @@
|
|||
const automationsToCreate = value.filter(
|
||||
action => action["##eventHandlerType"] === "Trigger Automation"
|
||||
)
|
||||
automationsToCreate.forEach(action => createAutomation(action.parameters))
|
||||
for (let action of automationsToCreate) {
|
||||
await createAutomation(action.parameters)
|
||||
}
|
||||
|
||||
dispatch("change", value)
|
||||
notifier.success("Component actions saved.")
|
||||
|
@ -27,11 +29,8 @@
|
|||
// 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",
|
||||
|
@ -39,19 +38,14 @@
|
|||
)
|
||||
|
||||
newBlock.inputs = {
|
||||
fields: Object.entries(parameters.fields).reduce(
|
||||
(fields, [key, value]) => {
|
||||
fields[key] = value.type
|
||||
return fields
|
||||
},
|
||||
{}
|
||||
),
|
||||
fields: Object.keys(parameters.fields).reduce((fields, key) => {
|
||||
fields[key] = "string"
|
||||
return fields
|
||||
}, {}),
|
||||
}
|
||||
|
||||
automationStore.actions.addBlockToAutomation(newBlock)
|
||||
|
||||
await automationStore.actions.save($automationStore.selectedAutomation)
|
||||
|
||||
parameters.automationId = $automationStore.selectedAutomation.automation._id
|
||||
delete parameters.newAutomationName
|
||||
}
|
||||
|
|
|
@ -37,8 +37,8 @@
|
|||
a List
|
||||
</div>
|
||||
{:else}
|
||||
<Label size="m" color="dark">Datasource</Label>
|
||||
<Select secondary bind:value={parameters.providerId}>
|
||||
<Label small>Datasource</Label>
|
||||
<Select thin secondary bind:value={parameters.providerId}>
|
||||
<option value="" />
|
||||
{#each dataProviderComponents as provider}
|
||||
<option value={provider._id}>{provider._instanceName}</option>
|
||||
|
@ -50,22 +50,15 @@
|
|||
<style>
|
||||
.root {
|
||||
display: grid;
|
||||
column-gap: var(--spacing-s);
|
||||
column-gap: var(--spacing-l);
|
||||
row-gap: var(--spacing-s);
|
||||
grid-template-columns: auto 1fr auto 1fr auto;
|
||||
grid-template-columns: auto 1fr;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.root :global(> div:nth-child(2)) {
|
||||
grid-column-start: 2;
|
||||
grid-column-end: 6;
|
||||
}
|
||||
|
||||
.cannot-use {
|
||||
color: var(--red);
|
||||
font-size: var(--font-size-s);
|
||||
text-align: center;
|
||||
width: 70%;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,63 +3,57 @@
|
|||
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||
|
||||
export let parameters
|
||||
|
||||
$: query = $backendUiStore.queries.find(q => q._id === parameters.queryId)
|
||||
$: datasource = $backendUiStore.datasources.find(
|
||||
ds => ds._id === parameters.datasourceId
|
||||
)
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId
|
||||
).map(property => ({
|
||||
...property,
|
||||
category: property.type === "instance" ? "Component" : "Table",
|
||||
label: property.readableBinding,
|
||||
path: property.runtimeBinding,
|
||||
}))
|
||||
)
|
||||
|
||||
$: query =
|
||||
parameters.queryId &&
|
||||
$backendUiStore.queries.find(query => query._id === parameters.queryId)
|
||||
function fetchQueryDefinition(query) {
|
||||
const source = $backendUiStore.datasources.find(
|
||||
ds => ds._id === query.datasourceId
|
||||
).source
|
||||
return $backendUiStore.integrations[source].query[query.queryVerb]
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<Label size="m" color="dark">Datasource</Label>
|
||||
<Select thin secondary bind:value={parameters.datasourceId}>
|
||||
<Label small>Datasource</Label>
|
||||
<Select thin secondary bind:value={parameters.datasourceId}>
|
||||
<option value="" />
|
||||
{#each $backendUiStore.datasources as datasource}
|
||||
<option value={datasource._id}>{datasource.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
|
||||
<Spacer medium />
|
||||
|
||||
{#if parameters.datasourceId}
|
||||
<Label small>Query</Label>
|
||||
<Select thin secondary bind:value={parameters.queryId}>
|
||||
<option value="" />
|
||||
{#each $backendUiStore.datasources as datasource}
|
||||
<option value={datasource._id}>{datasource.name}</option>
|
||||
{#each $backendUiStore.queries.filter(query => query.datasourceId === datasource._id) as query}
|
||||
<option value={query._id}>{query.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{/if}
|
||||
|
||||
<Spacer medium />
|
||||
<Spacer medium />
|
||||
|
||||
{#if parameters.datasourceId}
|
||||
<Label size="m" color="dark">Query</Label>
|
||||
<Select thin secondary bind:value={parameters.queryId}>
|
||||
<option value="" />
|
||||
{#each $backendUiStore.queries.filter(query => query.datasourceId === datasource._id) as query}
|
||||
<option value={query._id}>{query.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{/if}
|
||||
|
||||
<Spacer medium />
|
||||
|
||||
{#if query?.parameters?.length > 0}
|
||||
<ParameterBuilder
|
||||
bind:customParams={parameters.queryParams}
|
||||
parameters={query.parameters}
|
||||
bindings={bindableProperties} />
|
||||
{#if query.fields.sql}
|
||||
<pre>{query.fields.queryString}</pre>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
padding: var(--spacing-m);
|
||||
}
|
||||
</style>
|
||||
{#if query?.parameters?.length > 0}
|
||||
<ParameterBuilder
|
||||
bind:customParams={parameters.queryParams}
|
||||
parameters={query.parameters}
|
||||
bindings={bindableProperties} />
|
||||
<IntegrationQueryEditor
|
||||
height={200}
|
||||
{query}
|
||||
schema={fetchQueryDefinition(query)}
|
||||
editable={false} />
|
||||
{/if}
|
||||
|
|
|
@ -13,8 +13,10 @@
|
|||
</script>
|
||||
|
||||
<div class="root">
|
||||
<Label size="m" color="dark">Screen</Label>
|
||||
<Label small>Screen</Label>
|
||||
<DrawerBindableInput
|
||||
title="Destination URL"
|
||||
placeholder="/screen"
|
||||
value={parameters.url}
|
||||
on:change={value => (parameters.url = value.detail)}
|
||||
{bindings} />
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
<script>
|
||||
import { Select, Label } from "@budibase/bbui"
|
||||
import { currentAsset, store } from "builderStore"
|
||||
import { getDataProviderComponents } from "builderStore/dataBinding"
|
||||
|
||||
export let parameters
|
||||
|
||||
$: dataProviders = getDataProviderComponents(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<Label size="m" color="dark">Form</Label>
|
||||
<Select secondary bind:value={parameters.componentId}>
|
||||
<option value="" />
|
||||
{#if dataProviders}
|
||||
{#each dataProviders 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>
|
|
@ -1,115 +1,89 @@
|
|||
<script>
|
||||
import {
|
||||
DataList,
|
||||
Label,
|
||||
TextButton,
|
||||
Spacer,
|
||||
Select,
|
||||
Input,
|
||||
} from "@budibase/bbui"
|
||||
import { Label, TextButton, Spacer, Select, Input } from "@budibase/bbui"
|
||||
import { store, currentAsset } from "builderStore"
|
||||
import {
|
||||
getBindableProperties,
|
||||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "builderStore/dataBinding"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
import { CloseCircleIcon, AddIcon } from "components/common/Icons"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import DrawerBindableInput from "components/common/DrawerBindableInput.svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let parameterFields
|
||||
export let schemaFields
|
||||
export let fieldLabel = "Column"
|
||||
export let valueLabel = "Value"
|
||||
|
||||
const emptyField = () => ({ name: "", value: "" })
|
||||
|
||||
let fields = Object.entries(parameterFields || {})
|
||||
$: onChange(fields)
|
||||
$: bindableProperties = getBindableProperties(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
|
||||
// this statement initialises fields from parameters.fields
|
||||
$: fields =
|
||||
fields ||
|
||||
Object.keys(parameterFields || {}).map(name => ({
|
||||
name,
|
||||
value:
|
||||
(parameterFields &&
|
||||
runtimeToReadableBinding(
|
||||
bindableProperties,
|
||||
parameterFields[name].value
|
||||
)) ||
|
||||
"",
|
||||
}))
|
||||
|
||||
const addField = () => {
|
||||
const newFields = fields.filter(f => f.name)
|
||||
newFields.push(emptyField())
|
||||
fields = newFields
|
||||
rebuildParameters()
|
||||
fields = [...fields.filter(field => field[0]), ["", ""]]
|
||||
}
|
||||
|
||||
const removeField = field => () => {
|
||||
fields = fields.filter(f => f !== field)
|
||||
rebuildParameters()
|
||||
const removeField = name => {
|
||||
fields = fields.filter(field => field[0] !== name)
|
||||
}
|
||||
|
||||
const rebuildParameters = () => {
|
||||
// rebuilds paramters.fields every time a field name or value is added
|
||||
// as UI below is bound to "fields" array, but we need to output a { key: value }
|
||||
const newParameterFields = {}
|
||||
for (let field of fields) {
|
||||
if (field.name) {
|
||||
// value and type is needed by the client, so it can parse
|
||||
// a string into a correct type
|
||||
newParameterFields[field.name] = {
|
||||
type: schemaFields
|
||||
? schemaFields.find(f => f.name === field.name).type
|
||||
: "string",
|
||||
value: readableToRuntimeBinding(bindableProperties, field.value),
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatch("fieldschanged", newParameterFields)
|
||||
const updateFieldValue = (idx, value) => {
|
||||
fields[idx][1] = value
|
||||
fields = fields
|
||||
}
|
||||
|
||||
// just wraps binding in {{ ... }}
|
||||
const toBindingExpression = bindingPath => `{{ ${bindingPath} }}`
|
||||
const updateFieldName = (idx, name) => {
|
||||
fields[idx][0] = name
|
||||
fields = fields
|
||||
}
|
||||
|
||||
const onChange = fields => {
|
||||
const newParamFields = {}
|
||||
fields
|
||||
.filter(field => field[0])
|
||||
.forEach(([field, value]) => {
|
||||
newParamFields[field] = value
|
||||
})
|
||||
dispatch("change", newParamFields)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if fields}
|
||||
{#each fields as field}
|
||||
<Label size="m" color="dark">{fieldLabel}</Label>
|
||||
{#each fields as field, idx}
|
||||
<Label small>{fieldLabel}</Label>
|
||||
{#if schemaFields}
|
||||
<Select secondary bind:value={field.name} on:blur={rebuildParameters}>
|
||||
<Select
|
||||
thin
|
||||
secondary
|
||||
value={field[0]}
|
||||
on:change={event => updateFieldName(idx, event.target.value)}>
|
||||
<option value="" />
|
||||
{#each schemaFields as schemaField}
|
||||
<option value={schemaField.name}>{schemaField.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{:else}
|
||||
<Input secondary bind:value={field.name} on:blur={rebuildParameters} />
|
||||
<Input
|
||||
thin
|
||||
secondary
|
||||
value={field[0]}
|
||||
on:change={event => updateFieldName(idx, event.target.value)} />
|
||||
{/if}
|
||||
<Label size="m" color="dark">Value</Label>
|
||||
<DataList secondary bind:value={field.value} on:blur={rebuildParameters}>
|
||||
<option value="" />
|
||||
{#each bindableProperties as bindableProp}
|
||||
<option value={toBindingExpression(bindableProp.readableBinding)}>
|
||||
{bindableProp.readableBinding}
|
||||
</option>
|
||||
{/each}
|
||||
</DataList>
|
||||
<Label small>{valueLabel}</Label>
|
||||
<DrawerBindableInput
|
||||
title={`Value for "${field[0]}"`}
|
||||
value={field[1]}
|
||||
bindings={bindableProperties}
|
||||
on:change={event => updateFieldValue(idx, event.detail)} />
|
||||
<div class="remove-field-container">
|
||||
<TextButton text small on:click={removeField(field)}>
|
||||
<TextButton text small on:click={() => removeField(field[0])}>
|
||||
<CloseCircleIcon />
|
||||
</TextButton>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<div>
|
||||
<Spacer small />
|
||||
|
||||
<TextButton text small blue on:click={addField}>
|
||||
Add
|
||||
{fieldLabel}
|
||||
|
|
|
@ -37,8 +37,8 @@
|
|||
Repeater
|
||||
</div>
|
||||
{:else}
|
||||
<Label size="m" color="dark">Datasource</Label>
|
||||
<Select secondary bind:value={parameters.providerId}>
|
||||
<Label small>Datasource</Label>
|
||||
<Select thin secondary bind:value={parameters.providerId}>
|
||||
<option value="" />
|
||||
{#each dataProviderComponents as provider}
|
||||
<option value={provider._id}>{provider._instanceName}</option>
|
||||
|
@ -49,7 +49,7 @@
|
|||
<SaveFields
|
||||
parameterFields={parameters.fields}
|
||||
{schemaFields}
|
||||
on:fieldschanged={onFieldsChanged} />
|
||||
on:change={onFieldsChanged} />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -57,7 +57,7 @@
|
|||
<style>
|
||||
.root {
|
||||
display: grid;
|
||||
column-gap: var(--spacing-s);
|
||||
column-gap: var(--spacing-l);
|
||||
row-gap: var(--spacing-s);
|
||||
grid-template-columns: auto 1fr auto 1fr auto;
|
||||
align-items: baseline;
|
||||
|
@ -71,8 +71,6 @@
|
|||
.cannot-use {
|
||||
color: var(--red);
|
||||
font-size: var(--font-size-s);
|
||||
text-align: center;
|
||||
width: 70%;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -27,13 +27,11 @@
|
|||
schema,
|
||||
}
|
||||
})
|
||||
|
||||
$: hasAutomations = automations && automations.length > 0
|
||||
|
||||
$: selectedAutomation =
|
||||
parameters &&
|
||||
parameters.automationId &&
|
||||
automations.find(a => a._id === parameters.automationId)
|
||||
$: selectedAutomation = automations?.find(
|
||||
a => a._id === parameters?.automationId
|
||||
)
|
||||
$: selectedSchema = selectedAutomation?.schema
|
||||
|
||||
const onFieldsChanged = e => {
|
||||
parameters.fields = e.detail
|
||||
|
@ -42,95 +40,98 @@
|
|||
const setNew = () => {
|
||||
automationStatus = AUTOMATION_STATUS.NEW
|
||||
parameters.automationId = undefined
|
||||
parameters.fields = {}
|
||||
}
|
||||
|
||||
const setExisting = () => {
|
||||
automationStatus = AUTOMATION_STATUS.EXISTING
|
||||
parameters.newAutomationName = ""
|
||||
parameters.fields = {}
|
||||
parameters.automationId = automations[0]?._id
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<div class="radio-container" on:click={setNew}>
|
||||
<input
|
||||
type="radio"
|
||||
value={AUTOMATION_STATUS.NEW}
|
||||
bind:group={automationStatus}
|
||||
disabled={!hasAutomations} />
|
||||
|
||||
<Label disabled={!hasAutomations}>Create a new automation</Label>
|
||||
<div class="radios">
|
||||
<div class="radio-container" on:click={setNew}>
|
||||
<input
|
||||
type="radio"
|
||||
value={AUTOMATION_STATUS.NEW}
|
||||
bind:group={automationStatus} />
|
||||
<Label small>Create a new automation</Label>
|
||||
</div>
|
||||
<div class="radio-container" on:click={hasAutomations ? setExisting : null}>
|
||||
<input
|
||||
type="radio"
|
||||
value={AUTOMATION_STATUS.EXISTING}
|
||||
bind:group={automationStatus}
|
||||
disabled={!hasAutomations} />
|
||||
<Label small grey={!hasAutomations}>Use an existing automation</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="radio-container" on:click={setExisting}>
|
||||
<input
|
||||
type="radio"
|
||||
value={AUTOMATION_STATUS.EXISTING}
|
||||
bind:group={automationStatus}
|
||||
disabled={!hasAutomations} />
|
||||
<div class="fields">
|
||||
<Label small>Automation</Label>
|
||||
|
||||
<Label disabled={!hasAutomations}>Use an existing automation</Label>
|
||||
{#if automationStatus === AUTOMATION_STATUS.EXISTING}
|
||||
<Select
|
||||
thin
|
||||
secondary
|
||||
bind:value={parameters.automationId}
|
||||
placeholder="Choose automation">
|
||||
{#each automations as automation}
|
||||
<option value={automation._id}>{automation.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{:else}
|
||||
<Input
|
||||
thin
|
||||
bind:value={parameters.newAutomationName}
|
||||
placeholder="Enter automation name" />
|
||||
{/if}
|
||||
|
||||
{#key parameters.automationId}
|
||||
<SaveFields
|
||||
schemaFields={selectedSchema}
|
||||
parameterFields={parameters.fields}
|
||||
fieldLabel="Field"
|
||||
on:change={onFieldsChanged} />
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
<Label size="m" color="dark">Automation</Label>
|
||||
|
||||
{#if automationStatus === AUTOMATION_STATUS.EXISTING}
|
||||
<Select
|
||||
secondary
|
||||
bind:value={parameters.automationId}
|
||||
placeholder="Choose automation">
|
||||
<option value="" />
|
||||
{#each automations as automation}
|
||||
<option value={automation._id}>{automation.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{:else}
|
||||
<Input
|
||||
secondary
|
||||
bind:value={parameters.newAutomationName}
|
||||
placeholder="Enter automation name" />
|
||||
{/if}
|
||||
|
||||
<SaveFields
|
||||
schemaFields={automationStatus === AUTOMATION_STATUS.EXISTING && selectedAutomation && selectedAutomation.schema}
|
||||
fieldLabel="Field"
|
||||
on:fieldschanged={onFieldsChanged} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
.fields {
|
||||
display: grid;
|
||||
column-gap: var(--spacing-s);
|
||||
column-gap: var(--spacing-l);
|
||||
row-gap: var(--spacing-s);
|
||||
grid-template-columns: auto 1fr auto 1fr auto;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.root :global(> div:nth-child(4)) {
|
||||
.fields :global(> div:nth-child(2)) {
|
||||
grid-column: 2 / span 4;
|
||||
}
|
||||
|
||||
.radios,
|
||||
.radio-container {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
.radios {
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
.radio-container {
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
.radio-container :global(label) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.radio-container:nth-child(1) {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
.radio-container:nth-child(2) {
|
||||
grid-column: 3 / span 3;
|
||||
}
|
||||
|
||||
.radio-container :global(> label) {
|
||||
margin-left: var(--spacing-m);
|
||||
}
|
||||
|
||||
.radio-container > input {
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.radio-container > input:focus {
|
||||
outline: none;
|
||||
input[type="radio"]:checked {
|
||||
background: var(--blue);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
</script>
|
||||
|
||||
<div class="root">
|
||||
<Label size="m" color="dark">Form</Label>
|
||||
<Select secondary bind:value={parameters.componentId}>
|
||||
<Label small>Form</Label>
|
||||
<Select thin secondary bind:value={parameters.componentId}>
|
||||
<option value="" />
|
||||
{#if actionProviders}
|
||||
{#each actionProviders as component}
|
||||
|
|
|
@ -4,7 +4,6 @@ import DeleteRow from "./DeleteRow.svelte"
|
|||
import ExecuteQuery from "./ExecuteQuery.svelte"
|
||||
import TriggerAutomation from "./TriggerAutomation.svelte"
|
||||
import ValidateForm from "./ValidateForm.svelte"
|
||||
import RefreshDatasource from "./RefreshDatasource.svelte"
|
||||
|
||||
// defines what actions are available, when adding a new one
|
||||
// the component is the setup panel for the action
|
||||
|
@ -36,8 +35,4 @@ export default [
|
|||
name: "Validate Form",
|
||||
component: ValidateForm,
|
||||
},
|
||||
{
|
||||
name: "Refresh Datasource",
|
||||
component: RefreshDatasource,
|
||||
},
|
||||
]
|
||||
|
|
|
@ -43,13 +43,19 @@
|
|||
<div class="root">
|
||||
{#if !Object.keys(tempValue || {}).length}
|
||||
<p>Add your first filter column.</p>
|
||||
<Spacer small />
|
||||
{:else}
|
||||
<p>
|
||||
Results are filtered to only those which match all of the following
|
||||
constaints.
|
||||
</p>
|
||||
{/if}
|
||||
<Spacer small />
|
||||
<div class="fields">
|
||||
<SaveFields
|
||||
parameterFields={value}
|
||||
{schemaFields}
|
||||
on:fieldschanged={onFieldsChanged} />
|
||||
valueLabel="Equals"
|
||||
on:change={onFieldsChanged} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,17 +63,18 @@
|
|||
|
||||
<style>
|
||||
.root {
|
||||
padding: var(--spacing-m);
|
||||
min-height: 40vh;
|
||||
padding: var(--spacing-l);
|
||||
min-height: calc(40vh - 2 * var(--spacing-l));
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 var(--spacing-s) 0;
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
|
||||
.fields {
|
||||
display: grid;
|
||||
column-gap: var(--spacing-s);
|
||||
column-gap: var(--spacing-l);
|
||||
row-gap: var(--spacing-s);
|
||||
grid-template-columns: auto 1fr auto 1fr auto;
|
||||
align-items: baseline;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
export let lineNumbers = true
|
||||
export let tab = true
|
||||
export let mode
|
||||
export let editorHeight = 500
|
||||
// export let parameters = []
|
||||
|
||||
let completions = handlebarsCompletions()
|
||||
|
@ -169,15 +170,17 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<textarea tabindex="0" bind:this={refs.editor} readonly {value} />
|
||||
<div style={`--code-mirror-height: ${editorHeight}px`}>
|
||||
<textarea tabindex="0" bind:this={refs.editor} readonly {value} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
textarea {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
:global(.CodeMirror) {
|
||||
height: 500px !important;
|
||||
div :global(.CodeMirror) {
|
||||
height: var(--code-mirror-height) !important;
|
||||
border-radius: var(--border-radius-s);
|
||||
font-family: monospace !important;
|
||||
line-height: 1.3;
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
<script>
|
||||
import {
|
||||
Button,
|
||||
TextArea,
|
||||
Label,
|
||||
Input,
|
||||
Heading,
|
||||
Select,
|
||||
} from "@budibase/bbui"
|
||||
import { Input } from "@budibase/bbui"
|
||||
import Editor from "./QueryEditor.svelte"
|
||||
|
||||
export let fields = {}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script>
|
||||
import { Button, Input, Heading, Spacer } from "@budibase/bbui"
|
||||
import BindableInput from "components/common/BindableInput.svelte"
|
||||
import { Button, Input, Label } from "@budibase/bbui"
|
||||
import {
|
||||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "builderStore/dataBinding"
|
||||
import DrawerBindableInput from "components/common/DrawerBindableInput.svelte"
|
||||
|
||||
export let bindable = true
|
||||
export let parameters = []
|
||||
|
@ -30,8 +30,7 @@
|
|||
</script>
|
||||
|
||||
<section>
|
||||
<Heading extraSmall black>Parameters</Heading>
|
||||
<Spacer large />
|
||||
<Label small>Parameters</Label>
|
||||
<div class="parameters" class:bindable>
|
||||
{#each parameters as parameter, idx}
|
||||
<Input
|
||||
|
@ -45,9 +44,9 @@
|
|||
disabled={bindable}
|
||||
bind:value={parameter.default} />
|
||||
{#if bindable}
|
||||
<BindableInput
|
||||
<DrawerBindableInput
|
||||
title={`Query parameter "${parameter.name}"`}
|
||||
placeholder="Value"
|
||||
type="string"
|
||||
thin
|
||||
on:change={evt => onBindingChange(parameter.name, evt.detail)}
|
||||
value={runtimeToReadableBinding(bindings, customParams?.[parameter.name])}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import { TextArea, Label, Input, Heading, Spacer } from "@budibase/bbui"
|
||||
import Editor from "./QueryEditor.svelte"
|
||||
import ParameterBuilder from "./QueryParameterBuilder.svelte"
|
||||
import FieldsBuilder from "./QueryFieldsBuilder.svelte"
|
||||
|
||||
const QueryTypes = {
|
||||
|
@ -14,6 +11,7 @@
|
|||
export let query
|
||||
export let schema
|
||||
export let editable = true
|
||||
export let height = 500
|
||||
|
||||
function updateQuery({ detail }) {
|
||||
query.fields[schema.type] = detail.value
|
||||
|
@ -24,6 +22,7 @@
|
|||
{#key query._id}
|
||||
{#if schema.type === QueryTypes.SQL}
|
||||
<Editor
|
||||
editorHeight={height}
|
||||
label="Query"
|
||||
mode="sql"
|
||||
on:change={updateQuery}
|
||||
|
@ -32,6 +31,7 @@
|
|||
parameters={query.parameters} />
|
||||
{:else if schema.type === QueryTypes.JSON}
|
||||
<Editor
|
||||
editorHeight={height}
|
||||
label="Query"
|
||||
mode="json"
|
||||
on:change={updateQuery}
|
||||
|
|
|
@ -8,8 +8,8 @@ const saveRowHandler = async (action, context) => {
|
|||
if (providerId) {
|
||||
let draft = context[providerId]
|
||||
if (fields) {
|
||||
for (let [key, entry] of Object.entries(fields)) {
|
||||
draft[key] = entry.value
|
||||
for (let [field, value] of Object.entries(fields)) {
|
||||
draft[field] = value
|
||||
}
|
||||
}
|
||||
await saveRow(draft)
|
||||
|
@ -26,11 +26,7 @@ const deleteRowHandler = async action => {
|
|||
const triggerAutomationHandler = async action => {
|
||||
const { fields } = action.parameters
|
||||
if (fields) {
|
||||
const params = {}
|
||||
for (let field in fields) {
|
||||
params[field] = fields[field].value
|
||||
}
|
||||
await triggerAutomation(action.parameters.automationId, params)
|
||||
await triggerAutomation(action.parameters.automationId, fields)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,10 +35,10 @@
|
|||
return rows
|
||||
}
|
||||
let filteredData = [...rows]
|
||||
Object.keys(filter).forEach(field => {
|
||||
if (filter[field].value != null && filter[field].value !== "") {
|
||||
Object.entries(filter).forEach(([field, value]) => {
|
||||
if (value != null && value !== "") {
|
||||
filteredData = filteredData.filter(row => {
|
||||
return row[field] === filter[field].value
|
||||
return row[field] === value
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue